diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/source/treelist | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | vcl/source/treelist/headbar.cxx | 1302 | ||||
-rw-r--r-- | vcl/source/treelist/iconview.cxx | 315 | ||||
-rw-r--r-- | vcl/source/treelist/iconviewimpl.cxx | 741 | ||||
-rw-r--r-- | vcl/source/treelist/iconviewimpl.hxx | 91 | ||||
-rw-r--r-- | vcl/source/treelist/imap.cxx | 994 | ||||
-rw-r--r-- | vcl/source/treelist/imap2.cxx | 530 | ||||
-rw-r--r-- | vcl/source/treelist/imap3.cxx | 84 | ||||
-rw-r--r-- | vcl/source/treelist/inetimg.cxx | 136 | ||||
-rw-r--r-- | vcl/source/treelist/svimpbox.cxx | 3142 | ||||
-rw-r--r-- | vcl/source/treelist/svlbitm.cxx | 550 | ||||
-rw-r--r-- | vcl/source/treelist/svtabbx.cxx | 1078 | ||||
-rw-r--r-- | vcl/source/treelist/transfer.cxx | 2227 | ||||
-rw-r--r-- | vcl/source/treelist/transfer2.cxx | 516 | ||||
-rw-r--r-- | vcl/source/treelist/treelist.cxx | 1524 | ||||
-rw-r--r-- | vcl/source/treelist/treelistbox.cxx | 3612 | ||||
-rw-r--r-- | vcl/source/treelist/treelistentry.cxx | 235 | ||||
-rw-r--r-- | vcl/source/treelist/uiobject.cxx | 185 | ||||
-rw-r--r-- | vcl/source/treelist/viewdataentry.cxx | 87 |
18 files changed, 17349 insertions, 0 deletions
diff --git a/vcl/source/treelist/headbar.cxx b/vcl/source/treelist/headbar.cxx new file mode 100644 index 000000000..3bd6bbaa3 --- /dev/null +++ b/vcl/source/treelist/headbar.cxx @@ -0,0 +1,1302 @@ +/* -*- 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 <vcl/headbar.hxx> +#include <rtl/ustrbuf.hxx> +#include <tools/debug.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/help.hxx> +#include <vcl/image.hxx> +#include <vcl/salnativewidgets.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/ptrstyle.hxx> + +#include <com/sun/star/accessibility/XAccessible.hpp> + +class ImplHeadItem +{ +public: + sal_uInt16 mnId; + HeaderBarItemBits mnBits; + tools::Long mnSize; + OString maHelpId; + Image maImage; + OUString maOutText; + OUString maText; + OUString maHelpText; +}; + +#define HEAD_ARROWSIZE1 4 +#define HEAD_ARROWSIZE2 7 + +#define HEADERBAR_TEXTOFF 2 +#define HEADERBAR_ARROWOFF 5 +#define HEADERBAR_SPLITOFF 3 + +#define HEADERBAR_DRAGOUTOFF 15 + +#define HEAD_HITTEST_ITEM (sal_uInt16(0x0001)) +#define HEAD_HITTEST_DIVIDER (sal_uInt16(0x0002)) + +void HeaderBar::ImplInit( WinBits nWinStyle ) +{ + mnBorderOff1 = 0; + mnBorderOff2 = 0; + mnOffset = 0; + mnDX = 0; + mnDY = 0; + mnDragSize = 0; + mnStartPos = 0; + mnDragPos = 0; + mnMouseOff = 0; + mnCurItemId = 0; + mnItemDragPos = HEADERBAR_ITEM_NOTFOUND; + mbDrag = false; + mbItemDrag = false; + mbOutDrag = false; + mbItemMode = false; + + // evaluate StyleBits + if ( nWinStyle & WB_DRAG ) + mbDragable = true; + else + mbDragable = false; + if ( nWinStyle & WB_BUTTONSTYLE ) + mbButtonStyle = true; + else + mbButtonStyle = false; + if ( nWinStyle & WB_BORDER ) + { + mnBorderOff1 = 1; + mnBorderOff2 = 1; + } + else + { + if ( nWinStyle & WB_BOTTOMBORDER ) + mnBorderOff2 = 1; + } + + ImplInitSettings( true, true, true ); +} + +HeaderBar::HeaderBar(vcl::Window* pParent, WinBits nWinStyle) + : Window(pParent, nWinStyle & WB_3DLOOK) +{ + SetType(WindowType::HEADERBAR); + ImplInit(nWinStyle); + SetSizePixel( CalcWindowSizePixel() ); +} + +Size HeaderBar::GetOptimalSize() const +{ + return CalcWindowSizePixel(); +} + +HeaderBar::~HeaderBar() = default; + +void HeaderBar::ApplySettings(vcl::RenderContext& rRenderContext) +{ + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + + ApplyControlFont(rRenderContext, rStyleSettings.GetToolFont()); + + ApplyControlForeground(rRenderContext, rStyleSettings.GetButtonTextColor()); + SetTextFillColor(); + + ApplyControlBackground(rRenderContext, rStyleSettings.GetFaceColor()); +} + +void HeaderBar::ImplInitSettings(bool bFont, bool bForeground, bool bBackground) +{ + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + + if (bFont) + ApplyControlFont(*GetOutDev(), rStyleSettings.GetToolFont()); + + if (bForeground || bFont) + { + ApplyControlForeground(*GetOutDev(), rStyleSettings.GetButtonTextColor()); + SetTextFillColor(); + } + + if (bBackground) + ApplyControlBackground(*GetOutDev(), rStyleSettings.GetFaceColor()); +} + +tools::Long HeaderBar::ImplGetItemPos( sal_uInt16 nPos ) const +{ + tools::Long nX = -mnOffset; + for ( size_t i = 0; i < nPos; i++ ) + nX += mvItemList[ i ]->mnSize; + return nX; +} + +tools::Rectangle HeaderBar::ImplGetItemRect( sal_uInt16 nPos ) const +{ + tools::Rectangle aRect( ImplGetItemPos( nPos ), 0, 0, mnDY-1 ); + aRect.SetRight( aRect.Left() + mvItemList[ nPos ]->mnSize - 1 ); + // check for overflow on various systems + if ( aRect.Right() > 16000 ) + aRect.SetRight( 16000 ); + return aRect; +} + +sal_uInt16 HeaderBar::ImplDoHitTest( const Point& rPos, + tools::Long& nMouseOff, sal_uInt16& nPos ) const +{ + size_t nCount = static_cast<sal_uInt16>(mvItemList.size()); + bool bLastFixed = true; + tools::Long nX = -mnOffset; + + for ( size_t i = 0; i < nCount; i++ ) + { + auto& pItem = mvItemList[ i ]; + + if ( rPos.X() < (nX+pItem->mnSize) ) + { + sal_uInt16 nMode; + + if ( !bLastFixed && (rPos.X() < (nX+HEADERBAR_SPLITOFF)) ) + { + nMode = HEAD_HITTEST_DIVIDER; + nPos = i-1; + nMouseOff = rPos.X()-nX+1; + } + else + { + nPos = i; + + if ( rPos.X() >= (nX+pItem->mnSize-HEADERBAR_SPLITOFF) ) + { + nMode = HEAD_HITTEST_DIVIDER; + nMouseOff = rPos.X()-(nX+pItem->mnSize); + } + else + { + nMode = HEAD_HITTEST_ITEM; + nMouseOff = rPos.X()-nX; + } + } + + return nMode; + } + + bLastFixed = false; + + nX += pItem->mnSize; + } + + if ( !bLastFixed ) + { + auto& pItem = mvItemList[ nCount-1 ]; + if ( (pItem->mnSize < 4) && (rPos.X() < (nX+HEADERBAR_SPLITOFF)) ) + { + nPos = nCount-1; + nMouseOff = rPos.X()-nX+1; + return HEAD_HITTEST_DIVIDER; + } + } + + return 0; +} + +void HeaderBar::ImplInvertDrag( sal_uInt16 nStartPos, sal_uInt16 nEndPos ) +{ + tools::Rectangle aRect1 = ImplGetItemRect( nStartPos ); + tools::Rectangle aRect2 = ImplGetItemRect( nEndPos ); + Point aStartPos = aRect1.Center(); + Point aEndPos = aStartPos; + tools::Rectangle aStartRect( aStartPos.X()-2, aStartPos.Y()-2, + aStartPos.X()+2, aStartPos.Y()+2 ); + + if ( nEndPos > nStartPos ) + { + aStartPos.AdjustX(3 ); + aEndPos.setX( aRect2.Right()-6 ); + } + else + { + aStartPos.AdjustX( -3 ); + aEndPos.setX( aRect2.Left()+6 ); + } + + GetOutDev()->SetRasterOp( RasterOp::Invert ); + GetOutDev()->DrawRect( aStartRect ); + GetOutDev()->DrawLine( aStartPos, aEndPos ); + if ( nEndPos > nStartPos ) + { + GetOutDev()->DrawLine( Point( aEndPos.X()+1, aEndPos.Y()-3 ), + Point( aEndPos.X()+1, aEndPos.Y()+3 ) ); + GetOutDev()->DrawLine( Point( aEndPos.X()+2, aEndPos.Y()-2 ), + Point( aEndPos.X()+2, aEndPos.Y()+2 ) ); + GetOutDev()->DrawLine( Point( aEndPos.X()+3, aEndPos.Y()-1 ), + Point( aEndPos.X()+3, aEndPos.Y()+1 ) ); + GetOutDev()->DrawPixel( Point( aEndPos.X()+4, aEndPos.Y() ) ); + } + else + { + GetOutDev()->DrawLine( Point( aEndPos.X()-1, aEndPos.Y()-3 ), + Point( aEndPos.X()-1, aEndPos.Y()+3 ) ); + GetOutDev()->DrawLine( Point( aEndPos.X()-2, aEndPos.Y()-2 ), + Point( aEndPos.X()-2, aEndPos.Y()+2 ) ); + GetOutDev()->DrawLine( Point( aEndPos.X()-3, aEndPos.Y()-1 ), + Point( aEndPos.X()-3, aEndPos.Y()+1 ) ); + GetOutDev()->DrawPixel( Point( aEndPos.X()-4, aEndPos.Y() ) ); + } + GetOutDev()->SetRasterOp( RasterOp::OverPaint ); +} + +void HeaderBar::ImplDrawItem(vcl::RenderContext& rRenderContext, sal_uInt16 nPos, bool bHigh, + const tools::Rectangle& rItemRect, const tools::Rectangle* pRect ) +{ + ImplControlValue aControlValue(0); + tools::Rectangle aCtrlRegion; + ControlState nState(ControlState::NONE); + + tools::Rectangle aRect = rItemRect; + + // do not display if there is no space + if (aRect.GetWidth() <= 1) + return; + + // check of rectangle is visible + if (pRect) + { + if (aRect.Right() < pRect->Left()) + return; + else if (aRect.Left() > pRect->Right()) + return; + } + else + { + if (aRect.Right() < 0) + return; + else if (aRect.Left() > mnDX) + return; + } + + auto& pItem = mvItemList[nPos]; + HeaderBarItemBits nBits = pItem->mnBits; + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + if (rRenderContext.IsNativeControlSupported(ControlType::WindowBackground, ControlPart::Entire)) + { + aCtrlRegion = aRect; + rRenderContext.DrawNativeControl(ControlType::WindowBackground, ControlPart::Entire, + aCtrlRegion, nState, aControlValue, OUString()); + + } + else + { + // do not draw border + aRect.AdjustTop(mnBorderOff1 ); + aRect.AdjustBottom( -mnBorderOff2 ); + + // delete background + if ( !pRect ) + { + rRenderContext.DrawWallpaper(aRect, rRenderContext.GetBackground()); + } + } + + Color aSelectionTextColor(COL_TRANSPARENT); + + if (rRenderContext.IsNativeControlSupported(ControlType::ListHeader, ControlPart::Button)) + { + aCtrlRegion = aRect; + aControlValue.setTristateVal(ButtonValue::On); + nState |= ControlState::ENABLED; + if (bHigh) + nState |= ControlState::PRESSED; + rRenderContext.DrawNativeControl(ControlType::ListHeader, ControlPart::Button, + aCtrlRegion, nState, aControlValue, OUString()); + } + else + { + // draw separation line + rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor()); + rRenderContext.DrawLine(Point(aRect.Right(), aRect.Top()), Point(aRect.Right(), aRect.Bottom())); + + // draw ButtonStyle + // avoid 3D borders + if (bHigh) + vcl::RenderTools::DrawSelectionBackground(rRenderContext, *this, aRect, 1, true, false, false, &aSelectionTextColor); + else if (!mbButtonStyle || (nBits & HeaderBarItemBits::FLAT)) + vcl::RenderTools::DrawSelectionBackground(rRenderContext, *this, aRect, 0, true, false, false, &aSelectionTextColor); + } + + // do not draw if there is no space + if (aRect.GetWidth() < 1) + return; + + // calculate size and position and draw content + pItem->maOutText = pItem->maText; + Size aImageSize = pItem->maImage.GetSizePixel(); + Size aTxtSize(rRenderContext.GetTextWidth(pItem->maOutText), 0); + if (!pItem->maOutText.isEmpty()) + aTxtSize.setHeight( rRenderContext.GetTextHeight() ); + tools::Long nArrowWidth = 0; + if (nBits & (HeaderBarItemBits::UPARROW | HeaderBarItemBits::DOWNARROW)) + nArrowWidth = HEAD_ARROWSIZE2 + HEADERBAR_ARROWOFF; + + // do not draw if there is not enough space for the image + tools::Long nTestHeight = aImageSize.Height(); + if (!(nBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE))) + nTestHeight += aTxtSize.Height(); + if ((aImageSize.Width() > aRect.GetWidth()) || (nTestHeight > aRect.GetHeight())) + { + aImageSize.setWidth( 0 ); + aImageSize.setHeight( 0 ); + } + + // cut text to correct length + bool bLeftText = false; + tools::Long nMaxTxtWidth = aRect.GetWidth() - (HEADERBAR_TEXTOFF * 2) - nArrowWidth; + if (nBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE)) + nMaxTxtWidth -= aImageSize.Width(); + tools::Long nTxtWidth = aTxtSize.Width(); + if (nTxtWidth > nMaxTxtWidth) + { + bLeftText = true; + OUStringBuffer aBuf(pItem->maOutText); + aBuf.append("..."); + do + { + aBuf.remove(aBuf.getLength() - 3 - 1, 1); + nTxtWidth = rRenderContext.GetTextWidth(aBuf.toString()); + } + while ((nTxtWidth > nMaxTxtWidth) && (aBuf.getLength() > 3)); + pItem->maOutText = aBuf.makeStringAndClear(); + if (pItem->maOutText.getLength() == 3) + { + nTxtWidth = 0; + pItem->maOutText.clear(); + } + } + + // calculate text/imageposition + tools::Long nTxtPos; + if (!bLeftText && (nBits & HeaderBarItemBits::RIGHT)) + { + nTxtPos = aRect.Right() - nTxtWidth - HEADERBAR_TEXTOFF; + if (nBits & HeaderBarItemBits::RIGHTIMAGE) + nTxtPos -= aImageSize.Width(); + } + else if (!bLeftText && (nBits & HeaderBarItemBits::CENTER)) + { + tools::Long nTempWidth = nTxtWidth; + if (nBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE)) + nTempWidth += aImageSize.Width(); + nTxtPos = aRect.Left() + (aRect.GetWidth() - nTempWidth) / 2; + if (nBits & HeaderBarItemBits::LEFTIMAGE) + nTxtPos += aImageSize.Width(); + if (nArrowWidth) + { + if (nTxtPos + nTxtWidth + nArrowWidth >= aRect.Right()) + { + nTxtPos = aRect.Left() + HEADERBAR_TEXTOFF; + if (nBits & HeaderBarItemBits::LEFTIMAGE) + nTxtPos += aImageSize.Width(); + } + } + } + else + { + nTxtPos = aRect.Left() + HEADERBAR_TEXTOFF; + if (nBits & HeaderBarItemBits::LEFTIMAGE) + nTxtPos += aImageSize.Width(); + if (nBits & HeaderBarItemBits::RIGHT) + nTxtPos += nArrowWidth; + } + + // calculate text/imageposition + tools::Long nTxtPosY = 0; + if (!pItem->maOutText.isEmpty() || (nArrowWidth && aTxtSize.Height())) + { + tools::Long nTempHeight = aTxtSize.Height(); + nTempHeight += aImageSize.Height(); + nTxtPosY = aRect.Top()+((aRect.GetHeight()-nTempHeight)/2); + if (!(nBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE))) + nTxtPosY += aImageSize.Height(); + } + + // display text + if (!pItem->maOutText.isEmpty()) + { + if (aSelectionTextColor != COL_TRANSPARENT) + { + rRenderContext.Push(vcl::PushFlags::TEXTCOLOR); + rRenderContext.SetTextColor(aSelectionTextColor); + } + if (IsEnabled()) + rRenderContext.DrawText(Point(nTxtPos, nTxtPosY), pItem->maOutText); + else + rRenderContext.DrawCtrlText(Point(nTxtPos, nTxtPosY), pItem->maOutText, 0, pItem->maOutText.getLength(), DrawTextFlags::Disable); + if (aSelectionTextColor != COL_TRANSPARENT) + rRenderContext.Pop(); + } + + // calculate the position and draw image if it is available + tools::Long nImagePosY = 0; + if (aImageSize.Width() && aImageSize.Height()) + { + tools::Long nImagePos = nTxtPos; + if (nBits & HeaderBarItemBits::LEFTIMAGE) + { + nImagePos -= aImageSize.Width(); + if (nBits & HeaderBarItemBits::RIGHT) + nImagePos -= nArrowWidth; + } + else if (nBits & HeaderBarItemBits::RIGHTIMAGE) + { + nImagePos += nTxtWidth; + if (!(nBits & HeaderBarItemBits::RIGHT)) + nImagePos += nArrowWidth; + } + else + { + if (nBits & HeaderBarItemBits::RIGHT ) + nImagePos = aRect.Right()-aImageSize.Width(); + else if (nBits & HeaderBarItemBits::CENTER) + nImagePos = aRect.Left() + (aRect.GetWidth() - aImageSize.Width()) / 2; + else + nImagePos = aRect.Left() + HEADERBAR_TEXTOFF; + } + + tools::Long nTempHeight = aImageSize.Height(); + if (!(nBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE))) + nTempHeight += aTxtSize.Height(); + nImagePosY = aRect.Top() + ((aRect.GetHeight() - nTempHeight) / 2); + + if (nImagePos + aImageSize.Width() <= aRect.Right()) + { + DrawImageFlags nStyle = DrawImageFlags::NONE; + if (!IsEnabled()) + nStyle |= DrawImageFlags::Disable; + rRenderContext.DrawImage(Point(nImagePos, nImagePosY), pItem->maImage, nStyle); + } + } + + if (!(nBits & (HeaderBarItemBits::UPARROW | HeaderBarItemBits::DOWNARROW))) + return; + + tools::Long nArrowX = nTxtPos; + if (nBits & HeaderBarItemBits::RIGHT) + nArrowX -= nArrowWidth; + else + nArrowX += nTxtWidth + HEADERBAR_ARROWOFF; + if (!(nBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE)) && pItem->maText.isEmpty()) + { + if (nBits & HeaderBarItemBits::RIGHT) + nArrowX -= aImageSize.Width(); + else + nArrowX += aImageSize.Width(); + } + + // is there enough space to draw the item? + bool bDraw = true; + if (nArrowX < aRect.Left() + HEADERBAR_TEXTOFF) + bDraw = false; + else if (nArrowX + HEAD_ARROWSIZE2 > aRect.Right()) + bDraw = false; + + if (!bDraw) + return; + + if (rRenderContext.IsNativeControlSupported(ControlType::ListHeader, ControlPart::Arrow)) + { + aCtrlRegion = tools::Rectangle(Point(nArrowX, aRect.Top()), Size(nArrowWidth, aRect.GetHeight())); + // control value passes 1 if arrow points down, 0 otherwise + aControlValue.setNumericVal((nBits & HeaderBarItemBits::DOWNARROW) ? 1 : 0); + nState |= ControlState::ENABLED; + if (bHigh) + nState |= ControlState::PRESSED; + rRenderContext.DrawNativeControl(ControlType::ListHeader, ControlPart::Arrow, aCtrlRegion, + nState, aControlValue, OUString()); + } + else + { + tools::Long nArrowY; + if (aTxtSize.Height()) + nArrowY = nTxtPosY + (aTxtSize.Height() / 2); + else if (aImageSize.Width() && aImageSize.Height()) + nArrowY = nImagePosY + (aImageSize.Height() / 2); + else + nArrowY = aRect.Top() + ((aRect.GetHeight() - HEAD_ARROWSIZE2) / 2); + nArrowY -= HEAD_ARROWSIZE1 - 1; + if (nBits & HeaderBarItemBits::DOWNARROW) + { + rRenderContext.SetLineColor(rStyleSettings.GetLightColor()); + rRenderContext.DrawLine(Point(nArrowX, nArrowY), + Point(nArrowX + HEAD_ARROWSIZE2, nArrowY)); + rRenderContext.DrawLine(Point(nArrowX, nArrowY), + Point(nArrowX + HEAD_ARROWSIZE1, nArrowY + HEAD_ARROWSIZE2)); + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + rRenderContext.DrawLine(Point(nArrowX + HEAD_ARROWSIZE1, nArrowY + HEAD_ARROWSIZE2), + Point(nArrowX + HEAD_ARROWSIZE2, nArrowY)); + } + else + { + rRenderContext.SetLineColor(rStyleSettings.GetLightColor()); + rRenderContext.DrawLine(Point(nArrowX, nArrowY + HEAD_ARROWSIZE2), + Point(nArrowX + HEAD_ARROWSIZE1, nArrowY)); + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + rRenderContext.DrawLine(Point(nArrowX, nArrowY + HEAD_ARROWSIZE2), + Point(nArrowX + HEAD_ARROWSIZE2, nArrowY + HEAD_ARROWSIZE2)); + rRenderContext.DrawLine(Point(nArrowX + HEAD_ARROWSIZE2, nArrowY + HEAD_ARROWSIZE2), + Point(nArrowX + HEAD_ARROWSIZE1, nArrowY)); + } + } +} + +void HeaderBar::ImplDrawItem(vcl::RenderContext& rRenderContext, sal_uInt16 nPos, + bool bHigh, const tools::Rectangle* pRect ) +{ + tools::Rectangle aRect = ImplGetItemRect(nPos); + ImplDrawItem(rRenderContext, nPos, bHigh, aRect, pRect ); +} + +void HeaderBar::ImplUpdate(sal_uInt16 nPos, bool bEnd) +{ + if (!(IsVisible() && IsUpdateMode())) + return; + + tools::Rectangle aRect; + size_t nItemCount = mvItemList.size(); + if (nPos < nItemCount) + aRect = ImplGetItemRect(nPos); + else + { + aRect.SetBottom( mnDY - 1 ); + if (nItemCount) + aRect.SetLeft( ImplGetItemRect(nItemCount - 1).Right() ); + } + if (bEnd) + aRect.SetRight( mnDX - 1 ); + aRect.AdjustTop(mnBorderOff1 ); + aRect.AdjustBottom( -mnBorderOff2 ); + Invalidate(aRect); +} + +void HeaderBar::ImplStartDrag( const Point& rMousePos, bool bCommand ) +{ + sal_uInt16 nPos; + sal_uInt16 nHitTest = ImplDoHitTest( rMousePos, mnMouseOff, nPos ); + if ( !nHitTest ) + return; + + mbDrag = false; + auto& pItem = mvItemList[ nPos ]; + if ( nHitTest & HEAD_HITTEST_DIVIDER ) + mbDrag = true; + else + { + if ( ((pItem->mnBits & HeaderBarItemBits::CLICKABLE) && !(pItem->mnBits & HeaderBarItemBits::FLAT)) || + mbDragable ) + { + mbItemMode = true; + mbDrag = true; + if ( bCommand ) + { + if ( mbDragable ) + mbItemDrag = true; + else + { + mbItemMode = false; + mbDrag = false; + } + } + } + else + { + if ( !bCommand ) + { + mnCurItemId = pItem->mnId; + Select(); + mnCurItemId = 0; + } + } + } + + if ( mbDrag ) + { + mbOutDrag = false; + mnCurItemId = pItem->mnId; + mnItemDragPos = nPos; + StartTracking(); + mnStartPos = rMousePos.X()-mnMouseOff; + mnDragPos = mnStartPos; + maStartDragHdl.Call( this ); + if (mbItemMode) + Invalidate(); + else + { + tools::Rectangle aSizeRect( mnDragPos, 0, mnDragPos, mnDragSize+mnDY ); + ShowTracking( aSizeRect, ShowTrackFlags::Split ); + } + } + else + mnMouseOff = 0; +} + +void HeaderBar::ImplDrag( const Point& rMousePos ) +{ + sal_uInt16 nPos = GetItemPos( mnCurItemId ); + + mnDragPos = rMousePos.X()-mnMouseOff; + if ( mbItemMode ) + { + bool bNewOutDrag; + + tools::Rectangle aItemRect = ImplGetItemRect( nPos ); + bNewOutDrag = !aItemRect.Contains( rMousePos ); + + // if needed switch on ItemDrag + if ( bNewOutDrag && mbDragable && !mbItemDrag ) + { + if ( (rMousePos.Y() >= aItemRect.Top()) && (rMousePos.Y() <= aItemRect.Bottom()) ) + { + mbItemDrag = true; + Invalidate(); + } + } + + sal_uInt16 nOldItemDragPos = mnItemDragPos; + if ( mbItemDrag ) + { + bNewOutDrag = (rMousePos.Y() < -HEADERBAR_DRAGOUTOFF) || (rMousePos.Y() > mnDY+HEADERBAR_DRAGOUTOFF); + + if ( bNewOutDrag ) + mnItemDragPos = HEADERBAR_ITEM_NOTFOUND; + else + { + sal_uInt16 nTempId = GetItemId( Point( rMousePos.X(), 2 ) ); + if ( nTempId ) + mnItemDragPos = GetItemPos( nTempId ); + else + { + if ( rMousePos.X() <= 0 ) + mnItemDragPos = 0; + else + mnItemDragPos = GetItemCount()-1; + } + } + + if ( (mnItemDragPos != nOldItemDragPos) && + (nOldItemDragPos != nPos) && + (nOldItemDragPos != HEADERBAR_ITEM_NOTFOUND) ) + { + ImplInvertDrag( nPos, nOldItemDragPos ); + Invalidate(); + } + } + + if ( bNewOutDrag != mbOutDrag ) + Invalidate(); + + if ( mbItemDrag ) + { + if ( (mnItemDragPos != nOldItemDragPos) && + (mnItemDragPos != nPos) && + (mnItemDragPos != HEADERBAR_ITEM_NOTFOUND) ) + { + Invalidate(); + ImplInvertDrag( nPos, mnItemDragPos ); + } + } + + mbOutDrag = bNewOutDrag; + } + else + { + tools::Rectangle aItemRect = ImplGetItemRect( nPos ); + if ( mnDragPos < aItemRect.Left() ) + mnDragPos = aItemRect.Left(); + if ( (mnDragPos < 0) || (mnDragPos > mnDX-1) ) + HideTracking(); + else + { + tools::Rectangle aSizeRect( mnDragPos, 0, mnDragPos, mnDragSize+mnDY ); + ShowTracking( aSizeRect, ShowTrackFlags::Split ); + } + } +} + +void HeaderBar::ImplEndDrag( bool bCancel ) +{ + HideTracking(); + + if ( bCancel || mbOutDrag ) + { + if ( mbItemMode && (!mbOutDrag || mbItemDrag) ) + { + Invalidate(); + } + + mnCurItemId = 0; + } + else + { + sal_uInt16 nPos = GetItemPos( mnCurItemId ); + if ( mbItemMode ) + { + if ( mbItemDrag ) + { + SetPointer( PointerStyle::Arrow ); + if ( (mnItemDragPos != nPos) && + (mnItemDragPos != HEADERBAR_ITEM_NOTFOUND) ) + { + ImplInvertDrag( nPos, mnItemDragPos ); + MoveItem( mnCurItemId, mnItemDragPos ); + } + else + Invalidate(); + } + else + { + Select(); + ImplUpdate( nPos ); + } + } + else + { + tools::Long nDelta = mnDragPos - mnStartPos; + if ( nDelta ) + { + auto& pItem = mvItemList[ nPos ]; + pItem->mnSize += nDelta; + ImplUpdate( nPos, true ); + } + } + } + + mbDrag = false; + EndDrag(); + mnCurItemId = 0; + mnItemDragPos = HEADERBAR_ITEM_NOTFOUND; + mbOutDrag = false; + mbItemMode = false; + mbItemDrag = false; +} + +void HeaderBar::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( !rMEvt.IsLeft() ) + return; + + if ( rMEvt.GetClicks() == 2 ) + { + tools::Long nTemp; + sal_uInt16 nPos; + sal_uInt16 nHitTest = ImplDoHitTest( rMEvt.GetPosPixel(), nTemp, nPos ); + if ( nHitTest ) + { + auto& pItem = mvItemList[ nPos ]; + if ( nHitTest & HEAD_HITTEST_DIVIDER ) + mbItemMode = false; + else + mbItemMode = true; + mnCurItemId = pItem->mnId; + DoubleClick(); + mbItemMode = false; + mnCurItemId = 0; + } + } + else + ImplStartDrag( rMEvt.GetPosPixel(), false ); +} + +void HeaderBar::MouseMove( const MouseEvent& rMEvt ) +{ + tools::Long nTemp1; + sal_uInt16 nTemp2; + PointerStyle eStyle = PointerStyle::Arrow; + sal_uInt16 nHitTest = ImplDoHitTest( rMEvt.GetPosPixel(), nTemp1, nTemp2 ); + + if ( nHitTest & HEAD_HITTEST_DIVIDER ) + eStyle = PointerStyle::HSizeBar; + SetPointer( eStyle ); +} + +void HeaderBar::Tracking( const TrackingEvent& rTEvt ) +{ + Point aMousePos = rTEvt.GetMouseEvent().GetPosPixel(); + + if ( rTEvt.IsTrackingEnded() ) + ImplEndDrag( rTEvt.IsTrackingCanceled() ); + else + ImplDrag( aMousePos ); +} + +void HeaderBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + if (mnBorderOff1 || mnBorderOff2) + { + rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetDarkShadowColor()); + if (mnBorderOff1) + rRenderContext.DrawLine(Point(0, 0), Point(mnDX - 1, 0)); + if (mnBorderOff2) + rRenderContext.DrawLine(Point(0, mnDY - 1), Point(mnDX - 1, mnDY - 1)); + // #i40393# draw left and right border, if WB_BORDER was set in ImplInit() + if (mnBorderOff1 && mnBorderOff2) + { + rRenderContext.DrawLine(Point(0, 0), Point(0, mnDY - 1)); + rRenderContext.DrawLine(Point(mnDX - 1, 0), Point(mnDX - 1, mnDY - 1)); + } + } + + sal_uInt16 nCurItemPos; + if (mbDrag) + nCurItemPos = GetItemPos(mnCurItemId); + else + nCurItemPos = HEADERBAR_ITEM_NOTFOUND; + sal_uInt16 nItemCount = static_cast<sal_uInt16>(mvItemList.size()); + for (sal_uInt16 i = 0; i < nItemCount; i++) + ImplDrawItem(rRenderContext, i, (i == nCurItemPos), &rRect); +} + +void HeaderBar::Draw( OutputDevice* pDev, const Point& rPos, + SystemTextColorFlags nFlags ) +{ + Point aPos = pDev->LogicToPixel( rPos ); + Size aSize = GetSizePixel(); + tools::Rectangle aRect( aPos, aSize ); + vcl::Font aFont = GetDrawPixelFont( pDev ); + + pDev->Push(); + pDev->SetMapMode(); + pDev->SetFont( aFont ); + if ( nFlags & SystemTextColorFlags::Mono ) + pDev->SetTextColor( COL_BLACK ); + else + pDev->SetTextColor( GetTextColor() ); + pDev->SetTextFillColor(); + + // draw background + { + pDev->DrawWallpaper( aRect, GetBackground() ); + if ( mnBorderOff1 || mnBorderOff2 ) + { + pDev->SetLineColor( GetSettings().GetStyleSettings().GetDarkShadowColor() ); + if ( mnBorderOff1 ) + pDev->DrawLine( aRect.TopLeft(), Point( aRect.Right(), aRect.Top() ) ); + if ( mnBorderOff2 ) + pDev->DrawLine( Point( aRect.Left(), aRect.Bottom() ), Point( aRect.Right(), aRect.Bottom() ) ); + // #i40393# draw left and right border, if WB_BORDER was set in ImplInit() + if ( mnBorderOff1 && mnBorderOff2 ) + { + pDev->DrawLine( aRect.TopLeft(), Point( aRect.Left(), aRect.Bottom() ) ); + pDev->DrawLine( Point( aRect.Right(), aRect.Top() ), Point( aRect.Right(), aRect.Bottom() ) ); + } + } + } + + tools::Rectangle aItemRect( aRect ); + size_t nItemCount = mvItemList.size(); + for ( size_t i = 0; i < nItemCount; i++ ) + { + aItemRect.SetLeft( aRect.Left()+ImplGetItemPos( i ) ); + aItemRect.SetRight( aItemRect.Left() + mvItemList[ i ]->mnSize - 1 ); + // check for overflow on some systems + if ( aItemRect.Right() > 16000 ) + aItemRect.SetRight( 16000 ); + vcl::Region aRegion( aRect ); + pDev->SetClipRegion( aRegion ); + ImplDrawItem(*pDev, i, false, aItemRect, &aRect ); + pDev->SetClipRegion(); + } + + pDev->Pop(); +} + +void HeaderBar::Resize() +{ + Size aSize = GetOutputSizePixel(); + if ( IsVisible() && (mnDY != aSize.Height()) ) + Invalidate(); + mnDX = aSize.Width(); + mnDY = aSize.Height(); +} + +void HeaderBar::Command( const CommandEvent& rCEvt ) +{ + if ( rCEvt.IsMouseEvent() && (rCEvt.GetCommand() == CommandEventId::StartDrag) && !mbDrag ) + { + ImplStartDrag( rCEvt.GetMousePosPixel(), true ); + return; + } + + Window::Command( rCEvt ); +} + +void HeaderBar::RequestHelp( const HelpEvent& rHEvt ) +{ + sal_uInt16 nItemId = GetItemId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) ); + if ( nItemId ) + { + if ( rHEvt.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON) ) + { + tools::Rectangle aItemRect = GetItemRect( nItemId ); + Point aPt = OutputToScreenPixel( aItemRect.TopLeft() ); + aItemRect.SetLeft( aPt.X() ); + aItemRect.SetTop( aPt.Y() ); + aPt = OutputToScreenPixel( aItemRect.BottomRight() ); + aItemRect.SetRight( aPt.X() ); + aItemRect.SetBottom( aPt.Y() ); + + OUString aStr = GetHelpText( nItemId ); + if ( aStr.isEmpty() || !(rHEvt.GetMode() & HelpEventMode::BALLOON) ) + { + auto& pItem = mvItemList[ GetItemPos( nItemId ) ]; + // Quick-help is only displayed if the text is not fully visible. + // Otherwise we display Helptext only if the items do not contain text + if ( pItem->maOutText != pItem->maText ) + aStr = pItem->maText; + else if (!pItem->maText.isEmpty()) + aStr.clear(); + } + + if (!aStr.isEmpty()) + { + if ( rHEvt.GetMode() & HelpEventMode::BALLOON ) + Help::ShowBalloon( this, aItemRect.Center(), aItemRect, aStr ); + else + Help::ShowQuickHelp( this, aItemRect, aStr ); + return; + } + } + } + + Window::RequestHelp( rHEvt ); +} + +void HeaderBar::StateChanged( StateChangedType nType ) +{ + Window::StateChanged( nType ); + + if ( nType == StateChangedType::Enable ) + Invalidate(); + else if ( (nType == StateChangedType::Zoom) || + (nType == StateChangedType::ControlFont) ) + { + ImplInitSettings( true, false, false ); + Invalidate(); + } + else if ( nType == StateChangedType::ControlForeground ) + { + ImplInitSettings( false, true, false ); + Invalidate(); + } + else if ( nType == StateChangedType::ControlBackground ) + { + ImplInitSettings( false, false, true ); + Invalidate(); + } +} + +void HeaderBar::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) ) + { + ImplInitSettings( true, true, true ); + Invalidate(); + } +} + +void HeaderBar::EndDrag() +{ + maEndDragHdl.Call( this ); +} + +void HeaderBar::Select() +{ + maSelectHdl.Call( this ); +} + +void HeaderBar::DoubleClick() +{ +} + +void HeaderBar::InsertItem( sal_uInt16 nItemId, const OUString& rText, + tools::Long nSize, HeaderBarItemBits nBits, sal_uInt16 nPos ) +{ + DBG_ASSERT( nItemId, "HeaderBar::InsertItem(): ItemId == 0" ); + DBG_ASSERT( GetItemPos( nItemId ) == HEADERBAR_ITEM_NOTFOUND, + "HeaderBar::InsertItem(): ItemId already exists" ); + + // create item and insert in the list + std::unique_ptr<ImplHeadItem> pItem(new ImplHeadItem); + pItem->mnId = nItemId; + pItem->mnBits = nBits; + pItem->mnSize = nSize; + pItem->maText = rText; + if ( nPos < mvItemList.size() ) { + auto it = mvItemList.begin(); + it += nPos; + mvItemList.insert( it, std::move(pItem) ); + } else { + mvItemList.push_back( std::move(pItem) ); + } + + // update display + ImplUpdate( nPos, true ); +} + +void HeaderBar::RemoveItem( sal_uInt16 nItemId ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + if ( nPos != HEADERBAR_ITEM_NOTFOUND ) + { + if ( nPos < mvItemList.size() ) { + auto it = mvItemList.begin(); + it += nPos; + mvItemList.erase( it ); + } + } +} + +void HeaderBar::MoveItem( sal_uInt16 nItemId, sal_uInt16 nNewPos ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + if ( nPos == HEADERBAR_ITEM_NOTFOUND ) + return; + + if ( nPos == nNewPos ) + return; + + auto it = mvItemList.begin(); + it += nPos; + std::unique_ptr<ImplHeadItem> pItem = std::move(*it); + mvItemList.erase( it ); + if ( nNewPos < nPos ) + nPos = nNewPos; + it = mvItemList.begin(); + it += nNewPos; + mvItemList.insert( it, std::move(pItem) ); + ImplUpdate( nPos, true); +} + +void HeaderBar::Clear() +{ + // delete all items + mvItemList.clear(); + + ImplUpdate( 0, true ); +} + +void HeaderBar::SetOffset( tools::Long nNewOffset ) +{ + // tdf#129856 (see also #i40393#) invalidate old left and right border area if WB_BORDER was set in ImplInit() + if (mnBorderOff1 && mnBorderOff2) + { + Invalidate(tools::Rectangle(0, 0, 1, mnDY)); + Invalidate(tools::Rectangle(mnDX - 1, 0, mnDX, mnDY)); + } + + // move area + tools::Rectangle aRect( 0, mnBorderOff1, mnDX-1, mnDY-mnBorderOff1-mnBorderOff2 ); + tools::Long nDelta = mnOffset-nNewOffset; + mnOffset = nNewOffset; + Scroll( nDelta, 0, aRect ); +} + +sal_uInt16 HeaderBar::GetItemCount() const +{ + return static_cast<sal_uInt16>(mvItemList.size()); +} + +sal_uInt16 HeaderBar::GetItemPos( sal_uInt16 nItemId ) const +{ + for ( size_t i = 0, n = mvItemList.size(); i < n; ++i ) { + auto& pItem = mvItemList[ i ]; + if ( pItem->mnId == nItemId ) + return static_cast<sal_uInt16>(i); + } + return HEADERBAR_ITEM_NOTFOUND; +} + +sal_uInt16 HeaderBar::GetItemId( sal_uInt16 nPos ) const +{ + ImplHeadItem* pItem = (nPos < mvItemList.size() ) ? mvItemList[ nPos ].get() : nullptr; + if ( pItem ) + return pItem->mnId; + else + return 0; +} + +sal_uInt16 HeaderBar::GetItemId( const Point& rPos ) const +{ + for ( size_t i = 0, n = mvItemList.size(); i < n; ++i ) { + if ( ImplGetItemRect( i ).Contains( rPos ) ) { + return GetItemId( i ); + } + } + return 0; +} + +tools::Rectangle HeaderBar::GetItemRect( sal_uInt16 nItemId ) const +{ + tools::Rectangle aRect; + sal_uInt16 nPos = GetItemPos( nItemId ); + if ( nPos != HEADERBAR_ITEM_NOTFOUND ) + aRect = ImplGetItemRect( nPos ); + return aRect; +} + +void HeaderBar::SetItemSize( sal_uInt16 nItemId, tools::Long nNewSize ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + if ( nPos != HEADERBAR_ITEM_NOTFOUND ) + { + auto& pItem = mvItemList[ nPos ]; + if ( pItem->mnSize != nNewSize ) + { + pItem->mnSize = nNewSize; + ImplUpdate( nPos, true ); + } + } +} + +tools::Long HeaderBar::GetItemSize( sal_uInt16 nItemId ) const +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + if ( nPos != HEADERBAR_ITEM_NOTFOUND ) + return mvItemList[ nPos ]->mnSize; + else + return 0; +} + +void HeaderBar::SetItemBits( sal_uInt16 nItemId, HeaderBarItemBits nNewBits ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + if ( nPos != HEADERBAR_ITEM_NOTFOUND ) + { + auto& pItem = mvItemList[ nPos ]; + if ( pItem->mnBits != nNewBits ) + { + pItem->mnBits = nNewBits; + ImplUpdate( nPos ); + } + } +} + +HeaderBarItemBits HeaderBar::GetItemBits( sal_uInt16 nItemId ) const +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + if ( nPos != HEADERBAR_ITEM_NOTFOUND ) + return mvItemList[ nPos ]->mnBits; + else + return HeaderBarItemBits::NONE; +} + +void HeaderBar::SetItemText( sal_uInt16 nItemId, const OUString& rText ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + if ( nPos != HEADERBAR_ITEM_NOTFOUND ) + { + mvItemList[ nPos ]->maText = rText; + ImplUpdate( nPos ); + } +} + +OUString HeaderBar::GetItemText( sal_uInt16 nItemId ) const +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + if ( nPos != HEADERBAR_ITEM_NOTFOUND ) + return mvItemList[ nPos ]->maText; + return OUString(); +} + +OUString HeaderBar::GetHelpText( sal_uInt16 nItemId ) const +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + if ( nPos != HEADERBAR_ITEM_NOTFOUND ) + { + auto& pItem = mvItemList[ nPos ]; + if ( pItem->maHelpText.isEmpty() && !pItem->maHelpId.isEmpty() ) + { + Help* pHelp = Application::GetHelp(); + if ( pHelp ) + pItem->maHelpText = pHelp->GetHelpText( OStringToOUString( pItem->maHelpId, RTL_TEXTENCODING_UTF8 ), this ); + } + + return pItem->maHelpText; + } + + return OUString(); +} + +Size HeaderBar::CalcWindowSizePixel() const +{ + tools::Long nMaxImageSize = 0; + Size aSize( 0, GetTextHeight() ); + + for (auto& pItem : mvItemList) + { + // take image size into account + tools::Long nImageHeight = pItem->maImage.GetSizePixel().Height(); + if ( !(pItem->mnBits & (HeaderBarItemBits::LEFTIMAGE | HeaderBarItemBits::RIGHTIMAGE)) && !pItem->maText.isEmpty() ) + nImageHeight += aSize.Height(); + if ( nImageHeight > nMaxImageSize ) + nMaxImageSize = nImageHeight; + + // add width + aSize.AdjustWidth(pItem->mnSize ); + } + + if ( nMaxImageSize > aSize.Height() ) + aSize.setHeight( nMaxImageSize ); + + // add border + if ( mbButtonStyle ) + aSize.AdjustHeight(4 ); + else + aSize.AdjustHeight(2 ); + aSize.AdjustHeight(mnBorderOff1+mnBorderOff2 ); + + return aSize; +} + +css::uno::Reference< css::accessibility::XAccessible > HeaderBar::CreateAccessible() +{ + if ( !mxAccessible.is() ) + { + maCreateAccessibleHdl.Call( this ); + + if ( !mxAccessible.is() ) + mxAccessible = Window::CreateAccessible(); + } + + return mxAccessible; +} + +void HeaderBar::SetAccessible( const css::uno::Reference< css::accessibility::XAccessible >& _xAccessible ) +{ + mxAccessible = _xAccessible; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/iconview.cxx b/vcl/source/treelist/iconview.cxx new file mode 100644 index 000000000..7814810cb --- /dev/null +++ b/vcl/source/treelist/iconview.cxx @@ -0,0 +1,315 @@ +/* -*- 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 <vcl/toolkit/treelistentry.hxx> +#include <vcl/toolkit/viewdataentry.hxx> +#include <iconview.hxx> +#include "iconviewimpl.hxx" +#include <vcl/accessiblefactory.hxx> +#include <vcl/uitest/uiobject.hxx> +#include <tools/json_writer.hxx> +#include <vcl/toolkit/svlbitm.hxx> +#include <tools/stream.hxx> +#include <vcl/cvtgrf.hxx> +#include <comphelper/base64.hxx> + +namespace +{ +const int separatorHeight = 10; +const int nSpacing = 5; // 5 pixels from top, from bottom, between icon and label +} + +IconView::IconView(vcl::Window* pParent, WinBits nBits) + : SvTreeListBox(pParent, nBits) +{ + nColumns = 1; + mbCenterAndClipText = true; + SetEntryWidth(100); + + pImpl.reset(new IconViewImpl(this, GetModel(), GetStyle())); +} + +Size IconView::GetEntrySize(const SvTreeListEntry& entry) const +{ + if (entry.GetFlags() & SvTLEntryFlags::IS_SEPARATOR) + return { GetEntryWidth() * GetColumnsCount(), separatorHeight }; + return { GetEntryWidth(), GetEntryHeight() }; +} + +void IconView::CalcEntryHeight(SvTreeListEntry const* pEntry) +{ + int nHeight = nSpacing * 2; + SvViewDataEntry* pViewData = GetViewDataEntry(pEntry); + const size_t nCount = pEntry->ItemCount(); + bool bHasIcon = false; + for (size_t nCur = 0; nCur < nCount; ++nCur) + { + nHeight += SvLBoxItem::GetHeight(pViewData, nCur); + + if (!bHasIcon && pEntry->GetItem(nCur).GetType() == SvLBoxItemType::ContextBmp) + bHasIcon = true; + } + + if (bHasIcon && nCount > 1) + nHeight += nSpacing; // between icon and label + + if (nHeight > nEntryHeight) + { + nEntryHeight = nHeight; + Control::SetFont(GetFont()); + pImpl->SetEntryHeight(); + } +} + +void IconView::Resize() +{ + Size aBoxSize = Control::GetOutputSizePixel(); + + if (!aBoxSize.Width()) + return; + + nColumns = nEntryWidth ? aBoxSize.Width() / nEntryWidth : 1; + + SvTreeListBox::Resize(); +} + +tools::Rectangle IconView::GetFocusRect(const SvTreeListEntry* pEntry, tools::Long) +{ + return { GetEntryPosition(pEntry), GetEntrySize(*pEntry) }; +} + +void IconView::PaintEntry(SvTreeListEntry& rEntry, tools::Long nX, tools::Long nY, + vcl::RenderContext& rRenderContext) +{ + pImpl->UpdateContextBmpWidthMax(&rEntry); + + const Size entrySize = GetEntrySize(rEntry); + short nTempEntryHeight = entrySize.Height(); + short nTempEntryWidth = entrySize.Width(); + + Point aEntryPos(nX, nY); + + const Color aBackupTextColor(rRenderContext.GetTextColor()); + const vcl::Font aBackupFont(rRenderContext.GetFont()); + const Color aBackupColor = rRenderContext.GetFillColor(); + + const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings(); + + const Size aOutputSize = GetOutputSizePixel(); + if (aOutputSize.getHeight() < nTempEntryHeight) + nTempEntryHeight = aOutputSize.getHeight(); + + const SvViewDataEntry* pViewDataEntry = GetViewDataEntry(&rEntry); + + bool bCurFontIsSel = false; + if (pViewDataEntry->IsHighlighted()) + { + vcl::Font aHighlightFont(rRenderContext.GetFont()); + const Color aHighlightTextColor(rSettings.GetHighlightTextColor()); + aHighlightFont.SetColor(aHighlightTextColor); + + // set font color to highlight + rRenderContext.SetTextColor(aHighlightTextColor); + rRenderContext.SetFont(aHighlightFont); + bCurFontIsSel = true; + } + + bool bFillColorSet = false; + // draw background + if (!(nTreeFlags & SvTreeFlags::USESEL)) + { + // set background pattern/color + Wallpaper aWallpaper = rRenderContext.GetBackground(); + + if (pViewDataEntry->IsHighlighted()) + { + Color aNewWallColor = rSettings.GetHighlightColor(); + // if the face color is bright then the deactivate color is also bright + // -> so you can't see any deactivate selection + const WinBits nWindowStyle = GetStyle(); + const bool bHideSelection = (nWindowStyle & WB_HIDESELECTION) != 0 && !HasFocus(); + if (bHideSelection && !rSettings.GetFaceColor().IsBright() + && aWallpaper.GetColor().IsBright() != rSettings.GetDeactiveColor().IsBright()) + { + aNewWallColor = rSettings.GetDeactiveColor(); + } + aWallpaper.SetColor(aNewWallColor); + } + else // no selection + { + aWallpaper.SetColor(rEntry.GetBackColor()); + } + + Color aBackgroundColor = aWallpaper.GetColor(); + if (aBackgroundColor != COL_TRANSPARENT) + { + rRenderContext.SetFillColor(aBackgroundColor); + bFillColorSet = true; + // this case may occur for smaller horizontal resizes + if (nTempEntryWidth > 1) + rRenderContext.DrawRect({ aEntryPos, Size(nTempEntryWidth, nTempEntryHeight) }); + } + } + + const size_t nItemCount = rEntry.ItemCount(); + size_t nIconItem = nItemCount; + + int nLabelHeight = 0; + std::vector<size_t> aTextItems; + + for (size_t nCurItem = 0; nCurItem < nItemCount; ++nCurItem) + { + SvLBoxItem& rItem = rEntry.GetItem(nCurItem); + SvLBoxItemType nItemType = rItem.GetType(); + + if (nItemType == SvLBoxItemType::ContextBmp) + { + nIconItem = nCurItem; + continue; + } + + aTextItems.push_back(nCurItem); + auto nItemHeight = SvLBoxItem::GetHeight(pViewDataEntry, nCurItem); + nLabelHeight += nItemHeight; + } + + int nLabelYPos = nY + nTempEntryHeight - nLabelHeight - nSpacing; // padding from bottom + for (auto nCurItem : aTextItems) + { + aEntryPos.setY(nLabelYPos); + + auto nItemHeight = SvLBoxItem::GetHeight(pViewDataEntry, nCurItem); + nLabelYPos += nItemHeight; + + rEntry.GetItem(nCurItem).Paint(aEntryPos, *this, rRenderContext, pViewDataEntry, rEntry); + } + + if (bFillColorSet) + rRenderContext.SetFillColor(aBackupColor); + + // draw icon + if (nIconItem < nItemCount) + { + SvLBoxItem& rItem = rEntry.GetItem(nIconItem); + auto nItemWidth = rItem.GetWidth(this, pViewDataEntry, nIconItem); + auto nItemHeight = SvLBoxItem::GetHeight(pViewDataEntry, nIconItem); + + aEntryPos.setY(nY); + + // center horizontally + aEntryPos.AdjustX((nTempEntryWidth - nItemWidth) / 2); + // center vertically + int nImageAreaHeight = nTempEntryHeight - nSpacing * 2; // spacings from top, from bottom + if (nLabelHeight > 0) + { + nImageAreaHeight -= nLabelHeight + nSpacing; // spacing between icon and label + } + aEntryPos.AdjustY((nImageAreaHeight - nItemHeight) / 2 + nSpacing); + + rItem.Paint(aEntryPos, *this, rRenderContext, pViewDataEntry, rEntry); + } + + if (bCurFontIsSel) + { + rRenderContext.SetTextColor(aBackupTextColor); + rRenderContext.SetFont(aBackupFont); + } +} + +css::uno::Reference<css::accessibility::XAccessible> IconView::CreateAccessible() +{ + if (vcl::Window* pParent = GetAccessibleParentWindow()) + { + if (auto xAccParent = pParent->GetAccessible()) + { + // need to be done here to get the vclxwindow later on in the accessible + css::uno::Reference<css::awt::XWindowPeer> xHoldAlive(GetComponentInterface()); + return pImpl->m_aFactoryAccess.getFactory().createAccessibleIconView(*this, xAccParent); + } + } + return {}; +} + +OUString IconView::GetEntryAccessibleDescription(SvTreeListEntry* pEntry) const +{ + assert(pEntry); + + if (maEntryAccessibleDescriptionHdl.IsSet()) + return maEntryAccessibleDescriptionHdl.Call(pEntry); + + return SvTreeListBox::GetEntryAccessibleDescription(pEntry); +} + +FactoryFunction IconView::GetUITestFactory() const { return IconViewUIObject::create; } + +static OUString extractPngString(const SvLBoxContextBmp* pBmpItem) +{ + BitmapEx aImage = pBmpItem->GetBitmap1().GetBitmapEx(); + SvMemoryStream aOStm(65535, 65535); + if (GraphicConverter::Export(aOStm, aImage, ConvertDataFormat::PNG) == ERRCODE_NONE) + { + css::uno::Sequence<sal_Int8> aSeq(static_cast<sal_Int8 const*>(aOStm.GetData()), + aOStm.Tell()); + OUStringBuffer aBuffer("data:image/png;base64,"); + ::comphelper::Base64::encode(aBuffer, aSeq); + return aBuffer.makeStringAndClear(); + } + + return ""; +} + +static void lcl_DumpEntryAndSiblings(tools::JsonWriter& rJsonWriter, SvTreeListEntry* pEntry, + const SvTreeListBox* pTabListBox) +{ + while (pEntry) + { + auto aNode = rJsonWriter.startStruct(); + + // simple listbox value + const SvLBoxItem* pIt = pEntry->GetFirstItem(SvLBoxItemType::String); + if (pIt) + rJsonWriter.put("text", static_cast<const SvLBoxString*>(pIt)->GetText()); + + pIt = pEntry->GetFirstItem(SvLBoxItemType::ContextBmp); + if (pIt) + { + const SvLBoxContextBmp* pBmpItem = static_cast<const SvLBoxContextBmp*>(pIt); + if (pBmpItem) + rJsonWriter.put("image", extractPngString(pBmpItem)); + } + + if (pTabListBox->IsSelected(pEntry)) + rJsonWriter.put("selected", "true"); + + rJsonWriter.put("row", + OString::number(pTabListBox->GetModel()->GetAbsPos(pEntry)).getStr()); + + pEntry = pEntry->NextSibling(); + } +} + +void IconView::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + SvTreeListBox::DumpAsPropertyTree(rJsonWriter); + rJsonWriter.put("type", "iconview"); + auto aNode = rJsonWriter.startArray("entries"); + lcl_DumpEntryAndSiblings(rJsonWriter, First(), this); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/iconviewimpl.cxx b/vcl/source/treelist/iconviewimpl.cxx new file mode 100644 index 000000000..6839ff1db --- /dev/null +++ b/vcl/source/treelist/iconviewimpl.cxx @@ -0,0 +1,741 @@ +/* -*- 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 <vcl/svapp.hxx> +#include <vcl/toolkit/treelistentry.hxx> +#include <tools/debug.hxx> +#include <iconview.hxx> +#include "iconviewimpl.hxx" + +IconViewImpl::IconViewImpl( SvTreeListBox* pTreeListBox, SvTreeList* pTreeList, WinBits nWinStyle ) +: SvImpLBox( pTreeListBox, pTreeList, nWinStyle ) +{ +} + +static bool IsSeparator(const SvTreeListEntry* entry) +{ + return entry && entry->GetFlags() & SvTLEntryFlags::IS_SEPARATOR; +} + +Size IconViewImpl::GetEntrySize(const SvTreeListEntry& entry) const +{ + return static_cast<const IconView*>(m_pView.get())->GetEntrySize(entry); +} + +void IconViewImpl::IterateVisibleEntryAreas(const IterateEntriesFunc& f, bool fromStartEntry) const +{ + tools::Long x = 0, y = 0; + short column = 0; + const tools::Long rowWidth = m_pView->GetEntryWidth() * m_pView->GetColumnsCount(); + tools::Long nPrevHeight = 0; + for (auto entry = fromStartEntry ? m_pStartEntry : m_pView->FirstVisible(); entry; + entry = m_pView->NextVisible(entry)) + { + const Size s = GetEntrySize(*entry); + if (x >= rowWidth || IsSeparator(entry)) + { + column = 0; + x = 0; + y += nPrevHeight; + } + EntryAreaInfo info{ entry, column, tools::Rectangle{ Point{ x, y }, s } }; + const auto result = f(info); + if (result == CallbackResult::Stop) + return; + ++column; + x += s.Width(); + nPrevHeight = s.Height(); + } +} + +tools::Long IconViewImpl::GetEntryRow(const SvTreeListEntry* entry) const +{ + tools::Long nEntryRow = -1; + auto GetRow = [entry, &nEntryRow, row = -1](const EntryAreaInfo& info) mutable + { + if (info.column == 0 && !IsSeparator(info.entry)) + ++row; + if (info.entry != entry) + return CallbackResult::Continue; + nEntryRow = row; + return CallbackResult::Stop; + }; + IterateVisibleEntryAreas(GetRow); + return nEntryRow; +} + +void IconViewImpl::SetStartEntry(SvTreeListEntry* entry) +{ + const tools::Long max = m_aVerSBar->GetRangeMax() - m_aVerSBar->GetVisibleSize(); + tools::Long row = -1; + auto GetEntryAndRow = [&entry, &row, max, found = entry](const EntryAreaInfo& info) mutable + { + if (info.column == 0 && !IsSeparator(info.entry)) + { + found = info.entry; + ++row; + } + if (row >= max || info.entry == entry) + { + entry = found; + return CallbackResult::Stop; + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(GetEntryAndRow); + + m_pStartEntry = entry; + m_aVerSBar->SetThumbPos(row); + m_pView->Invalidate(GetVisibleArea()); +} + +void IconViewImpl::ScrollTo(SvTreeListEntry* entry) +{ + if (!m_aVerSBar->IsVisible()) + return; + const tools::Long entryRow = GetEntryRow(entry); + const tools::Long oldStartRow = m_aVerSBar->GetThumbPos(); + if (entryRow < oldStartRow) + IconViewImpl::SetStartEntry(entry); + const tools::Long visibleRows = m_aVerSBar->GetVisibleSize(); + const tools::Long posRelativeToBottom = entryRow - (oldStartRow + visibleRows) + 1; + if (posRelativeToBottom > 0) + IconViewImpl::SetStartEntry(GoToNextRow(m_pStartEntry, posRelativeToBottom)); +} + +SvTreeListEntry* IconViewImpl::GoToPrevRow(SvTreeListEntry* pEntry, int nRows) const +{ + SvTreeListEntry* pPrev = pEntry; + auto FindPrev = [this, pEntry, nRows, &pPrev, + prevs = std::vector<SvTreeListEntry*>()](const EntryAreaInfo& info) mutable + { + if (info.column == 0 && !IsSeparator(info.entry)) + prevs.push_back(info.entry); + if (pEntry == info.entry) + { + if (prevs.size() > 1) + { + int i = std::max(0, static_cast<int>(prevs.size()) - nRows - 1); + pPrev = prevs[i]; + for (short column = info.column; column; --column) + { + SvTreeListEntry* pNext = m_pView->NextVisible(pPrev); + if (!pNext || IsSeparator(pNext)) + break; + pPrev = pNext; + } + } + return CallbackResult::Stop; + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindPrev); + + return pPrev; +} + +SvTreeListEntry* IconViewImpl::GoToNextRow(SvTreeListEntry* pEntry, int nRows) const +{ + SvTreeListEntry* pNext = pEntry; + auto FindNext + = [pEntry, nRows, &pNext, column = -1](const EntryAreaInfo& info) mutable + { + if (info.column <= column && !IsSeparator(info.entry)) + { + if (info.column == 0 && --nRows < 0) + return CallbackResult::Stop; + pNext = info.entry; + if (info.column == column && nRows == 0) + return CallbackResult::Stop; + } + else if (pEntry == info.entry) + { + column = info.column; + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindNext); + + return pNext; +} + +void IconViewImpl::CursorUp() +{ + if (!m_pStartEntry) + return; + + SvTreeListEntry* pPrevFirstToDraw = GoToPrevRow(m_pStartEntry, 1); + + m_nFlags &= ~LBoxFlags::Filling; + ShowCursor( false ); + SetStartEntry(pPrevFirstToDraw); + ShowCursor( true ); + m_pView->NotifyScrolled(); +} + +void IconViewImpl::CursorDown() +{ + if (!m_pStartEntry) + return; + + SvTreeListEntry* pNextFirstToDraw = GoToNextRow(m_pStartEntry, 1); + + m_nFlags &= ~LBoxFlags::Filling; + ShowCursor( false ); + SetStartEntry(pNextFirstToDraw); + ShowCursor( true ); + m_pView->NotifyScrolled(); +} + +void IconViewImpl::PageDown( sal_uInt16 nDelta ) +{ + if( !nDelta ) + return; + + if (!m_pStartEntry) + return; + + SvTreeListEntry* pNext = GoToNextRow(m_pStartEntry, nDelta); + + ShowCursor( false ); + + m_nFlags &= ~LBoxFlags::Filling; + SetStartEntry(pNext); + + ShowCursor( true ); +} + +void IconViewImpl::PageUp( sal_uInt16 nDelta ) +{ + if( !nDelta ) + return; + + if (!m_pStartEntry) + return; + + SvTreeListEntry* pPrev = GoToPrevRow(m_pStartEntry, nDelta); + + m_nFlags &= ~LBoxFlags::Filling; + ShowCursor( false ); + + SetStartEntry(pPrev); + + ShowCursor( true ); +} + +void IconViewImpl::KeyDown( bool bPageDown ) +{ + if( !m_aVerSBar->IsVisible() ) + return; + + tools::Long nDelta; + if( bPageDown ) + nDelta = m_aVerSBar->GetPageSize(); + else + nDelta = 1; + + if( nDelta <= 0 ) + return; + + m_nFlags &= ~LBoxFlags::Filling; + + if( bPageDown ) + PageDown( static_cast<short>(nDelta) ); + else + CursorDown(); +} + +void IconViewImpl::KeyUp( bool bPageUp ) +{ + if( !m_aVerSBar->IsVisible() ) + return; + + tools::Long nDelta; + if( bPageUp ) + nDelta = m_aVerSBar->GetPageSize(); + else + nDelta = 1; + + m_nFlags &= ~LBoxFlags::Filling; + + if( bPageUp ) + PageUp( static_cast<short>(nDelta) ); + else + CursorUp(); +} + +tools::Long IconViewImpl::GetEntryLine(const SvTreeListEntry* pEntry) const +{ + if(!m_pStartEntry ) + return -1; // invisible position + + return IconViewImpl::GetEntryPosition(pEntry).Y(); +} + +Point IconViewImpl::GetEntryPosition(const SvTreeListEntry* pEntry) const +{ + Point result{ -m_pView->GetEntryWidth(), -m_pView->GetEntryHeight() }; // invisible + auto FindEntryPos = [pEntry, &result](const EntryAreaInfo& info) + { + if (pEntry == info.entry) + { + result = info.area.TopLeft(); + return CallbackResult::Stop; + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindEntryPos, true); + + return result; +} + +// Returns the last entry (in respective row) if position is just past the last entry +SvTreeListEntry* IconViewImpl::GetClickedEntry( const Point& rPoint ) const +{ + DBG_ASSERT( m_pView->GetModel(), "IconViewImpl::GetClickedEntry: how can this ever happen?" ); + if ( !m_pView->GetModel() ) + return nullptr; + if( m_pView->GetEntryCount() == 0 || !m_pStartEntry || !m_pView->GetEntryHeight() || !m_pView->GetEntryWidth()) + return nullptr; + + SvTreeListEntry* pEntry = nullptr; + auto FindEntryByPos = [&pEntry, &rPoint](const EntryAreaInfo& info) + { + if (info.area.Contains(rPoint)) + { + pEntry = info.entry; + return CallbackResult::Stop; + } + else if (info.area.Top() > rPoint.Y()) + { + return CallbackResult::Stop; // we are already below the clicked row + } + else if (info.area.Bottom() > rPoint.Y()) + { + pEntry = info.entry; // Same row; store the entry in case the click is past all entries + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindEntryByPos, true); + + return pEntry; +} + +bool IconViewImpl::IsEntryInView( SvTreeListEntry* pEntry ) const +{ + // parent collapsed + if( !m_pView->IsEntryVisible(pEntry) ) + return false; + + tools::Long nY = GetEntryLine( pEntry ); + if( nY < 0 ) + return false; + + tools::Long height = GetEntrySize(*pEntry).Height(); + if (nY + height > m_aOutputSize.Height()) + return false; + + return true; +} + +void IconViewImpl::AdjustScrollBars( Size& rSize ) +{ + tools::Long nEntryHeight = m_pView->GetEntryHeight(); + if( !nEntryHeight ) + return; + + sal_uInt16 nResult = 0; + + Size aOSize( m_pView->Control::GetOutputSizePixel() ); + + const WinBits nWindowStyle = m_pView->GetStyle(); + bool bVerSBar = ( nWindowStyle & WB_VSCROLL ) != 0; + + // number of entries visible within the view + const tools::Long nVisibleRows = aOSize.Height() / nEntryHeight; + m_nVisibleCount = nVisibleRows * m_pView->GetColumnsCount(); + + tools::Long nTotalRows = 0; + tools::Long totalHeight = 0; + auto CountRowsAndHeight = [&nTotalRows, &totalHeight](const EntryAreaInfo& info) + { + totalHeight = std::max(totalHeight, info.area.Bottom()); + if (info.column == 0 && !IsSeparator(info.entry)) + ++nTotalRows; + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(CountRowsAndHeight); + + // do we need a vertical scrollbar? + if( bVerSBar || totalHeight > aOSize.Height()) + { + nResult = 1; + } + + // do we need a Horizontal scrollbar? + bool bHorSBar = (nWindowStyle & WB_HSCROLL) != 0; + if (bHorSBar || m_pView->GetEntryWidth() > aOSize.Width()) + { + nResult += 2; + m_aHorSBar->SetRange(Range(0, m_pView->GetEntryWidth())); + m_aHorSBar->SetVisibleSize(aOSize.Width()); + } + + PositionScrollBars( aOSize, nResult ); + + // adapt Range, VisibleRange etc. + + // refresh output size, in case we have to scroll + tools::Rectangle aRect; + aRect.SetSize( aOSize ); + m_aSelEng.SetVisibleArea( aRect ); + + // vertical scrollbar + if( !m_bInVScrollHdl ) + { + m_aVerSBar->SetRange(Range(0, nTotalRows)); + m_aVerSBar->SetPageSize(nVisibleRows); + m_aVerSBar->SetVisibleSize(nVisibleRows); + } + else + { + m_nFlags |= LBoxFlags::EndScrollSetVisSize; + } + + if( nResult & 0x0001 ) + m_aVerSBar->Show(); + else + m_aVerSBar->Hide(); + + if (nResult & 0x0002) + m_aHorSBar->Show(); + else + m_aHorSBar->Hide(); + + rSize = aOSize; +} + +// returns 0 if position is just past the last entry +SvTreeListEntry* IconViewImpl::GetEntry( const Point& rPoint ) const +{ + if( (m_pView->GetEntryCount() == 0) || !m_pStartEntry || + (rPoint.Y() > m_aOutputSize.Height()) + || !m_pView->GetEntryHeight() + || !m_pView->GetEntryWidth()) + return nullptr; + + SvTreeListEntry* pEntry = nullptr; + auto FindEntryByPos = [&pEntry, &rPoint](const EntryAreaInfo& info) + { + if (info.area.Contains(rPoint)) + { + pEntry = info.entry; + return CallbackResult::Stop; + } + else if (info.area.Top() > rPoint.Y()) + { + return CallbackResult::Stop; // we are already below the clicked row + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindEntryByPos, true); + + return pEntry; +} + +void IconViewImpl::SyncVerThumb() +{ + m_aVerSBar->SetThumbPos(GetEntryRow(m_pStartEntry)); +} + +void IconViewImpl::UpdateAll( bool bInvalidateCompleteView ) +{ + FindMostRight(); + SyncVerThumb(); + FillView(); + ShowVerSBar(); + if( m_bSimpleTravel && m_pCursor && m_pView->HasFocus() ) + m_pView->Select( m_pCursor ); + ShowCursor( true ); + if( bInvalidateCompleteView ) + m_pView->Invalidate(); + else + m_pView->Invalidate( GetVisibleArea() ); +} + +void IconViewImpl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + if (!m_pView->GetVisibleCount()) + return; + + m_nFlags |= LBoxFlags::InPaint; + + if (m_nFlags & LBoxFlags::Filling) + { + SvTreeListEntry* pFirst = m_pView->First(); + if (pFirst != m_pStartEntry) + { + ShowCursor(false); + m_pStartEntry = m_pView->First(); + m_aVerSBar->SetThumbPos( 0 ); + StopUserEvent(); + ShowCursor(true); + m_nCurUserEvent = Application::PostUserEvent(LINK(this, SvImpLBox, MyUserEvent), + reinterpret_cast<void*>(1)); + return; + } + } + + if (!m_pStartEntry) + { + m_pStartEntry = m_pView->First(); + } + + if (!m_pCursor && !mbNoAutoCurEntry) + { + // do not select if multiselection or explicit set + bool bNotSelect = (m_aSelEng.GetSelectionMode() == SelectionMode::Multiple ) || ((m_nStyle & WB_NOINITIALSELECTION) == WB_NOINITIALSELECTION); + SetCursor(m_pStartEntry, bNotSelect); + } + + auto PaintEntry = [iconView = static_cast<IconView*>(m_pView.get()), &rRect, + &rRenderContext](const EntryAreaInfo& info) + { + if (!info.area.GetIntersection(rRect).IsEmpty()) + { + iconView->PaintEntry(*info.entry, info.area.Left(), info.area.Top(), rRenderContext); + } + else if (info.area.Top() > rRect.Bottom()) + { + return CallbackResult::Stop; // we are already below the last visible row + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(PaintEntry, true); + + m_nFlags &= ~LBoxFlags::DeselectAll; + rRenderContext.SetClipRegion(); + m_nFlags &= ~LBoxFlags::InPaint; +} + +void IconViewImpl::InvalidateEntry( tools::Long nId ) const +{ + if( m_nFlags & LBoxFlags::InPaint ) + return; + if (nId < 0) + return; + + // nId is a Y coordinate of the top of the element, coming from GetEntryLine + tools::Rectangle aRect( GetVisibleArea() ); + if (nId > aRect.Bottom()) + return; + aRect.SetTop(nId); // Invalidate everything below + m_pView->Invalidate( aRect ); +} + +bool IconViewImpl::KeyInput( const KeyEvent& rKEvt ) +{ + const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); + + if( rKeyCode.IsMod2() ) + return false; // don't evaluate Alt key + + m_nFlags &= ~LBoxFlags::Filling; + + if( !m_pCursor ) + m_pCursor = m_pStartEntry; + if( !m_pCursor ) + return false; + + sal_uInt16 aCode = rKeyCode.GetCode(); + + bool bShift = rKeyCode.IsShift(); + bool bMod1 = rKeyCode.IsMod1(); + + SvTreeListEntry* pNewCursor; + + bool bHandled = true; + + switch( aCode ) + { + case KEY_LEFT: + if( !IsEntryInView( m_pCursor ) ) + MakeVisible( m_pCursor ); + + pNewCursor = m_pCursor; + do + { + pNewCursor = m_pView->PrevVisible(pNewCursor); + } while( pNewCursor && !IsSelectable(pNewCursor) ); + + // if there is no next entry, take the current one + // this ensures that in case of _one_ entry in the list, this entry is selected when pressing + // the cursor key + if (!pNewCursor) + pNewCursor = m_pCursor; + + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on + if( !IsEntryInView( pNewCursor ) ) + KeyUp( false ); + break; + + case KEY_RIGHT: + if( !IsEntryInView( m_pCursor ) ) + MakeVisible( m_pCursor ); + + pNewCursor = m_pCursor; + do + { + pNewCursor = m_pView->NextVisible(pNewCursor); + } while( pNewCursor && !IsSelectable(pNewCursor) ); + + // if there is no next entry, take the current one + // this ensures that in case of _one_ entry in the list, this entry is selected when pressing + // the cursor key + if ( !pNewCursor && m_pCursor ) + pNewCursor = m_pCursor; + + if( pNewCursor ) + { + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + if( IsEntryInView( pNewCursor ) ) + SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on + else + { + if( m_pCursor ) + m_pView->Select( m_pCursor, false ); + KeyDown( false ); + SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on + } + } + else + KeyDown( false ); // because scrollbar range might still + // allow scrolling + break; + + case KEY_UP: + { + pNewCursor = GoToPrevRow(m_pCursor, 1); + + if( pNewCursor ) + { + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on + ScrollTo(pNewCursor); + } + break; + } + + case KEY_DOWN: + { + pNewCursor = GoToNextRow(m_pCursor, 1); + + if( pNewCursor ) + { + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + ScrollTo(pNewCursor); + SetCursor(pNewCursor, bMod1); // no selection, when Ctrl is on + } + else + KeyDown( false ); // because scrollbar range might still + // allow scrolling + break; + } + + case KEY_PAGEUP: + if (!bMod1) + { + const sal_uInt16 nDelta = m_aVerSBar->GetPageSize(); + pNewCursor = GoToPrevRow(m_pCursor, nDelta); + + if (pNewCursor) + { + m_aSelEng.CursorPosChanging(bShift, bMod1); + ScrollTo(pNewCursor); + SetCursor(pNewCursor); + } + } + else + bHandled = false; + break; + + case KEY_PAGEDOWN: + if (!bMod1) + { + const sal_uInt16 nDelta = m_aVerSBar->GetPageSize(); + pNewCursor = GoToNextRow(m_pCursor, nDelta); + + if (pNewCursor) + { + m_aSelEng.CursorPosChanging(bShift, bMod1); + ScrollTo(pNewCursor); + SetCursor(pNewCursor); + } + else + KeyDown(false); + } + else + bHandled = false; + break; + + case KEY_RETURN: + case KEY_SPACE: + { + bHandled = !m_pView->aDoubleClickHdl.Call(m_pView); + break; + } + + case KEY_END: + { + pNewCursor = m_pView->GetModel()->Last(); + + while( pNewCursor && !IsSelectable(pNewCursor) ) + { + pNewCursor = m_pView->PrevVisible(pNewCursor); + } + + SetStartEntry(pNewCursor); + + if( pNewCursor && pNewCursor != m_pCursor) + { +// SelAllDestrAnch( false ); + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + SetCursor( pNewCursor ); + } + + bHandled = true; + + break; + } + + default: + { + bHandled = false; + break; + } + } + + if(!bHandled) + return SvImpLBox::KeyInput( rKEvt ); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/iconviewimpl.hxx b/vcl/source/treelist/iconviewimpl.hxx new file mode 100644 index 000000000..c265ae5a2 --- /dev/null +++ b/vcl/source/treelist/iconviewimpl.hxx @@ -0,0 +1,91 @@ +/* -*- 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 . + */ + +#pragma once + +#include <svimpbox.hxx> + +class SvTreeListBox; +class Point; + +class IconViewImpl : public SvImpLBox +{ +public: + IconViewImpl(SvTreeListBox* pTreeListBox, SvTreeList* pTreeList, WinBits nWinStyle); + + void KeyDown(bool bPageDown) override; + + void KeyUp(bool bPageUp) override; + + Point GetEntryPosition(const SvTreeListEntry* pEntry) const override; + + SvTreeListEntry* GetClickedEntry(const Point& rPoint) const override; + + bool IsEntryInView(SvTreeListEntry* pEntry) const override; + + void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override; + + // returns 0 if position is just past the last entry + SvTreeListEntry* GetEntry(const Point& rPoint) const override; + + void UpdateAll(bool bInvalidateCompleteView) override; + + bool KeyInput(const KeyEvent&) override; + + void InvalidateEntry(tools::Long nId) const override; + +protected: + tools::Long GetEntryLine(const SvTreeListEntry* pEntry) const override; + + void CursorUp() override; + void CursorDown() override; + void PageDown(sal_uInt16 nDelta) override; + void PageUp(sal_uInt16 nDelta) override; + + void SyncVerThumb() override; + void AdjustScrollBars(Size& rSize) override; + +private: + enum class CallbackResult + { + Continue, + Stop, // Stop iteration + }; + struct EntryAreaInfo + { + SvTreeListEntry* entry; + short column; + tools::Rectangle area; // The area for the entry + }; + using IterateEntriesFunc = std::function<CallbackResult(const EntryAreaInfo&)>; + + void IterateVisibleEntryAreas(const IterateEntriesFunc& f, bool fromStartEntry = false) const; + + Size GetEntrySize(const SvTreeListEntry& entry) const; + // Get first entry at most n rows above; nullptr if no rows above + SvTreeListEntry* GoToPrevRow(SvTreeListEntry* pEntry, int n) const; + // Get first entry at most n rows below; nullptr if no rows below + SvTreeListEntry* GoToNextRow(SvTreeListEntry* pEntry, int n) const; + + tools::Long GetEntryRow(const SvTreeListEntry* entry) const; + void SetStartEntry(SvTreeListEntry* entry); + void ScrollTo(SvTreeListEntry* entry); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/imap.cxx b/vcl/source/treelist/imap.cxx new file mode 100644 index 000000000..84a0717c6 --- /dev/null +++ b/vcl/source/treelist/imap.cxx @@ -0,0 +1,994 @@ +/* -*- 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 <tools/urlobj.hxx> +#include <tools/fract.hxx> +#include <tools/GenericTypeSerializer.hxx> +#include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/mapmod.hxx> +#include <o3tl/numeric.hxx> +#include <svl/urihelper.hxx> +#include <vcl/imap.hxx> +#include <vcl/imapobj.hxx> +#include <vcl/imapcirc.hxx> +#include <vcl/imaprect.hxx> +#include <vcl/imappoly.hxx> + +#include <string.h> +#include <math.h> +#include <memory> +#include <sal/log.hxx> + + +#define SCALEPOINT(aPT,aFracX,aFracY) (aPT).setX(tools::Long((aPT).X()*aFracX)); \ + (aPT).setY(tools::Long((aPT).Y()*aFracY)); + + +/******************************************************************************/ + + +IMapObject::IMapObject() + : bActive( false ) + , nReadVersion( 0 ) +{ +} + +IMapObject::IMapObject( const OUString& rURL, const OUString& rAltText, const OUString& rDesc, + const OUString& rTarget, const OUString& rName, bool bURLActive ) +: aURL( rURL ) +, aAltText( rAltText ) +, aDesc( rDesc ) +, aTarget( rTarget ) +, aName( rName ) +, bActive( bURLActive ) +, nReadVersion( 0 ) +{ +} + + +void IMapObject::Write( SvStream& rOStm ) const +{ + const rtl_TextEncoding eEncoding = osl_getThreadTextEncoding(); + + rOStm.WriteUInt16( static_cast<sal_uInt16>(GetType()) ); + rOStm.WriteUInt16( IMAP_OBJ_VERSION ); + rOStm.WriteUInt16( eEncoding ); + + const OString aRelURL = OUStringToOString( + URIHelper::simpleNormalizedMakeRelative("", aURL), eEncoding); + write_uInt16_lenPrefixed_uInt8s_FromOString(rOStm, aRelURL); + write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStm, aAltText, eEncoding); + rOStm.WriteBool( bActive ); + write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStm, aTarget, eEncoding); + + IMapCompat aCompat( rOStm, StreamMode::WRITE ); + + WriteIMapObject( rOStm ); + aEventList.Write( rOStm ); // V4 + write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStm, aName, eEncoding); // V5 +} + + +/****************************************************************************** +|* +|* Binary import +|* +\******************************************************************************/ + +void IMapObject::Read( SvStream& rIStm ) +{ + rtl_TextEncoding nTextEncoding; + + // read on type and version + rIStm.SeekRel( 2 ); + rIStm.ReadUInt16( nReadVersion ); + rIStm.ReadUInt16( nTextEncoding ); + aURL = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStm, nTextEncoding); + aAltText = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStm, nTextEncoding); + rIStm.ReadCharAsBool( bActive ); + aTarget = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStm, nTextEncoding); + + // make URL absolute + aURL = URIHelper::SmartRel2Abs( INetURLObject(u""), aURL, URIHelper::GetMaybeFileHdl(), true, false, INetURLObject::EncodeMechanism::WasEncoded, INetURLObject::DecodeMechanism::Unambiguous ); + IMapCompat aCompat( rIStm, StreamMode::READ ); + + ReadIMapObject( rIStm ); + + // from version 4 onwards we read an eventlist + if ( nReadVersion >= 0x0004 ) + { + aEventList.Read(rIStm); + + // from version 5 onwards an objectname could be available + if ( nReadVersion >= 0x0005 ) + aName = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStm, nTextEncoding); + } +} + +bool IMapObject::IsEqual( const IMapObject& rEqObj ) const +{ + return ( ( aURL == rEqObj.aURL ) && + ( aAltText == rEqObj.aAltText ) && + ( aDesc == rEqObj.aDesc ) && + ( aTarget == rEqObj.aTarget ) && + ( aName == rEqObj.aName ) && + ( bActive == rEqObj.bActive ) ); +} + +IMapRectangleObject::IMapRectangleObject( const tools::Rectangle& rRect, + const OUString& rURL, + const OUString& rAltText, + const OUString& rDesc, + const OUString& rTarget, + const OUString& rName, + bool bURLActive, + bool bPixelCoords ) : + IMapObject ( rURL, rAltText, rDesc, rTarget, rName, bURLActive ) +{ + ImpConstruct( rRect, bPixelCoords ); +} + +void IMapRectangleObject::ImpConstruct( const tools::Rectangle& rRect, bool bPixel ) +{ + if ( bPixel ) + aRect = Application::GetDefaultDevice()->PixelToLogic( rRect, MapMode( MapUnit::Map100thMM ) ); + else + aRect = rRect; +} + + +/****************************************************************************** +|* +|* Binary export +|* +\******************************************************************************/ + +void IMapRectangleObject::WriteIMapObject( SvStream& rOStm ) const +{ + tools::GenericTypeSerializer aSerializer(rOStm); + aSerializer.writeRectangle(aRect); +} + + +/****************************************************************************** +|* +|* Binary import +|* +\******************************************************************************/ + +void IMapRectangleObject::ReadIMapObject( SvStream& rIStm ) +{ + tools::GenericTypeSerializer aSerializer(rIStm); + aSerializer.readRectangle(aRect); +} + + +/****************************************************************************** +|* +|* return type +|* +\******************************************************************************/ + +IMapObjectType IMapRectangleObject::GetType() const +{ + return IMapObjectType::Rectangle; +} + + +/****************************************************************************** +|* +|* Hit test +|* +\******************************************************************************/ + +bool IMapRectangleObject::IsHit( const Point& rPoint ) const +{ + return aRect.Contains( rPoint ); +} + +tools::Rectangle IMapRectangleObject::GetRectangle( bool bPixelCoords ) const +{ + tools::Rectangle aNewRect; + + if ( bPixelCoords ) + aNewRect = Application::GetDefaultDevice()->LogicToPixel( aRect, MapMode( MapUnit::Map100thMM ) ); + else + aNewRect = aRect; + + return aNewRect; +} + +void IMapRectangleObject::Scale( const Fraction& rFracX, const Fraction& rFracY ) +{ + Point aTL( aRect.TopLeft() ); + Point aBR( aRect.BottomRight() ); + + if ( rFracX.GetDenominator() && rFracY.GetDenominator() ) + { + SCALEPOINT( aTL, rFracX, rFracY ); + SCALEPOINT( aBR, rFracX, rFracY ); + } + + aRect = tools::Rectangle( aTL, aBR ); +} + +bool IMapRectangleObject::IsEqual( const IMapRectangleObject& rEqObj ) const +{ + return ( IMapObject::IsEqual( rEqObj ) && ( aRect == rEqObj.aRect ) ); +} + +IMapCircleObject::IMapCircleObject( const Point& rCenter, sal_Int32 nCircleRadius, + const OUString& rURL, + const OUString& rAltText, + const OUString& rDesc, + const OUString& rTarget, + const OUString& rName, + bool bURLActive, + bool bPixelCoords ) : + IMapObject ( rURL, rAltText, rDesc, rTarget, rName, bURLActive ) +{ + ImpConstruct( rCenter, nCircleRadius, bPixelCoords ); +} + +void IMapCircleObject::ImpConstruct( const Point& rCenter, sal_Int32 nRad, bool bPixel ) +{ + if ( bPixel ) + { + MapMode aMap100( MapUnit::Map100thMM ); + + aCenter = Application::GetDefaultDevice()->PixelToLogic( rCenter, aMap100 ); + nRadius = Application::GetDefaultDevice()->PixelToLogic( Size( nRad, 0 ), aMap100 ).Width(); + } + else + { + aCenter = rCenter; + nRadius = nRad; + } +} + + +/****************************************************************************** +|* +|* Binary export +|* +\******************************************************************************/ + +void IMapCircleObject::WriteIMapObject( SvStream& rOStm ) const +{ + sal_uInt32 nTmp = nRadius; + tools::GenericTypeSerializer aSerializer(rOStm); + aSerializer.writePoint(aCenter); + rOStm.WriteUInt32( nTmp ); +} + + +/****************************************************************************** +|* +|* Binary import +|* +\******************************************************************************/ + +void IMapCircleObject::ReadIMapObject( SvStream& rIStm ) +{ + sal_uInt32 nTmp; + + tools::GenericTypeSerializer aSerializer(rIStm); + aSerializer.readPoint(aCenter); + rIStm.ReadUInt32( nTmp ); + + nRadius = nTmp; +} + + +/****************************************************************************** +|* +|* return type +|* +\******************************************************************************/ + +IMapObjectType IMapCircleObject::GetType() const +{ + return IMapObjectType::Circle; +} + + +/****************************************************************************** +|* +|* Hit-Test +|* +\******************************************************************************/ + +bool IMapCircleObject::IsHit( const Point& rPoint ) const +{ + const Point aPoint( aCenter - rPoint ); + bool bRet = false; + + if ( static_cast<sal_Int32>(sqrt( static_cast<double>(aPoint.X()) * aPoint.X() + + aPoint.Y() * aPoint.Y() )) <= nRadius ) + { + bRet = true; + } + + return bRet; +} + +Point IMapCircleObject::GetCenter( bool bPixelCoords ) const +{ + Point aNewPoint; + + if ( bPixelCoords ) + aNewPoint = Application::GetDefaultDevice()->LogicToPixel( aCenter, MapMode( MapUnit::Map100thMM ) ); + else + aNewPoint = aCenter; + + return aNewPoint; +} + +sal_Int32 IMapCircleObject::GetRadius( bool bPixelCoords ) const +{ + sal_Int32 nNewRadius; + + if ( bPixelCoords ) + nNewRadius = Application::GetDefaultDevice()->LogicToPixel( Size( nRadius, 0 ), MapMode( MapUnit::Map100thMM ) ).Width(); + else + nNewRadius = nRadius; + + return nNewRadius; +} + +void IMapCircleObject::Scale( const Fraction& rFracX, const Fraction& rFracY ) +{ + Fraction aAverage( rFracX ); + + aAverage += rFracY; + aAverage *= Fraction( 1, 2 ); + + if ( rFracX.GetDenominator() && rFracY.GetDenominator() ) + { + SCALEPOINT( aCenter, rFracX, rFracY ); + } + + if (!aAverage.GetDenominator()) + throw o3tl::divide_by_zero(); + + nRadius = double(nRadius * aAverage); +} + +bool IMapCircleObject::IsEqual( const IMapCircleObject& rEqObj ) const +{ + return ( IMapObject::IsEqual( rEqObj ) && + ( aCenter == rEqObj.aCenter ) && + ( nRadius == rEqObj.nRadius ) ); +} + +IMapPolygonObject::IMapPolygonObject( const tools::Polygon& rPoly, + const OUString& rURL, + const OUString& rAltText, + const OUString& rDesc, + const OUString& rTarget, + const OUString& rName, + bool bURLActive, + bool bPixelCoords ) : + IMapObject ( rURL, rAltText, rDesc, rTarget, rName, bURLActive ), + bEllipse ( false ) +{ + ImpConstruct( rPoly, bPixelCoords ); +} + +void IMapPolygonObject::ImpConstruct( const tools::Polygon& rPoly, bool bPixel ) +{ + if ( bPixel ) + aPoly = Application::GetDefaultDevice()->PixelToLogic( rPoly, MapMode( MapUnit::Map100thMM ) ); + else + aPoly = rPoly; +} + + +/****************************************************************************** +|* +|* Binary export +|* +\******************************************************************************/ + +void IMapPolygonObject::WriteIMapObject( SvStream& rOStm ) const +{ + tools::GenericTypeSerializer aSerializer(rOStm); + WritePolygon( rOStm, aPoly ); + // Version 2 + rOStm.WriteBool( bEllipse ); + aSerializer.writeRectangle(aEllipse); +} + + +/****************************************************************************** +|* +|* Binary import +|* +\******************************************************************************/ + +void IMapPolygonObject::ReadIMapObject( SvStream& rIStm ) +{ + ReadPolygon( rIStm, aPoly ); + + // Version >= 2 has additional ellipses information + if ( nReadVersion >= 2 ) + { + rIStm.ReadCharAsBool( bEllipse ); + tools::GenericTypeSerializer aSerializer(rIStm); + aSerializer.readRectangle(aEllipse); + } +} + + +/****************************************************************************** +|* +|* return type +|* +\******************************************************************************/ + +IMapObjectType IMapPolygonObject::GetType() const +{ + return IMapObjectType::Polygon; +} + + +/****************************************************************************** +|* +|* hit test +|* +\******************************************************************************/ + +bool IMapPolygonObject::IsHit( const Point& rPoint ) const +{ + return aPoly.Contains( rPoint ); +} + +tools::Polygon IMapPolygonObject::GetPolygon( bool bPixelCoords ) const +{ + tools::Polygon aNewPoly; + + if ( bPixelCoords ) + aNewPoly = Application::GetDefaultDevice()->LogicToPixel( aPoly, MapMode( MapUnit::Map100thMM ) ); + else + aNewPoly = aPoly; + + return aNewPoly; +} + +void IMapPolygonObject::SetExtraEllipse( const tools::Rectangle& rEllipse ) +{ + if ( aPoly.GetSize() ) + { + bEllipse = true; + aEllipse = rEllipse; + } +} + +void IMapPolygonObject::Scale( const Fraction& rFracX, const Fraction& rFracY ) +{ + sal_uInt16 nCount = aPoly.GetSize(); + + for ( sal_uInt16 i = 0; i < nCount; i++ ) + { + Point aScaledPt( aPoly[ i ] ); + + if ( rFracX.GetDenominator() && rFracY.GetDenominator() ) + { + SCALEPOINT( aScaledPt, rFracX, rFracY ); + } + + aPoly[ i ] = aScaledPt; + } + + if ( !bEllipse ) + return; + + Point aTL( aEllipse.TopLeft() ); + Point aBR( aEllipse.BottomRight() ); + + if ( rFracX.GetDenominator() && rFracY.GetDenominator() ) + { + SCALEPOINT( aTL, rFracX, rFracY ); + SCALEPOINT( aBR, rFracX, rFracY ); + } + + aEllipse = tools::Rectangle( aTL, aBR ); +} + +bool IMapPolygonObject::IsEqual( const IMapPolygonObject& rEqObj ) +{ + bool bRet = false; + + if ( IMapObject::IsEqual( rEqObj ) ) + { + const tools::Polygon& rEqPoly = rEqObj.aPoly; + const sal_uInt16 nCount = aPoly.GetSize(); + const sal_uInt16 nEqCount = rEqPoly.GetSize(); + + if ( nCount == nEqCount ) + { + bool bDifferent = false; + + for ( sal_uInt16 i = 0; i < nCount; i++ ) + { + if ( aPoly[ i ] != rEqPoly[ i ] ) + { + bDifferent = true; + break; + } + } + + if ( !bDifferent ) + bRet = true; + } + } + + return bRet; +} + +/****************************************************************************** +|* +|* Ctor +|* +\******************************************************************************/ + +ImageMap::ImageMap( const OUString& rName ) +: aName( rName ) +{ +} + + +/****************************************************************************** +|* +|* Copy-Ctor +|* +\******************************************************************************/ + +ImageMap::ImageMap( const ImageMap& rImageMap ) +{ + + size_t nCount = rImageMap.GetIMapObjectCount(); + + for ( size_t i = 0; i < nCount; i++ ) + { + IMapObject* pCopyObj = rImageMap.GetIMapObject( i ); + + switch( pCopyObj->GetType() ) + { + case IMapObjectType::Rectangle: + maList.emplace_back( new IMapRectangleObject( *static_cast<IMapRectangleObject*>( pCopyObj ) ) ); + break; + + case IMapObjectType::Circle: + maList.emplace_back( new IMapCircleObject( *static_cast<IMapCircleObject*>( pCopyObj ) ) ); + break; + + case IMapObjectType::Polygon: + maList.emplace_back( new IMapPolygonObject( *static_cast<IMapPolygonObject*>( pCopyObj ) ) ); + break; + + default: + break; + } + } + + aName = rImageMap.aName; +} + + +/****************************************************************************** +|* +|* Dtor +|* +\******************************************************************************/ + +ImageMap::~ImageMap() +{ +} + + +/****************************************************************************** +|* +|* release internal memory +|* +\******************************************************************************/ + +void ImageMap::ClearImageMap() +{ + maList.clear(); + + aName.clear(); +} + + +/****************************************************************************** +|* +|* assignment operator +|* +\******************************************************************************/ + +ImageMap& ImageMap::operator=( const ImageMap& rImageMap ) +{ + if (this != &rImageMap) + { + size_t nCount = rImageMap.GetIMapObjectCount(); + + ClearImageMap(); + + for ( size_t i = 0; i < nCount; i++ ) + { + IMapObject* pCopyObj = rImageMap.GetIMapObject( i ); + + switch( pCopyObj->GetType() ) + { + case IMapObjectType::Rectangle: + maList.emplace_back( new IMapRectangleObject( *static_cast<IMapRectangleObject*>(pCopyObj) ) ); + break; + + case IMapObjectType::Circle: + maList.emplace_back( new IMapCircleObject( *static_cast<IMapCircleObject*>(pCopyObj) ) ); + break; + + case IMapObjectType::Polygon: + maList.emplace_back( new IMapPolygonObject( *static_cast<IMapPolygonObject*>(pCopyObj) ) ); + break; + + default: + break; + } + } + + aName = rImageMap.aName; + } + return *this; +} + + +/****************************************************************************** +|* +|* compare operator I +|* +\******************************************************************************/ + +bool ImageMap::operator==( const ImageMap& rImageMap ) +{ + const size_t nCount = maList.size(); + const size_t nEqCount = rImageMap.GetIMapObjectCount(); + bool bRet = false; + + if ( nCount == nEqCount ) + { + bool bDifferent = ( aName != rImageMap.aName ); + + for ( size_t i = 0; ( i < nCount ) && !bDifferent; i++ ) + { + IMapObject* pObj = maList[ i ].get(); + IMapObject* pEqObj = rImageMap.GetIMapObject( i ); + + if ( pObj->GetType() == pEqObj->GetType() ) + { + switch( pObj->GetType() ) + { + case IMapObjectType::Rectangle: + { + if ( ! static_cast<IMapRectangleObject*>(pObj)->IsEqual( *static_cast<IMapRectangleObject*>(pEqObj) ) ) + bDifferent = true; + } + break; + + case IMapObjectType::Circle: + { + if ( ! static_cast<IMapCircleObject*>(pObj)->IsEqual( *static_cast<IMapCircleObject*>(pEqObj) ) ) + bDifferent = true; + } + break; + + case IMapObjectType::Polygon: + { + if ( ! static_cast<IMapPolygonObject*>(pObj)->IsEqual( *static_cast<IMapPolygonObject*>(pEqObj) ) ) + bDifferent = true; + } + break; + + default: + break; + } + } + else + bDifferent = true; + } + + if ( !bDifferent ) + bRet = true; + } + + return bRet; +} + + +/****************************************************************************** +|* +|* compare operator II +|* +\******************************************************************************/ + +bool ImageMap::operator!=( const ImageMap& rImageMap ) +{ + return !( *this == rImageMap ); +} + + +/****************************************************************************** +|* +|* insert new object +|* +\******************************************************************************/ + +void ImageMap::InsertIMapObject( const IMapObject& rIMapObject ) +{ + switch( rIMapObject.GetType() ) + { + case IMapObjectType::Rectangle: + maList.emplace_back( new IMapRectangleObject( static_cast<const IMapRectangleObject&>( rIMapObject ) ) ); + break; + + case IMapObjectType::Circle: + maList.emplace_back( new IMapCircleObject( static_cast<const IMapCircleObject&>( rIMapObject ) ) ); + break; + + case IMapObjectType::Polygon: + maList.emplace_back( new IMapPolygonObject( static_cast<const IMapPolygonObject&>( rIMapObject ) ) ); + break; + + default: + break; + } +} + +void ImageMap::InsertIMapObject( std::unique_ptr<IMapObject> pNewObject ) +{ + maList.emplace_back( std::move(pNewObject) ); +} + +/****************************************************************************** +|* +|* hit test +|* +\******************************************************************************/ + +IMapObject* ImageMap::GetHitIMapObject( const Size& rTotalSize, + const Size& rDisplaySize, + const Point& rRelHitPoint, + sal_uLong nFlags ) +{ + Point aRelPoint( rTotalSize.Width() * rRelHitPoint.X() / rDisplaySize.Width(), + rTotalSize.Height() * rRelHitPoint.Y() / rDisplaySize.Height() ); + + // transform point to check before checking if flags to mirror etc. are set, + if ( nFlags ) + { + if ( nFlags & IMAP_MIRROR_HORZ ) + aRelPoint.setX( rTotalSize.Width() - aRelPoint.X() ); + + if ( nFlags & IMAP_MIRROR_VERT ) + aRelPoint.setY( rTotalSize.Height() - aRelPoint.Y() ); + } + + // walk over all objects and execute HitTest + IMapObject* pObj = nullptr; + for(const auto& i : maList) { + if ( i->IsHit( aRelPoint ) ) { + pObj = i.get(); + break; + } + } + + return( pObj ? ( pObj->IsActive() ? pObj : nullptr ) : nullptr ); +} + +void ImageMap::Scale( const Fraction& rFracX, const Fraction& rFracY ) +{ + size_t nCount = maList.size(); + + for ( size_t i = 0; i < nCount; i++ ) + { + IMapObject* pObj = maList[ i ].get(); + + switch( pObj->GetType() ) + { + case IMapObjectType::Rectangle: + static_cast<IMapRectangleObject*>( pObj )->Scale( rFracX, rFracY ); + break; + + case IMapObjectType::Circle: + static_cast<IMapCircleObject*>( pObj )->Scale( rFracX, rFracY ); + break; + + case IMapObjectType::Polygon: + static_cast<IMapPolygonObject*>( pObj )->Scale( rFracX, rFracY ); + break; + + default: + break; + } + } +} + + +/****************************************************************************** +|* +|* sequentially write objects +|* +\******************************************************************************/ + +void ImageMap::ImpWriteImageMap( SvStream& rOStm ) const +{ + size_t nCount = maList.size(); + + for ( size_t i = 0; i < nCount; i++ ) + { + auto& pObj = maList[ i ]; + pObj->Write( rOStm ); + } +} + + +/****************************************************************************** +|* +|* sequentially read objects +|* +\******************************************************************************/ + +void ImageMap::ImpReadImageMap( SvStream& rIStm, size_t nCount ) +{ + const size_t nMinRecordSize = 12; //circle, three 32bit numbers + const size_t nMaxRecords = rIStm.remainingSize() / nMinRecordSize; + + if (nCount > nMaxRecords) + { + SAL_WARN("svtools.misc", "Parsing error: " << nMaxRecords << " max possible entries, but " << + nCount << " claimed, truncating"); + nCount = nMaxRecords; + } + + // read new objects + for (size_t i = 0; i < nCount; ++i) + { + sal_uInt16 nType; + + rIStm.ReadUInt16( nType ); + rIStm.SeekRel( -2 ); + + switch( static_cast<IMapObjectType>(nType) ) + { + case IMapObjectType::Rectangle: + { + IMapRectangleObject* pObj = new IMapRectangleObject; + pObj->Read( rIStm ); + maList.emplace_back( pObj ); + } + break; + + case IMapObjectType::Circle: + { + IMapCircleObject* pObj = new IMapCircleObject; + pObj->Read( rIStm ); + maList.emplace_back( pObj ); + } + break; + + case IMapObjectType::Polygon: + { + IMapPolygonObject* pObj = new IMapPolygonObject; + pObj->Read( rIStm ); + maList.emplace_back( pObj ); + } + break; + + default: + break; + } + } +} + + +/****************************************************************************** +|* +|* store binary +|* +\******************************************************************************/ + +void ImageMap::Write( SvStream& rOStm ) const +{ + IMapCompat* pCompat; + OUString aImageName( GetName() ); + SvStreamEndian nOldFormat = rOStm.GetEndian(); + sal_uInt16 nCount = static_cast<sal_uInt16>(GetIMapObjectCount()); + const rtl_TextEncoding eEncoding = osl_getThreadTextEncoding(); //vomit! + + rOStm.SetEndian( SvStreamEndian::LITTLE ); + + // write MagicCode + rOStm.WriteCharPtr( IMAPMAGIC ); + rOStm.WriteUInt16( IMAGE_MAP_VERSION ); + write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStm, aImageName, eEncoding); + write_uInt16_lenPrefixed_uInt8s_FromOString(rOStm, ""); //dummy + rOStm.WriteUInt16( nCount ); + write_uInt16_lenPrefixed_uInt8s_FromOUString(rOStm, aImageName, eEncoding); + + pCompat = new IMapCompat( rOStm, StreamMode::WRITE ); + + // here one can insert in newer versions + + delete pCompat; + + ImpWriteImageMap( rOStm ); + + rOStm.SetEndian( nOldFormat ); +} + + +/****************************************************************************** +|* +|* load binary +|* +\******************************************************************************/ + +void ImageMap::Read( SvStream& rIStm ) +{ + char cMagic[6]; + SvStreamEndian nOldFormat = rIStm.GetEndian(); + + rIStm.SetEndian( SvStreamEndian::LITTLE ); + rIStm.ReadBytes(cMagic, sizeof(cMagic)); + + if ( !memcmp( cMagic, IMAPMAGIC, sizeof( cMagic ) ) ) + { + IMapCompat* pCompat; + sal_uInt16 nCount; + + // delete old content + ClearImageMap(); + + // read on version + rIStm.SeekRel( 2 ); + + aName = read_uInt16_lenPrefixed_uInt8s_ToOUString(rIStm, osl_getThreadTextEncoding()); + read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm); // Dummy + rIStm.ReadUInt16( nCount ); + read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm); // Dummy + + pCompat = new IMapCompat( rIStm, StreamMode::READ ); + + // here one can read in newer versions + + delete pCompat; + ImpReadImageMap( rIStm, nCount ); + + } + else + rIStm.SetError( SVSTREAM_GENERALERROR ); + + rIStm.SetEndian( nOldFormat ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/imap2.cxx b/vcl/source/treelist/imap2.cxx new file mode 100644 index 000000000..980dae3ee --- /dev/null +++ b/vcl/source/treelist/imap2.cxx @@ -0,0 +1,530 @@ +/* -*- 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 <comphelper/string.hxx> +#include <string.h> +#include <o3tl/string_view.hxx> +#include <rtl/strbuf.hxx> +#include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> +#include <tools/urlobj.hxx> + +#include <svl/urihelper.hxx> +#include <vcl/imap.hxx> +#include <vcl/imapobj.hxx> +#include <vcl/imaprect.hxx> +#include <vcl/imapcirc.hxx> +#include <vcl/imappoly.hxx> + +#include <math.h> + +#define NOTEOL(c) ((c)!='\0') + +void IMapObject::AppendCERNCoords(OStringBuffer& rBuf, const Point& rPoint100) +{ + const Point aPixPt( Application::GetDefaultDevice()->LogicToPixel( rPoint100, MapMode( MapUnit::Map100thMM ) ) ); + + rBuf.append('('); + rBuf.append(static_cast<sal_Int32>(aPixPt.X())); + rBuf.append(','); + rBuf.append(static_cast<sal_Int32>(aPixPt.Y())); + rBuf.append(") "); +} + +void IMapObject::AppendNCSACoords(OStringBuffer& rBuf, const Point& rPoint100) +{ + const Point aPixPt( Application::GetDefaultDevice()->LogicToPixel( rPoint100, MapMode( MapUnit::Map100thMM ) ) ); + + rBuf.append(static_cast<sal_Int32>(aPixPt.X())); + rBuf.append(','); + rBuf.append(static_cast<sal_Int32>(aPixPt.Y())); + rBuf.append(' '); +} + +void IMapObject::AppendCERNURL(OStringBuffer& rBuf) const +{ + rBuf.append(OUStringToOString(URIHelper::simpleNormalizedMakeRelative("", aURL), osl_getThreadTextEncoding())); +} + +void IMapObject::AppendNCSAURL(OStringBuffer& rBuf) const +{ + rBuf.append(OUStringToOString(URIHelper::simpleNormalizedMakeRelative("", aURL), osl_getThreadTextEncoding())); + rBuf.append(' '); +} + +void IMapRectangleObject::WriteCERN( SvStream& rOStm ) const +{ + OStringBuffer aStrBuf("rectangle "); + + AppendCERNCoords(aStrBuf, aRect.TopLeft()); + AppendCERNCoords(aStrBuf, aRect.BottomRight()); + AppendCERNURL(aStrBuf); + + rOStm.WriteLine(aStrBuf); +} + +void IMapRectangleObject::WriteNCSA( SvStream& rOStm ) const +{ + OStringBuffer aStrBuf("rect "); + + AppendNCSAURL(aStrBuf); + AppendNCSACoords(aStrBuf, aRect.TopLeft()); + AppendNCSACoords(aStrBuf, aRect.BottomRight()); + + rOStm.WriteLine(aStrBuf); +} + +void IMapCircleObject::WriteCERN( SvStream& rOStm ) const +{ + OStringBuffer aStrBuf("circle "); + + AppendCERNCoords(aStrBuf, aCenter); + aStrBuf.append(nRadius); + aStrBuf.append(' '); + AppendCERNURL(aStrBuf); + + rOStm.WriteLine(aStrBuf); +} + +void IMapCircleObject::WriteNCSA( SvStream& rOStm ) const +{ + OStringBuffer aStrBuf("circle "); + + AppendNCSAURL(aStrBuf); + AppendNCSACoords(aStrBuf, aCenter); + AppendNCSACoords(aStrBuf, aCenter + Point(nRadius, 0)); + + rOStm.WriteLine(aStrBuf); +} + +void IMapPolygonObject::WriteCERN( SvStream& rOStm ) const +{ + OStringBuffer aStrBuf("polygon "); + const sal_uInt16 nCount = aPoly.GetSize(); + + for (sal_uInt16 i = 0; i < nCount; ++i) + AppendCERNCoords(aStrBuf, aPoly[i]); + + AppendCERNURL(aStrBuf); + + rOStm.WriteLine(aStrBuf); +} + +void IMapPolygonObject::WriteNCSA( SvStream& rOStm ) const +{ + OStringBuffer aStrBuf("poly "); + const sal_uInt16 nCount = std::min( aPoly.GetSize(), sal_uInt16(100) ); + + AppendNCSAURL(aStrBuf); + + for (sal_uInt16 i = 0; i < nCount; ++i) + AppendNCSACoords(aStrBuf, aPoly[i]); + + rOStm.WriteLine(aStrBuf); +} + +void ImageMap::Write( SvStream& rOStm, IMapFormat nFormat ) const +{ + switch( nFormat ) + { + case IMapFormat::Binary : Write( rOStm ); break; + case IMapFormat::CERN : ImpWriteCERN( rOStm ); break; + case IMapFormat::NCSA : ImpWriteNCSA( rOStm ); break; + + default: + break; + } +} + +void ImageMap::ImpWriteCERN( SvStream& rOStm ) const +{ + size_t nCount = maList.size(); + + for ( size_t i = 0; i < nCount; i++ ) + { + IMapObject* pObj = maList[ i ].get(); + + switch( pObj->GetType() ) + { + case IMapObjectType::Rectangle: + static_cast<IMapRectangleObject*>( pObj )->WriteCERN( rOStm ); + break; + + case IMapObjectType::Circle: + static_cast<IMapCircleObject*>( pObj )->WriteCERN( rOStm ); + break; + + case IMapObjectType::Polygon: + static_cast<IMapPolygonObject*>( pObj )->WriteCERN( rOStm ); + break; + + default: + break; + } + } +} + +void ImageMap::ImpWriteNCSA( SvStream& rOStm ) const +{ + size_t nCount = maList.size(); + + for ( size_t i = 0; i < nCount; i++ ) + { + IMapObject* pObj = maList[ i ].get(); + + switch( pObj->GetType() ) + { + case IMapObjectType::Rectangle: + static_cast<IMapRectangleObject*>( pObj )->WriteNCSA( rOStm ); + break; + + case IMapObjectType::Circle: + static_cast<IMapCircleObject*>( pObj )->WriteNCSA( rOStm ); + break; + + case IMapObjectType::Polygon: + static_cast<IMapPolygonObject*>( pObj )->WriteNCSA( rOStm ); + break; + + default: + break; + } + } +} + +sal_uLong ImageMap::Read( SvStream& rIStm, IMapFormat nFormat ) +{ + sal_uLong nRet = IMAP_ERR_FORMAT; + + if ( nFormat == IMapFormat::Detect ) + nFormat = ImpDetectFormat( rIStm ); + + switch ( nFormat ) + { + case IMapFormat::Binary : Read( rIStm ); break; + case IMapFormat::CERN : ImpReadCERN( rIStm ); break; + case IMapFormat::NCSA : ImpReadNCSA( rIStm ); break; + + default: + break; + } + + if ( !rIStm.GetError() ) + nRet = IMAP_ERR_OK; + + return nRet; +} + +void ImageMap::ImpReadCERN( SvStream& rIStm ) +{ + // delete old content + ClearImageMap(); + + OStringBuffer aStr; + while ( rIStm.ReadLine( aStr ) ) + ImpReadCERNLine( aStr ); +} + +void ImageMap::ImpReadCERNLine( std::string_view rLine ) +{ + OString aStr( comphelper::string::stripStart(rLine, ' ') ); + aStr = comphelper::string::stripStart(aStr, '\t'); + aStr = aStr.replaceAll(";", ""); + aStr = aStr.toAsciiLowerCase(); + + const char* pStr = aStr.getStr(); + char cChar = *pStr++; + + // find instruction + OStringBuffer aBuf; + while ((cChar >= 'a') && (cChar <= 'z')) + { + aBuf.append(cChar); + cChar = *pStr++; + } + OString aToken = aBuf.makeStringAndClear(); + + if ( !(NOTEOL( cChar )) ) + return; + + if ( ( aToken == "rectangle" ) || ( aToken == "rect" ) ) + { + const Point aTopLeft( ImpReadCERNCoords( &pStr ) ); + const Point aBottomRight( ImpReadCERNCoords( &pStr ) ); + const OUString aURL( ImpReadCERNURL( &pStr ) ); + const tools::Rectangle aRect( aTopLeft, aBottomRight ); + + maList.emplace_back( new IMapRectangleObject( aRect, aURL, OUString(), OUString(), OUString(), OUString() ) ); + } + else if ( ( aToken == "circle" ) || ( aToken == "circ" ) ) + { + const Point aCenter( ImpReadCERNCoords( &pStr ) ); + const tools::Long nRadius = ImpReadCERNRadius( &pStr ); + const OUString aURL( ImpReadCERNURL( &pStr ) ); + + maList.emplace_back( new IMapCircleObject( aCenter, nRadius, aURL, OUString(), OUString(), OUString(), OUString() ) ); + } + else if ( ( aToken == "polygon" ) || ( aToken == "poly" ) ) + { + const sal_uInt16 nCount = comphelper::string::getTokenCount(aStr, '(') - 1; + tools::Polygon aPoly( nCount ); + + for ( sal_uInt16 i = 0; i < nCount; i++ ) + aPoly[ i ] = ImpReadCERNCoords( &pStr ); + + const OUString aURL = ImpReadCERNURL( &pStr ); + + maList.emplace_back( new IMapPolygonObject( aPoly, aURL, OUString(), OUString(), OUString(), OUString() ) ); + } +} + +Point ImageMap::ImpReadCERNCoords( const char** ppStr ) +{ + OUStringBuffer aStrX; + OUStringBuffer aStrY; + Point aPt; + char cChar = *(*ppStr)++; + + while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) ) + cChar = *(*ppStr)++; + + if ( NOTEOL( cChar ) ) + { + while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) ) + { + aStrX.append( cChar ); + cChar = *(*ppStr)++; + } + + if ( NOTEOL( cChar ) ) + { + while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) ) + cChar = *(*ppStr)++; + + while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) ) + { + aStrY.append( cChar ); + cChar = *(*ppStr)++; + } + + if ( NOTEOL( cChar ) ) + while( NOTEOL( cChar ) && ( cChar != ')' ) ) + cChar = *(*ppStr)++; + + aPt = Point( o3tl::toInt32(aStrX), o3tl::toInt32(aStrY) ); + } + } + + return aPt; +} + +tools::Long ImageMap::ImpReadCERNRadius( const char** ppStr ) +{ + OUStringBuffer aStr; + char cChar = *(*ppStr)++; + + while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) ) + cChar = *(*ppStr)++; + + if ( NOTEOL( cChar ) ) + { + while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) ) + { + aStr.append( cChar ); + cChar = *(*ppStr)++; + } + } + + return o3tl::toInt32(aStr); +} + +OUString ImageMap::ImpReadCERNURL( const char** ppStr ) +{ + OUString aStr(OUString::createFromAscii(*ppStr)); + + aStr = comphelper::string::strip(aStr, ' '); + aStr = comphelper::string::strip(aStr, '\t'); + + return INetURLObject::GetAbsURL( u"", aStr ); +} + +void ImageMap::ImpReadNCSA( SvStream& rIStm ) +{ + // delete old content + ClearImageMap(); + + OStringBuffer aStr; + while ( rIStm.ReadLine( aStr ) ) + ImpReadNCSALine( aStr ); +} + +void ImageMap::ImpReadNCSALine( std::string_view rLine ) +{ + OString aStr( comphelper::string::stripStart(rLine, ' ') ); + aStr = comphelper::string::stripStart(aStr, '\t'); + aStr = aStr.replaceAll(";", ""); + aStr = aStr.toAsciiLowerCase(); + + const char* pStr = aStr.getStr(); + char cChar = *pStr++; + + // find instruction + OStringBuffer aBuf; + while ((cChar >= 'a') && (cChar <= 'z')) + { + aBuf.append(cChar); + cChar = *pStr++; + } + OString aToken = aBuf.makeStringAndClear(); + + if ( !(NOTEOL( cChar )) ) + return; + + if ( aToken == "rect" ) + { + const OUString aURL( ImpReadNCSAURL( &pStr ) ); + const Point aTopLeft( ImpReadNCSACoords( &pStr ) ); + const Point aBottomRight( ImpReadNCSACoords( &pStr ) ); + const tools::Rectangle aRect( aTopLeft, aBottomRight ); + + maList.emplace_back( new IMapRectangleObject( aRect, aURL, OUString(), OUString(), OUString(), OUString() ) ); + } + else if ( aToken == "circle" ) + { + const OUString aURL( ImpReadNCSAURL( &pStr ) ); + const Point aCenter( ImpReadNCSACoords( &pStr ) ); + const Point aDX( aCenter - ImpReadNCSACoords( &pStr ) ); + tools::Long nRadius = static_cast<tools::Long>(sqrt( static_cast<double>(aDX.X()) * aDX.X() + + static_cast<double>(aDX.Y()) * aDX.Y() )); + + maList.emplace_back( new IMapCircleObject( aCenter, nRadius, aURL, OUString(), OUString(), OUString(), OUString() ) ); + } + else if ( aToken == "poly" ) + { + const sal_uInt16 nCount = comphelper::string::getTokenCount(aStr, ',') - 1; + const OUString aURL( ImpReadNCSAURL( &pStr ) ); + tools::Polygon aPoly( nCount ); + + for ( sal_uInt16 i = 0; i < nCount; i++ ) + aPoly[ i ] = ImpReadNCSACoords( &pStr ); + + maList.emplace_back( new IMapPolygonObject( aPoly, aURL, OUString(), OUString(), OUString(), OUString() ) ); + } +} + +OUString ImageMap::ImpReadNCSAURL( const char** ppStr ) +{ + OUStringBuffer aStr; + char cChar = *(*ppStr)++; + + while( NOTEOL( cChar ) && ( ( cChar == ' ' ) || ( cChar == '\t' ) ) ) + cChar = *(*ppStr)++; + + if ( NOTEOL( cChar ) ) + { + while( NOTEOL( cChar ) && ( cChar != ' ' ) && ( cChar != '\t' ) ) + { + aStr.append( cChar ); + cChar = *(*ppStr)++; + } + } + + return INetURLObject::GetAbsURL( u"", aStr.makeStringAndClear() ); +} + +Point ImageMap::ImpReadNCSACoords( const char** ppStr ) +{ + OUStringBuffer aStrX; + OUStringBuffer aStrY; + Point aPt; + char cChar = *(*ppStr)++; + + while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) ) + cChar = *(*ppStr)++; + + if ( NOTEOL( cChar ) ) + { + while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) ) + { + aStrX.append( cChar ); + cChar = *(*ppStr)++; + } + + if ( NOTEOL( cChar ) ) + { + while( NOTEOL( cChar ) && ( ( cChar < '0' ) || ( cChar > '9' ) ) ) + cChar = *(*ppStr)++; + + while( NOTEOL( cChar ) && ( cChar >= '0' ) && ( cChar <= '9' ) ) + { + aStrY.append( cChar ); + cChar = *(*ppStr)++; + } + + aPt = Point( o3tl::toInt32(aStrX), o3tl::toInt32(aStrY) ); + } + } + + return aPt; +} + +IMapFormat ImageMap::ImpDetectFormat( SvStream& rIStm ) +{ + sal_uInt64 nPos = rIStm.Tell(); + IMapFormat nRet = IMapFormat::Binary; + char cMagic[6]; + + rIStm.ReadBytes(cMagic, sizeof(cMagic)); + + // if we do not have an internal formats + // we check the format + if ( memcmp( cMagic, IMAPMAGIC, sizeof( cMagic ) ) ) + { + tools::Long nCount = 128; + + rIStm.Seek( nPos ); + OString aStr; + while ( rIStm.ReadLine( aStr ) && nCount-- ) + { + aStr = aStr.toAsciiLowerCase(); + + if ( (aStr.indexOf("rect") != -1) || + (aStr.indexOf("circ") != -1) || + (aStr.indexOf("poly") != -1) ) + { + if ( ( aStr.indexOf('(') != -1 ) && + ( aStr.indexOf(')') != -1 ) ) + { + nRet = IMapFormat::CERN; + } + else + nRet = IMapFormat::NCSA; + + break; + } + } + } + + rIStm.Seek( nPos ); + + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/imap3.cxx b/vcl/source/treelist/imap3.cxx new file mode 100644 index 000000000..073725f34 --- /dev/null +++ b/vcl/source/treelist/imap3.cxx @@ -0,0 +1,84 @@ +/* -*- 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 <vcl/imap.hxx> + +#include <tools/debug.hxx> + +/****************************************************************************** +|* +|* Ctor +|* +\******************************************************************************/ + +IMapCompat::IMapCompat(SvStream& rStm, const StreamMode nStreamMode) + : pRWStm(&rStm) + , nCompatPos(0) + , nTotalSize(0) + , nStmMode(nStreamMode) +{ + DBG_ASSERT(nStreamMode == StreamMode::READ || nStreamMode == StreamMode::WRITE, "Wrong Mode!"); + + if (pRWStm->GetError()) + return; + + if (nStmMode == StreamMode::WRITE) + { + nCompatPos = pRWStm->Tell(); + pRWStm->SeekRel(4); + nTotalSize = nCompatPos + 4; + } + else + { + sal_uInt32 nTotalSizeTmp; + pRWStm->ReadUInt32(nTotalSizeTmp); + nTotalSize = nTotalSizeTmp; + nCompatPos = pRWStm->Tell(); + } +} + +/****************************************************************************** +|* +|* Dtor +|* +\******************************************************************************/ + +IMapCompat::~IMapCompat() +{ + if (pRWStm->GetError()) + return; + + if (nStmMode == StreamMode::WRITE) + { + const sal_uInt64 nEndPos = pRWStm->Tell(); + + pRWStm->Seek(nCompatPos); + pRWStm->WriteUInt32(nEndPos - nTotalSize); + pRWStm->Seek(nEndPos); + } + else + { + const sal_uInt64 nReadSize = pRWStm->Tell() - nCompatPos; + + if (nTotalSize > nReadSize) + pRWStm->SeekRel(nTotalSize - nReadSize); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/inetimg.cxx b/vcl/source/treelist/inetimg.cxx new file mode 100644 index 000000000..35c795cb0 --- /dev/null +++ b/vcl/source/treelist/inetimg.cxx @@ -0,0 +1,136 @@ +/* -*- 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 <osl/thread.h> +#include <sot/formats.hxx> +#include <tools/stream.hxx> + +#include <vcl/inetimg.hxx> +#include <o3tl/string_view.hxx> + +const sal_Unicode TOKEN_SEPARATOR = '\001'; + +void INetImage::Write( SvStream& rOStm, SotClipboardFormatId nFormat ) const +{ + switch( nFormat ) + { + case SotClipboardFormatId::INET_IMAGE: + { + OUString sString( + aImageURL + OUStringChar(TOKEN_SEPARATOR) + aTargetURL + + OUStringChar(TOKEN_SEPARATOR) + aTargetFrame + + OUStringChar(TOKEN_SEPARATOR) /* + aAlternateText */ + + OUStringChar(TOKEN_SEPARATOR) + + OUString::number(aSizePixel.Width()) + + OUStringChar(TOKEN_SEPARATOR) + + OUString::number(aSizePixel.Height())); + + OString sOut(OUStringToOString(sString, + RTL_TEXTENCODING_UTF8)); + + rOStm.WriteBytes(sOut.getStr(), sOut.getLength()); + static const char aEndChar[2] = { 0 }; + rOStm.WriteBytes(aEndChar, sizeof(aEndChar)); + } + break; + + case SotClipboardFormatId::NETSCAPE_IMAGE: + break; + default: break; + } +} + +bool INetImage::Read( SvStream& rIStm, SotClipboardFormatId nFormat ) +{ + bool bRet = false; + switch( nFormat ) + { + case SotClipboardFormatId::INET_IMAGE: + { + OUString sINetImg = read_zeroTerminated_uInt8s_ToOUString(rIStm, RTL_TEXTENCODING_UTF8); + sal_Int32 nStart = 0; + aImageURL = sINetImg.getToken( 0, TOKEN_SEPARATOR, nStart ); + aTargetURL = sINetImg.getToken( 0, TOKEN_SEPARATOR, nStart ); + aTargetFrame = sINetImg.getToken( 0, TOKEN_SEPARATOR, nStart ); + /*aAlternateText =*/ sINetImg.getToken( 0, TOKEN_SEPARATOR, nStart ); + aSizePixel.setWidth( o3tl::toInt32(o3tl::getToken(sINetImg, 0, TOKEN_SEPARATOR, + nStart )) ); + aSizePixel.setHeight(o3tl::toInt32(o3tl::getToken( sINetImg, 0, TOKEN_SEPARATOR, + nStart )) ); + bRet = !sINetImg.isEmpty(); + } + break; + + case SotClipboardFormatId::NETSCAPE_IMAGE: + { +/* + --> structure size MUST - alignment of 4! + int iSize; // size of all data, including variable length strings + sal_Bool bIsMap; // For server side maps + sal_Int32 iWidth; // Fixed size data correspond to fields in LO_ImageDataStruct + sal_Int32 iHeight; // and EDT_ImageData + sal_Int32 iHSpace; + sal_Int32 iVSpace; + sal_Int32 iBorder; + int iLowResOffset; // Offsets into string_data. If 0, string is NULL (not used) + int iAltOffset; // (alternate text?) + int iAnchorOffset; // HREF in image + int iExtraHTML_Offset; // Extra HTML (stored in CImageElement) + char pImageURL[1]; // Append all variable-length strings starting here +*/ + rtl_TextEncoding eSysCSet = osl_getThreadTextEncoding(); + sal_Int32 nVal, nAnchorOffset, nAltOffset; + sal_uInt64 nFilePos; + + nFilePos = rIStm.Tell(); + // skip over iSize (int), bIsMao ( sal_Bool ) alignment of 4 !!!! + rIStm.SeekRel( 8 ); + rIStm.ReadInt32( nVal ); aSizePixel.setWidth( nVal ); + rIStm.ReadInt32( nVal ); aSizePixel.setHeight( nVal ); + // skip over iHSpace, iVSpace, iBorder, iLowResOffset + rIStm.SeekRel( 3 * sizeof( sal_Int32 ) + sizeof( int ) ); + rIStm.ReadInt32( nAltOffset ); + rIStm.ReadInt32( nAnchorOffset ); + // skip over iExtraHTML_Offset + rIStm.SeekRel( sizeof( int ) ); + + aImageURL = read_zeroTerminated_uInt8s_ToOUString(rIStm, eSysCSet); + if( nAltOffset ) + { + rIStm.Seek( nFilePos + nAltOffset ); + /*aAlternateText =*/ read_zeroTerminated_uInt8s_ToOUString(rIStm, eSysCSet); + } + + if( nAnchorOffset ) + { + rIStm.Seek( nFilePos + nAnchorOffset ); + aTargetURL = read_zeroTerminated_uInt8s_ToOUString(rIStm, eSysCSet); + } + else if( !aTargetURL.isEmpty() ) + aTargetURL.clear(); + + bRet = ERRCODE_NONE == rIStm.GetError(); + } + break; + default: break; + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/svimpbox.cxx b/vcl/source/treelist/svimpbox.cxx new file mode 100644 index 000000000..e75a13945 --- /dev/null +++ b/vcl/source/treelist/svimpbox.cxx @@ -0,0 +1,3142 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/safeint.hxx> +#include <vcl/svapp.hxx> +#include <vcl/salnativewidgets.hxx> +#include <vcl/help.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandevent.hxx> + +#include <cstdlib> +#include <memory> +#include <stack> + +#include <vcl/toolkit/treelistbox.hxx> +#include <vcl/toolkit/svlbitm.hxx> +#include <tools/wintypes.hxx> +#include <bitmaps.hlst> +#include <svimpbox.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <tools/debug.hxx> + +#include <vcl/toolkit/treelistentry.hxx> +#include <vcl/toolkit/viewdataentry.hxx> + +// #i27063# (pl), #i32300# (pb) never access VCL after DeInitVCL - also no destructors +Image* SvImpLBox::s_pDefCollapsed = nullptr; +Image* SvImpLBox::s_pDefExpanded = nullptr; +oslInterlockedCount SvImpLBox::s_nImageRefCount = 0; + +SvImpLBox::SvImpLBox( SvTreeListBox* pLBView, SvTreeList* pLBTree, WinBits nWinStyle) + : m_aScrBarBox(VclPtr<ScrollBarBox>::Create(pLBView)) + , m_aFctSet(this, pLBView) + , mbForceMakeVisible (false) + , m_aEditIdle("SvImpLBox m_aEditIdle") + , m_aHorSBar(VclPtr<ScrollBar>::Create(pLBView, WB_DRAG | WB_HSCROLL)) + , m_aVerSBar(VclPtr<ScrollBar>::Create(pLBView, WB_DRAG | WB_VSCROLL)) + , m_aOutputSize(0, 0) + , mbNoAutoCurEntry(false) + , m_aSelEng(pLBView, nullptr) + , m_nNextVerVisSize(0) +{ + osl_atomic_increment(&s_nImageRefCount); + m_pView = pLBView; + m_pTree = pLBTree; + m_aSelEng.SetFunctionSet( static_cast<FunctionSet*>(&m_aFctSet) ); + m_aSelEng.ExpandSelectionOnMouseMove( false ); + SetStyle( nWinStyle ); + SetSelectionMode( SelectionMode::Single ); + SetDragDropMode( DragDropMode::NONE ); + + m_aVerSBar->SetScrollHdl( LINK( this, SvImpLBox, ScrollUpDownHdl ) ); + m_aHorSBar->SetScrollHdl( LINK( this, SvImpLBox, ScrollLeftRightHdl ) ); + m_aHorSBar->SetEndScrollHdl( LINK( this, SvImpLBox, EndScrollHdl ) ); + m_aVerSBar->SetEndScrollHdl( LINK( this, SvImpLBox, EndScrollHdl ) ); + m_aVerSBar->SetRange( Range(0,0) ); + m_aVerSBar->Hide(); + m_aHorSBar->SetRange( Range(0,0) ); + m_aHorSBar->SetPageSize( 24 ); // pixels + m_aHorSBar->SetLineSize( 8 ); // pixels + + m_nHorSBarHeight = static_cast<short>(m_aHorSBar->GetSizePixel().Height()); + m_nVerSBarWidth = static_cast<short>(m_aVerSBar->GetSizePixel().Width()); + + m_pStartEntry = nullptr; + m_pCursor = nullptr; + m_pCursorOld = nullptr; + m_pAnchor = nullptr; + m_nVisibleCount = 0; // number of rows of data in control + m_nNodeBmpTabDistance = NODE_BMP_TABDIST_NOTVALID; + m_nNodeBmpWidth = 0; + + // button animation in listbox + m_pActiveButton = nullptr; + m_pActiveEntry = nullptr; + m_pActiveTab = nullptr; + + m_nFlags = LBoxFlags::NONE; + + m_aEditIdle.SetPriority( TaskPriority::LOWEST ); + m_aEditIdle.SetInvokeHandler( LINK(this,SvImpLBox,EditTimerCall) ); + + m_nMostRight = -1; + m_pMostRightEntry = nullptr; + m_nCurUserEvent = nullptr; + + m_bUpdateMode = true; + m_bInVScrollHdl = false; + m_nFlags |= LBoxFlags::Filling; + + m_bSubLstOpLR = false; +} + +SvImpLBox::~SvImpLBox() +{ + m_aEditIdle.Stop(); + StopUserEvent(); + + if ( osl_atomic_decrement(&s_nImageRefCount) == 0 ) + { + delete s_pDefCollapsed; + s_pDefCollapsed = nullptr; + delete s_pDefExpanded; + s_pDefExpanded = nullptr; + } + m_aVerSBar.disposeAndClear(); + m_aHorSBar.disposeAndClear(); + m_aScrBarBox.disposeAndClear(); +} + +void SvImpLBox::UpdateStringSorter() +{ + const css::lang::Locale& rNewLocale = Application::GetSettings().GetLanguageTag().getLocale(); + + if( m_pStringSorter ) + { + // different Locale from the older one, drop it and force recreate + const css::lang::Locale &aLocale = m_pStringSorter->getLocale(); + if( aLocale.Language != rNewLocale.Language || + aLocale.Country != rNewLocale.Country || + aLocale.Variant != rNewLocale.Variant ) + m_pStringSorter.reset(); + } + + if( !m_pStringSorter ) + { + m_pStringSorter.reset(new comphelper::string::NaturalStringSorter( + ::comphelper::getProcessComponentContext(), + rNewLocale)); + } +} + +short SvImpLBox::UpdateContextBmpWidthVector( SvTreeListEntry const * pEntry, short nWidth ) +{ + DBG_ASSERT( m_pView->pModel, "View and Model aren't valid!" ); + + sal_uInt16 nDepth = m_pView->pModel->GetDepth( pEntry ); + // initialize vector if necessary + std::vector< short >::size_type nSize = m_aContextBmpWidthVector.size(); + while ( nDepth > nSize ) + { + m_aContextBmpWidthVector.resize( nSize + 1 ); + m_aContextBmpWidthVector.at( nSize ) = nWidth; + ++nSize; + } + if( m_aContextBmpWidthVector.size() == nDepth ) + { + m_aContextBmpWidthVector.resize( nDepth + 1 ); + m_aContextBmpWidthVector.at( nDepth ) = 0; + } + short nContextBmpWidth = m_aContextBmpWidthVector[ nDepth ]; + if( nContextBmpWidth < nWidth ) + { + m_aContextBmpWidthVector.at( nDepth ) = nWidth; + return nWidth; + } + else + return nContextBmpWidth; +} + +void SvImpLBox::UpdateContextBmpWidthVectorFromMovedEntry( SvTreeListEntry* pEntry ) +{ + DBG_ASSERT( pEntry, "Moved Entry is invalid!" ); + + SvLBoxContextBmp* pBmpItem = static_cast< SvLBoxContextBmp* >( pEntry->GetFirstItem(SvLBoxItemType::ContextBmp) ); + short nExpWidth = static_cast<short>(pBmpItem->GetBitmap1().GetSizePixel().Width()); + short nColWidth = static_cast<short>(pBmpItem->GetBitmap2().GetSizePixel().Width()); + short nMax = std::max(nExpWidth, nColWidth); + UpdateContextBmpWidthVector( pEntry, nMax ); + + if( pEntry->HasChildren() ) // recursive call, whether expanded or not + { + SvTreeListEntry* pChild = m_pView->FirstChild( pEntry ); + DBG_ASSERT( pChild, "The first child is invalid!" ); + do + { + UpdateContextBmpWidthVectorFromMovedEntry( pChild ); + pChild = m_pView->Next( pChild ); + } while ( pChild ); + } +} + +void SvImpLBox::UpdateContextBmpWidthMax( SvTreeListEntry const * pEntry ) +{ + sal_uInt16 nDepth = m_pView->pModel->GetDepth( pEntry ); + if( m_aContextBmpWidthVector.empty() ) + return; + short nWidth = m_aContextBmpWidthVector[ nDepth ]; + if( nWidth != m_pView->nContextBmpWidthMax ) { + m_pView->nContextBmpWidthMax = nWidth; + m_nFlags |= LBoxFlags::IgnoreChangedTabs; + m_pView->SetTabs(); + m_nFlags &= ~LBoxFlags::IgnoreChangedTabs; + } +} + +void SvImpLBox::SetStyle( WinBits i_nWinStyle ) +{ + m_nStyle = i_nWinStyle; + if ( ( m_nStyle & WB_SIMPLEMODE) && ( m_aSelEng.GetSelectionMode() == SelectionMode::Multiple ) ) + m_aSelEng.AddAlways( true ); +} + +void SvImpLBox::SetNoAutoCurEntry( bool b ) +{ + mbNoAutoCurEntry = b; +} + +// don't touch the model any more +void SvImpLBox::Clear() +{ + StopUserEvent(); + m_pStartEntry = nullptr; + m_pAnchor = nullptr; + + m_pActiveButton = nullptr; + m_pActiveEntry = nullptr; + m_pActiveTab = nullptr; + + m_nMostRight = -1; + m_pMostRightEntry = nullptr; + + // don't touch the cursor any more + if( m_pCursor ) + { + if( m_pView->HasFocus() ) + m_pView->HideFocus(); + m_pCursor = nullptr; + } + m_pCursorOld = nullptr; + m_aVerSBar->Hide(); + m_aVerSBar->SetThumbPos( 0 ); + Range aRange( 0, 0 ); + m_aVerSBar->SetRange( aRange ); + m_aOutputSize = m_pView->Control::GetOutputSizePixel(); + m_aHorSBar->Hide(); + m_aHorSBar->SetThumbPos( 0 ); + MapMode aMapMode( m_pView->GetMapMode()); + aMapMode.SetOrigin( Point(0,0) ); + m_pView->Control::SetMapMode( aMapMode ); + m_aHorSBar->SetRange( aRange ); + m_aHorSBar->SetSizePixel(Size(m_aOutputSize.Width(),m_nHorSBarHeight)); + m_pView->GetOutDev()->SetClipRegion(); + if( GetUpdateMode() ) + m_pView->Invalidate( GetVisibleArea() ); + m_nFlags |= LBoxFlags::Filling; + if( !m_aHorSBar->IsVisible() && !m_aVerSBar->IsVisible() ) + m_aScrBarBox->Hide(); + + m_aContextBmpWidthVector.clear(); + + CallEventListeners( VclEventId::ListboxItemRemoved ); +} + +// ********************************************************************* +// Paint, navigate, scroll +// ********************************************************************* + +IMPL_LINK_NOARG(SvImpLBox, EndScrollHdl, ScrollBar*, void) +{ + if( m_nFlags & LBoxFlags::EndScrollSetVisSize ) + { + m_aVerSBar->SetVisibleSize( m_nNextVerVisSize ); + m_nFlags &= ~LBoxFlags::EndScrollSetVisSize; + } +} + +// handler for vertical scrollbar + +IMPL_LINK( SvImpLBox, ScrollUpDownHdl, ScrollBar *, pScrollBar, void ) +{ + DBG_ASSERT(!m_bInVScrollHdl,"Scroll handler out-paces itself!"); + tools::Long nDelta = pScrollBar->GetDelta(); + if( !nDelta ) + return; + + // when only one row don't skip lines + if (pScrollBar->GetPageSize() == 1) + nDelta = nDelta > 0 ? 1 : -1; + + m_nFlags &= ~LBoxFlags::Filling; + + m_bInVScrollHdl = true; + + if( m_pView->IsEditingActive() ) + { + m_pView->EndEditing( true ); // Cancel + m_pView->PaintImmediately(); + } + + if( nDelta > 0 ) + { + if( nDelta == 1 && pScrollBar->GetPageSize() > 1) + CursorDown(); + else + PageDown( static_cast<sal_uInt16>(nDelta) ); + } + else + { + nDelta *= -1; + if( nDelta == 1 && pScrollBar->GetPageSize() > 1) + CursorUp(); + else + PageUp( static_cast<sal_uInt16>(nDelta) ); + } + m_bInVScrollHdl = false; +} + + +void SvImpLBox::CursorDown() +{ + if (!m_pStartEntry) + return; + + SvTreeListEntry* pNextFirstToDraw = m_pView->NextVisible(m_pStartEntry); + if( pNextFirstToDraw ) + { + m_nFlags &= ~LBoxFlags::Filling; + ShowCursor( false ); + m_pView->PaintImmediately(); + m_pStartEntry = pNextFirstToDraw; + tools::Rectangle aArea( GetVisibleArea() ); + m_pView->Scroll( 0, -(m_pView->GetEntryHeight()), aArea, ScrollFlags::NoChildren ); + m_pView->PaintImmediately(); + ShowCursor( true ); + m_pView->NotifyScrolled(); + } +} + +void SvImpLBox::CursorUp() +{ + if (!m_pStartEntry) + return; + + SvTreeListEntry* pPrevFirstToDraw = m_pView->PrevVisible(m_pStartEntry); + if( !pPrevFirstToDraw ) + return; + + m_nFlags &= ~LBoxFlags::Filling; + tools::Long nEntryHeight = m_pView->GetEntryHeight(); + ShowCursor( false ); + m_pView->PaintImmediately(); + m_pStartEntry = pPrevFirstToDraw; + tools::Rectangle aArea( GetVisibleArea() ); + if (aArea.GetHeight() > nEntryHeight) + aArea.AdjustBottom(-nEntryHeight); + m_pView->Scroll( 0, nEntryHeight, aArea, ScrollFlags::NoChildren ); + m_pView->PaintImmediately(); + ShowCursor( true ); + m_pView->NotifyScrolled(); +} + +void SvImpLBox::PageDown( sal_uInt16 nDelta ) +{ + sal_uInt16 nRealDelta = nDelta; + + if( !nDelta ) + return; + + if (!m_pStartEntry) + return; + + SvTreeListEntry* pNext = m_pView->NextVisible(m_pStartEntry, nRealDelta); + if( pNext == m_pStartEntry ) + return; + + ShowCursor( false ); + + m_nFlags &= ~LBoxFlags::Filling; + m_pStartEntry = pNext; + + if( nRealDelta >= m_nVisibleCount ) + { + m_pView->Invalidate( GetVisibleArea() ); + m_pView->PaintImmediately(); + } + else + { + tools::Rectangle aArea( GetVisibleArea() ); + tools::Long nScroll = m_pView->GetEntryHeight() * static_cast<tools::Long>(nRealDelta); + nScroll = -nScroll; + m_pView->PaintImmediately(); + m_pView->Scroll( 0, nScroll, aArea, ScrollFlags::NoChildren ); + m_pView->PaintImmediately(); + } + + ShowCursor( true ); + m_pView->NotifyScrolled(); +} + +void SvImpLBox::PageUp( sal_uInt16 nDelta ) +{ + sal_uInt16 nRealDelta = nDelta; + if( !nDelta ) + return; + + if (!m_pStartEntry) + return; + + SvTreeListEntry* pPrev = m_pView->PrevVisible(m_pStartEntry, nRealDelta); + if( pPrev == m_pStartEntry ) + return; + + m_nFlags &= ~LBoxFlags::Filling; + ShowCursor( false ); + + m_pStartEntry = pPrev; + if( nRealDelta >= m_nVisibleCount ) + { + m_pView->Invalidate( GetVisibleArea() ); + m_pView->PaintImmediately(); + } + else + { + tools::Long nEntryHeight = m_pView->GetEntryHeight(); + tools::Rectangle aArea( GetVisibleArea() ); + m_pView->PaintImmediately(); + m_pView->Scroll( 0, nEntryHeight*nRealDelta, aArea, ScrollFlags::NoChildren ); + m_pView->PaintImmediately(); + } + + ShowCursor( true ); + m_pView->NotifyScrolled(); +} + +void SvImpLBox::KeyUp( bool bPageUp ) +{ + if( !m_aVerSBar->IsVisible() ) + return; + + tools::Long nDelta; + if( bPageUp ) + nDelta = m_aVerSBar->GetPageSize(); + else + nDelta = 1; + + tools::Long nThumbPos = m_aVerSBar->GetThumbPos(); + + if( nThumbPos < nDelta ) + nDelta = nThumbPos; + + if( nDelta <= 0 ) + return; + + m_nFlags &= ~LBoxFlags::Filling; + + m_aVerSBar->SetThumbPos( nThumbPos - nDelta ); + if( bPageUp ) + PageUp( static_cast<short>(nDelta) ); + else + CursorUp(); +} + + +void SvImpLBox::KeyDown( bool bPageDown ) +{ + if( !m_aVerSBar->IsVisible() ) + return; + + tools::Long nDelta; + if( bPageDown ) + nDelta = m_aVerSBar->GetPageSize(); + else + nDelta = 1; + + tools::Long nThumbPos = m_aVerSBar->GetThumbPos(); + tools::Long nVisibleSize = m_aVerSBar->GetVisibleSize(); + tools::Long nRange = m_aVerSBar->GetRange().Len(); + + tools::Long nTmp = nThumbPos+nVisibleSize; + while( (nDelta > 0) && (nTmp+nDelta) >= nRange ) + nDelta--; + + if( nDelta <= 0 ) + return; + + m_nFlags &= ~LBoxFlags::Filling; + + m_aVerSBar->SetThumbPos( nThumbPos+nDelta ); + if( bPageDown ) + PageDown( static_cast<short>(nDelta) ); + else + CursorDown(); +} + + +void SvImpLBox::InvalidateEntriesFrom( tools::Long nY ) const +{ + if( !(m_nFlags & LBoxFlags::InPaint )) + { + tools::Rectangle aRect( GetVisibleArea() ); + aRect.SetTop( nY ); + m_pView->Invalidate( aRect ); + } +} + +void SvImpLBox::InvalidateEntry( tools::Long nY ) const +{ + if( m_nFlags & LBoxFlags::InPaint ) + return; + + tools::Rectangle aRect( GetVisibleArea() ); + tools::Long nMaxBottom = aRect.Bottom(); + aRect.SetTop( nY ); + aRect.SetBottom( nY ); aRect.AdjustBottom(m_pView->GetEntryHeight() ); + if( aRect.Top() > nMaxBottom ) + return; + if( aRect.Bottom() > nMaxBottom ) + aRect.SetBottom( nMaxBottom ); + if (m_pView->SupportsDoubleBuffering()) + // Perform full paint when flicker is to be avoided explicitly. + m_pView->Invalidate(); + else + m_pView->Invalidate(aRect); +} + +void SvImpLBox::InvalidateEntry( SvTreeListEntry* pEntry ) +{ + if( GetUpdateMode() ) + { + tools::Long nPrev = m_nMostRight; + SetMostRight( pEntry ); + if( nPrev < m_nMostRight ) + ShowVerSBar(); + } + if( !(m_nFlags & LBoxFlags::InPaint )) + { + bool bHasFocusRect = false; + if( pEntry==m_pCursor && m_pView->HasFocus() ) + { + bHasFocusRect = true; + ShowCursor( false ); + } + InvalidateEntry( GetEntryLine( pEntry ) ); + if( bHasFocusRect ) + ShowCursor( true ); + } +} + + +void SvImpLBox::RecalcFocusRect() +{ + if( m_pView->HasFocus() && m_pCursor ) + { + m_pView->HideFocus(); + tools::Long nY = GetEntryLine( m_pCursor ); + tools::Rectangle aRect = m_pView->GetFocusRect( m_pCursor, nY ); + vcl::Region aOldClip( m_pView->GetOutDev()->GetClipRegion()); + vcl::Region aClipRegion( GetClipRegionRect() ); + m_pView->GetOutDev()->SetClipRegion( aClipRegion ); + m_pView->ShowFocus( aRect ); + m_pView->GetOutDev()->SetClipRegion( aOldClip ); + } +} + + +// Sets cursor. When using SingleSelection, the selection is adjusted. +void SvImpLBox::SetCursor( SvTreeListEntry* pEntry, bool bForceNoSelect ) +{ + SvViewDataEntry* pViewDataNewCur = nullptr; + if( pEntry ) + pViewDataNewCur= m_pView->GetViewDataEntry(pEntry); + if( pEntry && + pEntry == m_pCursor && + pViewDataNewCur && + pViewDataNewCur->HasFocus() && + pViewDataNewCur->IsSelected()) + { + return; + } + + // if this cursor is not selectable, find first visible that is and use it + while( pEntry && pViewDataNewCur && !pViewDataNewCur->IsSelectable() ) + { + pEntry = m_pView->NextVisible(pEntry); + pViewDataNewCur = pEntry ? m_pView->GetViewDataEntry(pEntry) : nullptr; + } + + SvTreeListEntry* pOldCursor = m_pCursor; + if( m_pCursor && pEntry != m_pCursor ) + { + m_pView->SetEntryFocus( m_pCursor, false ); + if( m_bSimpleTravel ) + m_pView->Select( m_pCursor, false ); + m_pView->HideFocus(); + } + m_pCursor = pEntry; + if( m_pCursor ) + { + if (pViewDataNewCur) + pViewDataNewCur->SetFocus( true ); + if(!bForceNoSelect && m_bSimpleTravel && !(m_nFlags & LBoxFlags::DeselectAll) && GetUpdateMode()) + { + m_pView->Select( m_pCursor ); + CallEventListeners( VclEventId::ListboxTreeFocus, m_pCursor ); + } + // multiple selection: select in cursor move if we're not in + // Add mode (Ctrl-F8) + else if( GetUpdateMode() && + m_pView->GetSelectionMode() == SelectionMode::Multiple && + !(m_nFlags & LBoxFlags::DeselectAll) && !m_aSelEng.IsAddMode() && + !bForceNoSelect ) + { + m_pView->Select( m_pCursor ); + CallEventListeners( VclEventId::ListboxTreeFocus, m_pCursor ); + } + else + { + ShowCursor( true ); + if (bForceNoSelect && GetUpdateMode()) + { + CallEventListeners( VclEventId::ListboxTreeFocus, m_pCursor); + } + } + + if( m_pAnchor ) + { + DBG_ASSERT(m_aSelEng.GetSelectionMode() != SelectionMode::Single,"Mode?"); + SetAnchorSelection( pOldCursor, m_pCursor ); + } + } + m_nFlags &= ~LBoxFlags::DeselectAll; + + m_pView->OnCurrentEntryChanged(); +} + +void SvImpLBox::ShowCursor( bool bShow ) +{ + if( !bShow || !m_pCursor || !m_pView->HasFocus() ) + { + vcl::Region aOldClip( m_pView->GetOutDev()->GetClipRegion()); + vcl::Region aClipRegion( GetClipRegionRect() ); + m_pView->GetOutDev()->SetClipRegion( aClipRegion ); + m_pView->HideFocus(); + m_pView->GetOutDev()->SetClipRegion( aOldClip ); + } + else + { + tools::Long nY = GetEntryLine( m_pCursor ); + tools::Rectangle aRect = m_pView->GetFocusRect( m_pCursor, nY ); + vcl::Region aOldClip( m_pView->GetOutDev()->GetClipRegion()); + vcl::Region aClipRegion( GetClipRegionRect() ); + m_pView->GetOutDev()->SetClipRegion( aClipRegion ); + m_pView->ShowFocus( aRect ); + m_pView->GetOutDev()->SetClipRegion( aOldClip ); + } +} + + +void SvImpLBox::UpdateAll( bool bInvalidateCompleteView ) +{ + FindMostRight(); + m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1 ) ); + SyncVerThumb(); + FillView(); + ShowVerSBar(); + if( m_bSimpleTravel && m_pCursor && m_pView->HasFocus() ) + m_pView->Select( m_pCursor ); + ShowCursor( true ); + if( bInvalidateCompleteView ) + m_pView->Invalidate(); + else + m_pView->Invalidate( GetVisibleArea() ); +} + +IMPL_LINK( SvImpLBox, ScrollLeftRightHdl, ScrollBar *, pScrollBar, void ) +{ + tools::Long nDelta = pScrollBar->GetDelta(); + if( nDelta ) + { + if( m_pView->IsEditingActive() ) + { + m_pView->EndEditing( true ); // Cancel + m_pView->PaintImmediately(); + } + m_pView->nFocusWidth = -1; + KeyLeftRight( nDelta ); + } +} + +void SvImpLBox::KeyLeftRight( tools::Long nDelta ) +{ + if( !(m_nFlags & LBoxFlags::InResize) ) + m_pView->PaintImmediately(); + m_nFlags &= ~LBoxFlags::Filling; + ShowCursor( false ); + + // calculate new origin + tools::Long nPos = m_aHorSBar->GetThumbPos(); + Point aOrigin( -nPos, 0 ); + + MapMode aMapMode( m_pView->GetMapMode() ); + aMapMode.SetOrigin( aOrigin ); + m_pView->SetMapMode( aMapMode ); + + if( !(m_nFlags & LBoxFlags::InResize) ) + { + tools::Rectangle aRect( GetVisibleArea() ); + m_pView->Scroll( -nDelta, 0, aRect, ScrollFlags::NoChildren ); + } + else + m_pView->Invalidate(); + RecalcFocusRect(); + ShowCursor( true ); + m_pView->NotifyScrolled(); +} + + +// returns the last entry if position is just past the last entry +SvTreeListEntry* SvImpLBox::GetClickedEntry( const Point& rPoint ) const +{ + DBG_ASSERT( m_pView->GetModel(), "SvImpLBox::GetClickedEntry: how can this ever happen? Please tell me (frank.schoenheit@sun.com) how to reproduce!" ); + if ( !m_pView->GetModel() ) + // this is quite impossible. Nevertheless, stack traces from the crash reporter + // suggest it isn't. Okay, make it safe, and wait for somebody to reproduce it + // reliably :-\ ... + // #122359# / 2005-05-23 / frank.schoenheit@sun.com + return nullptr; + if( m_pView->GetEntryCount() == 0 || !m_pStartEntry || !m_pView->GetEntryHeight()) + return nullptr; + + sal_uInt16 nClickedEntry = static_cast<sal_uInt16>(rPoint.Y() / m_pView->GetEntryHeight() ); + sal_uInt16 nTemp = nClickedEntry; + SvTreeListEntry* pEntry = m_pView->NextVisible(m_pStartEntry, nTemp); + return pEntry; +} + + +// checks if the entry was hit "the right way" +// (Focusrect+ ContextBitmap at TreeListBox) + +bool SvImpLBox::EntryReallyHit(SvTreeListEntry* pEntry, const Point& rPosPixel, tools::Long nLine) +{ + bool bRet; + // we are not too exact when it comes to "special" entries + // (with CheckButtons etc.) + if( pEntry->ItemCount() >= 3 ) + return true; + + tools::Rectangle aRect( m_pView->GetFocusRect( pEntry, nLine )); + aRect.SetRight( GetOutputSize().Width() - m_pView->GetMapMode().GetOrigin().X() ); + + SvLBoxContextBmp* pBmp = static_cast<SvLBoxContextBmp*>(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp)); + aRect.AdjustLeft( -pBmp->GetWidth(m_pView,pEntry) ); + aRect.AdjustLeft( -4 ); // a little tolerance + + Point aPos( rPosPixel ); + aPos -= m_pView->GetMapMode().GetOrigin(); + bRet = aRect.Contains( aPos ); + return bRet; +} + + +// returns 0 if position is just past the last entry +SvTreeListEntry* SvImpLBox::GetEntry( const Point& rPoint ) const +{ + if( (m_pView->GetEntryCount() == 0) || !m_pStartEntry || + (rPoint.Y() > m_aOutputSize.Height()) + || !m_pView->GetEntryHeight()) + return nullptr; + + sal_uInt16 nClickedEntry = static_cast<sal_uInt16>(rPoint.Y() / m_pView->GetEntryHeight() ); + sal_uInt16 nTemp = nClickedEntry; + SvTreeListEntry* pEntry = m_pView->NextVisible(m_pStartEntry, nTemp); + if( nTemp != nClickedEntry ) + pEntry = nullptr; + return pEntry; +} + + +SvTreeListEntry* SvImpLBox::MakePointVisible(const Point& rPoint) +{ + if( !m_pCursor ) + return nullptr; + tools::Long nY = rPoint.Y(); + SvTreeListEntry* pEntry = nullptr; + tools::Long nMax = m_aOutputSize.Height(); + if( nY < 0 || nY >= nMax ) // aOutputSize.Height() ) + { + if( nY < 0 ) + pEntry = m_pView->PrevVisible(m_pCursor); + else + pEntry = m_pView->NextVisible(m_pCursor); + + if( pEntry && pEntry != m_pCursor ) + m_pView->SetEntryFocus( m_pCursor, false ); + + if( nY < 0 ) + KeyUp( false ); + else + KeyDown( false ); + } + else + { + pEntry = GetClickedEntry( rPoint ); + if( !pEntry ) + { + sal_uInt16 nSteps = 0xFFFF; + // TODO: LastVisible is not yet implemented! + pEntry = m_pView->NextVisible(m_pStartEntry, nSteps); + } + if( pEntry ) + { + if( pEntry != m_pCursor && + m_aSelEng.GetSelectionMode() == SelectionMode::Single + ) + m_pView->Select( m_pCursor, false ); + } + } + return pEntry; +} + +tools::Rectangle SvImpLBox::GetClipRegionRect() const +{ + Point aOrigin( m_pView->GetMapMode().GetOrigin() ); + aOrigin.setX( aOrigin.X() * -1 ); // conversion document coordinates + tools::Rectangle aClipRect( aOrigin, m_aOutputSize ); + aClipRect.AdjustBottom( 1 ); + return aClipRect; +} + + +void SvImpLBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + if (!m_pView->GetVisibleCount()) + return; + + m_nFlags |= LBoxFlags::InPaint; + + if (m_nFlags & LBoxFlags::Filling) + { + SvTreeListEntry* pFirst = m_pView->First(); + if (pFirst != m_pStartEntry) + { + ShowCursor(false); + m_pStartEntry = m_pView->First(); + m_aVerSBar->SetThumbPos( 0 ); + StopUserEvent(); + ShowCursor(true); + m_nCurUserEvent = Application::PostUserEvent(LINK(this, SvImpLBox, MyUserEvent), + reinterpret_cast<void*>(1)); + return; + } + } + + if (!m_pStartEntry) + { + m_pStartEntry = m_pView->First(); + } + + if (m_nNodeBmpTabDistance == NODE_BMP_TABDIST_NOTVALID) + SetNodeBmpTabDistance(); + + tools::Long nRectHeight = rRect.GetHeight(); + tools::Long nEntryHeight = m_pView->GetEntryHeight(); + + // calculate area for the entries we want to draw + sal_uInt16 nStartLine = static_cast<sal_uInt16>(rRect.Top() / nEntryHeight); + sal_uInt16 nCount = static_cast<sal_uInt16>(nRectHeight / nEntryHeight); + nCount += 2; // don't miss a row + + tools::Long nY = nStartLine * nEntryHeight; + SvTreeListEntry* pEntry = m_pStartEntry; + while (nStartLine && pEntry) + { + pEntry = m_pView->NextVisible(pEntry); + nStartLine--; + } + + if (!m_pCursor && !mbNoAutoCurEntry) + { + // do not select if multiselection or explicit set + bool bNotSelect = (m_aSelEng.GetSelectionMode() == SelectionMode::Multiple ) || ((m_nStyle & WB_NOINITIALSELECTION) == WB_NOINITIALSELECTION); + SetCursor(m_pStartEntry, bNotSelect); + } + + for(sal_uInt16 n=0; n< nCount && pEntry; n++) + { + /*long nMaxRight=*/ + m_pView->PaintEntry1(*pEntry, nY, rRenderContext ); + nY += nEntryHeight; + pEntry = m_pView->NextVisible(pEntry); + } + + if (m_nStyle & (WB_HASLINES | WB_HASLINESATROOT)) + DrawNet(rRenderContext); + + m_nFlags &= ~LBoxFlags::DeselectAll; + m_nFlags &= ~LBoxFlags::InPaint; +} + +void SvImpLBox::MakeVisible( SvTreeListEntry* pEntry, bool bMoveToTop ) +{ + if( !pEntry ) + return; + + bool bInView = IsEntryInView( pEntry ); + + if( bInView && (!bMoveToTop || m_pStartEntry == pEntry) ) + return; // is already visible + + if( m_pStartEntry || mbForceMakeVisible ) + m_nFlags &= ~LBoxFlags::Filling; + if( !bInView ) + { + if( !m_pView->IsEntryVisible(pEntry) ) // Parent(s) collapsed? + { + SvTreeListEntry* pParent = m_pView->GetParent( pEntry ); + while( pParent ) + { + if( !m_pView->IsExpanded( pParent ) ) + { + bool bRet = m_pView->Expand( pParent ); + DBG_ASSERT(bRet,"Not expanded!"); + } + pParent = m_pView->GetParent( pParent ); + } + // do the parent's children fit into the view or do we have to scroll? + if( IsEntryInView( pEntry ) && !bMoveToTop ) + return; // no need to scroll + } + } + + m_pStartEntry = pEntry; + ShowCursor( false ); + FillView(); + m_aVerSBar->SetThumbPos( static_cast<tools::Long>(m_pView->GetVisiblePos( m_pStartEntry )) ); + ShowCursor( true ); + m_pView->NotifyScrolled(); + m_pView->Invalidate(); +} + +void SvImpLBox::ScrollToAbsPos( tools::Long nPos ) +{ + if( m_pView->GetVisibleCount() == 0 ) + return; + tools::Long nLastEntryPos = m_pView->GetAbsPos( m_pView->Last() ); + + if( nPos < 0 ) + nPos = 0; + else if( nPos > nLastEntryPos ) + nPos = nLastEntryPos; + + SvTreeListEntry* pEntry = m_pView->GetEntryAtAbsPos( nPos ); + if( !pEntry || pEntry == m_pStartEntry ) + return; + + if( m_pStartEntry || mbForceMakeVisible ) + m_nFlags &= ~LBoxFlags::Filling; + + if( m_pView->IsEntryVisible(pEntry) ) + { + m_pStartEntry = pEntry; + ShowCursor( false ); + m_aVerSBar->SetThumbPos( nPos ); + ShowCursor( true ); + if (GetUpdateMode()) + m_pView->Invalidate(); + } +} + +void SvImpLBox::DrawNet(vcl::RenderContext& rRenderContext) +{ + if (m_pView->GetVisibleCount() < 2 && !m_pStartEntry->HasChildrenOnDemand() && + !m_pStartEntry->HasChildren()) + { + return; + } + + // for platforms that don't have nets, DrawNativeControl does nothing and returns true + // so that SvImpLBox::DrawNet() doesn't draw anything either + if (rRenderContext.IsNativeControlSupported(ControlType::ListNet, ControlPart::Entire)) + { + ImplControlValue aControlValue; + if (rRenderContext.DrawNativeControl(ControlType::ListNet, ControlPart::Entire, + tools::Rectangle(), ControlState::ENABLED, aControlValue, OUString())) + { + return; + } + } + + tools::Long nEntryHeight = m_pView->GetEntryHeight(); + tools::Long nEntryHeightDIV2 = nEntryHeight / 2; + if( nEntryHeightDIV2 && !(nEntryHeight & 0x0001)) + nEntryHeightDIV2--; + + SvTreeListEntry* pChild; + SvTreeListEntry* pEntry = m_pStartEntry; + + SvLBoxTab* pFirstDynamicTab = m_pView->GetFirstDynamicTab(); + while (m_pTree->GetDepth( pEntry ) > 0) + { + pEntry = m_pView->GetParent(pEntry); + } + sal_uInt16 nOffs = static_cast<sal_uInt16>(m_pView->GetVisiblePos(m_pStartEntry) - m_pView->GetVisiblePos(pEntry)); + tools::Long nY = 0; + nY -= (nOffs * nEntryHeight); + + DBG_ASSERT(pFirstDynamicTab,"No Tree!"); + + rRenderContext.Push(vcl::PushFlags::LINECOLOR); + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + Color aCol = rStyleSettings.GetFaceColor(); + + if (aCol.IsRGBEqual(rRenderContext.GetBackground().GetColor())) + aCol = rStyleSettings.GetShadowColor(); + rRenderContext.SetLineColor(aCol); + Point aPos1, aPos2; + sal_uInt16 nDistance; + sal_uLong nMax = m_nVisibleCount + nOffs + 1; + + const Image& rExpandedNodeBitmap = GetExpandedNodeBmp(); + + for (sal_uLong n=0; n< nMax && pEntry; n++) + { + if (m_pView->IsExpanded(pEntry)) + { + // draw vertical line + aPos1.setX(m_pView->GetTabPos(pEntry, pFirstDynamicTab) + m_nNodeBmpTabDistance + + rExpandedNodeBitmap.GetSizePixel().Width() / 2); + aPos1.setY(nY + nEntryHeight); + pChild = m_pView->FirstChild(pEntry); + assert(pChild && "Child?"); + pChild = pChild->LastSibling(); + nDistance = static_cast<sal_uInt16>(m_pView->GetVisiblePos(pChild) - + m_pView->GetVisiblePos(pEntry)); + aPos2 = aPos1; + aPos2.AdjustY((nDistance * nEntryHeight) - (nEntryHeightDIV2 + 2)); + rRenderContext.DrawLine(aPos1, aPos2); + } + // visible in control? + if (n >= nOffs && !m_pTree->IsAtRootDepth(pEntry)) + { + // draw horizontal line + aPos1.setX(m_pView->GetTabPos(m_pView->GetParent(pEntry), pFirstDynamicTab) + + m_nNodeBmpTabDistance + + rExpandedNodeBitmap.GetSizePixel().Width() / 2); + aPos1.setY(nY + nEntryHeightDIV2); + aPos2 = aPos1; + aPos2.AdjustX(m_pView->GetIndent() / 2); + rRenderContext.DrawLine(aPos1, aPos2); + } + nY += nEntryHeight; + pEntry = m_pView->NextVisible(pEntry); + } + + rRenderContext.Pop(); +} + +void SvImpLBox::PositionScrollBars( Size& rSize, sal_uInt16 nMask ) +{ + tools::Long nOverlap = 0; + + Size aVerSize( m_nVerSBarWidth, rSize.Height() ); + Size aHorSize( rSize.Width(), m_nHorSBarHeight ); + + if( nMask & 0x0001 ) + aHorSize.AdjustWidth( -m_nVerSBarWidth ); + if( nMask & 0x0002 ) + aVerSize.AdjustHeight( -m_nHorSBarHeight ); + + aVerSize.AdjustHeight(2 * nOverlap ); + Point aVerPos( rSize.Width() - aVerSize.Width() + nOverlap, -nOverlap ); + m_aVerSBar->SetPosSizePixel( aVerPos, aVerSize ); + + aHorSize.AdjustWidth(2 * nOverlap ); + Point aHorPos( -nOverlap, rSize.Height() - aHorSize.Height() + nOverlap ); + + m_aHorSBar->SetPosSizePixel( aHorPos, aHorSize ); + + if( nMask & 0x0001 ) + rSize.setWidth( aVerPos.X() ); + if( nMask & 0x0002 ) + rSize.setHeight( aHorPos.Y() ); + + if( (nMask & (0x0001|0x0002)) == (0x0001|0x0002) ) + m_aScrBarBox->Show(); + else + m_aScrBarBox->Hide(); +} + +void SvImpLBox::AdjustScrollBars( Size& rSize ) +{ + tools::Long nEntryHeight = m_pView->GetEntryHeight(); + if( !nEntryHeight ) + return; + + sal_uInt16 nResult = 0; + + Size aOSize( m_pView->Control::GetOutputSizePixel() ); + + const WinBits nWindowStyle = m_pView->GetStyle(); + bool bVerSBar = ( nWindowStyle & WB_VSCROLL ) != 0; + bool bHorBar = false; + tools::Long nMaxRight = aOSize.Width(); //GetOutputSize().Width(); + Point aOrigin( m_pView->GetMapMode().GetOrigin() ); + aOrigin.setX( aOrigin.X() * -1 ); + nMaxRight += aOrigin.X() - 1; + tools::Long nVis = m_nMostRight - aOrigin.X(); + if( (nWindowStyle & (WB_AUTOHSCROLL|WB_HSCROLL)) && + (nVis < m_nMostRight || nMaxRight < m_nMostRight) ) + { + bHorBar = true; + } + + // number of entries that are not collapsed + sal_uLong nTotalCount = m_pView->GetVisibleCount(); + + // number of entries visible within the view + m_nVisibleCount = o3tl::make_unsigned(aOSize.Height() / nEntryHeight); + + // do we need a vertical scrollbar? + if( bVerSBar || nTotalCount > m_nVisibleCount ) + { + nResult = 1; + nMaxRight -= m_nVerSBarWidth; + if( !bHorBar ) + { + if( (nWindowStyle & (WB_AUTOHSCROLL|WB_HSCROLL)) && + (nVis < m_nMostRight || nMaxRight < m_nMostRight) ) + bHorBar = true; + } + } + + // do we need a horizontal scrollbar? + if( bHorBar ) + { + nResult |= 0x0002; + // the number of entries visible within the view has to be recalculated + // because the horizontal scrollbar is now visible. + m_nVisibleCount = o3tl::make_unsigned(std::max<tools::Long>(0, aOSize.Height() - m_nHorSBarHeight) / nEntryHeight); + // we might actually need a vertical scrollbar now + if( !(nResult & 0x0001) && + ((nTotalCount > m_nVisibleCount) || bVerSBar) ) + { + nResult = 3; + } + } + + PositionScrollBars( aOSize, nResult ); + + // adapt Range, VisibleRange etc. + + // refresh output size, in case we have to scroll + tools::Rectangle aRect; + aRect.SetSize( aOSize ); + m_aSelEng.SetVisibleArea( aRect ); + + // vertical scrollbar + tools::Long nTemp = static_cast<tools::Long>(m_nVisibleCount); + nTemp--; + if( nTemp != m_aVerSBar->GetVisibleSize() ) + { + if( !m_bInVScrollHdl ) + { + m_aVerSBar->SetPageSize( nTemp - 1 ); + m_aVerSBar->SetVisibleSize( nTemp ); + } + else + { + m_nFlags |= LBoxFlags::EndScrollSetVisSize; + m_nNextVerVisSize = nTemp; + } + } + + // horizontal scrollbar + nTemp = m_aHorSBar->GetThumbPos(); + m_aHorSBar->SetVisibleSize( aOSize.Width() ); + tools::Long nNewThumbPos = m_aHorSBar->GetThumbPos(); + Range aRange( m_aHorSBar->GetRange() ); + if( aRange.Max() < m_nMostRight+25 ) + { + aRange.Max() = m_nMostRight+25; + m_aHorSBar->SetRange( aRange ); + } + + if( nTemp != nNewThumbPos ) + { + nTemp = nNewThumbPos - nTemp; + if( m_pView->IsEditingActive() ) + { + m_pView->EndEditing( true ); // Cancel + m_pView->PaintImmediately(); + } + m_pView->nFocusWidth = -1; + KeyLeftRight( nTemp ); + } + + if( nResult & 0x0001 ) + m_aVerSBar->Show(); + else + m_aVerSBar->Hide(); + + if( nResult & 0x0002 ) + m_aHorSBar->Show(); + else + { + m_aHorSBar->Hide(); + } + rSize = aOSize; +} + +void SvImpLBox::InitScrollBarBox() +{ + m_aScrBarBox->SetSizePixel( Size(m_nVerSBarWidth, m_nHorSBarHeight) ); + Size aSize( m_pView->Control::GetOutputSizePixel() ); + m_aScrBarBox->SetPosPixel( Point(aSize.Width()-m_nVerSBarWidth, aSize.Height()-m_nHorSBarHeight)); +} + +void SvImpLBox::Resize() +{ + m_aOutputSize = m_pView->Control::GetOutputSizePixel(); + if( m_aOutputSize.IsEmpty() ) + return; + m_nFlags |= LBoxFlags::InResize; + InitScrollBarBox(); + + if( m_pView->GetEntryHeight()) + { + AdjustScrollBars( m_aOutputSize ); + UpdateAll(false); + } + // HACK, as in floating and docked windows the scrollbars might not be drawn + // correctly/not be drawn at all after resizing! + if( m_aHorSBar->IsVisible()) + m_aHorSBar->Invalidate(); + if( m_aVerSBar->IsVisible()) + m_aVerSBar->Invalidate(); + m_nFlags &= ~LBoxFlags::InResize; +} + +void SvImpLBox::FillView() +{ + if( !m_pStartEntry ) + { + sal_uLong nVisibleViewCount = m_pView->GetVisibleCount(); + tools::Long nTempThumb = m_aVerSBar->GetThumbPos(); + if( nTempThumb < 0 ) + nTempThumb = 0; + else if( o3tl::make_unsigned(nTempThumb) >= nVisibleViewCount ) + nTempThumb = nVisibleViewCount == 0 ? 0 : nVisibleViewCount - 1; + m_pStartEntry = m_pView->GetEntryAtVisPos(nTempThumb); + } + if( !m_pStartEntry ) + return; + + sal_uInt16 nLast = static_cast<sal_uInt16>(m_pView->GetVisiblePos(m_pView->LastVisible())); + sal_uInt16 nThumb = static_cast<sal_uInt16>(m_pView->GetVisiblePos( m_pStartEntry )); + sal_uLong nCurDispEntries = nLast-nThumb+1; + if( nCurDispEntries >= m_nVisibleCount ) + return; + + ShowCursor( false ); + // fill window by moving the thumb up incrementally + bool bFound = false; + SvTreeListEntry* pTemp = m_pStartEntry; + while( nCurDispEntries < m_nVisibleCount && pTemp ) + { + pTemp = m_pView->PrevVisible(m_pStartEntry); + if( pTemp ) + { + nThumb--; + m_pStartEntry = pTemp; + nCurDispEntries++; + bFound = true; + } + } + if( bFound ) + { + m_aVerSBar->SetThumbPos( nThumb ); + ShowCursor( true ); // recalculate focus rectangle + m_pView->Invalidate(); + } +} + + +void SvImpLBox::ShowVerSBar() +{ + bool bVerBar = ( m_pView->GetStyle() & WB_VSCROLL ) != 0; + sal_uLong nVis = 0; + if( !bVerBar ) + nVis = m_pView->GetVisibleCount(); + if( bVerBar || (m_nVisibleCount && nVis > static_cast<sal_uLong>(m_nVisibleCount-1)) ) + { + if( !m_aVerSBar->IsVisible() ) + { + m_pView->nFocusWidth = -1; + AdjustScrollBars( m_aOutputSize ); + if( GetUpdateMode() ) + m_aVerSBar->Invalidate(); + } + } + else + { + if( m_aVerSBar->IsVisible() ) + { + m_pView->nFocusWidth = -1; + AdjustScrollBars( m_aOutputSize ); + } + } + + tools::Long nMaxRight = GetOutputSize().Width(); + Point aPos( m_pView->GetMapMode().GetOrigin() ); + aPos.setX( aPos.X() * -1 ); // convert document coordinates + nMaxRight = nMaxRight + aPos.X() - 1; + if( nMaxRight < m_nMostRight ) + { + if( !m_aHorSBar->IsVisible() ) + { + m_pView->nFocusWidth = -1; + AdjustScrollBars( m_aOutputSize ); + if( GetUpdateMode() ) + m_aHorSBar->Invalidate(); + } + else + { + Range aRange( m_aHorSBar->GetRange() ); + if( aRange.Max() < m_nMostRight+25 ) + { + aRange.Max() = m_nMostRight+25; + m_aHorSBar->SetRange( aRange ); + } + else + { + m_pView->nFocusWidth = -1; + AdjustScrollBars( m_aOutputSize ); + } + } + } + else + { + if( m_aHorSBar->IsVisible() ) + { + m_pView->nFocusWidth = -1; + AdjustScrollBars( m_aOutputSize ); + } + } +} + + +void SvImpLBox::SyncVerThumb() +{ + if( m_pStartEntry ) + { + tools::Long nEntryPos = m_pView->GetVisiblePos( m_pStartEntry ); + m_aVerSBar->SetThumbPos( nEntryPos ); + } + else + m_aVerSBar->SetThumbPos( 0 ); +} + +bool SvImpLBox::IsEntryInView( SvTreeListEntry* pEntry ) const +{ + // parent collapsed + if( !m_pView->IsEntryVisible(pEntry) ) + return false; + tools::Long nY = GetEntryLine( pEntry ); + if( nY < 0 ) + return false; + tools::Long nMax = m_nVisibleCount * m_pView->GetEntryHeight(); + return nY < nMax; +} + + +tools::Long SvImpLBox::GetEntryLine(const SvTreeListEntry* pEntry) const +{ + if(!m_pStartEntry ) + return -1; // invisible position + + tools::Long nFirstVisPos = m_pView->GetVisiblePos( m_pStartEntry ); + tools::Long nEntryVisPos = m_pView->GetVisiblePos( pEntry ); + nFirstVisPos = nEntryVisPos - nFirstVisPos; + nFirstVisPos *= m_pView->GetEntryHeight(); + return nFirstVisPos; +} + +void SvImpLBox::SetEntryHeight() +{ + SetNodeBmpWidth( GetExpandedNodeBmp() ); + SetNodeBmpWidth( GetCollapsedNodeBmp() ); + if(!m_pView->HasViewData()) // are we within the Clear? + { + Size aSize = m_pView->Control::GetOutputSizePixel(); + AdjustScrollBars( aSize ); + } + else + { + Resize(); + if( GetUpdateMode() ) + m_pView->Invalidate(); + } +} + + +// *********************************************************************** +// Callback Functions +// *********************************************************************** + +void SvImpLBox::EntryExpanded( SvTreeListEntry* pEntry ) +{ + // SelAllDestrAnch( false, true ); //DeselectAll(); + if( !GetUpdateMode() ) + return; + + ShowCursor( false ); + tools::Long nY = GetEntryLine( pEntry ); + if( IsLineVisible(nY) ) + { + InvalidateEntriesFrom( nY ); + FindMostRight( pEntry ); + } + m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1 ) ); + // if we expanded before the thumb, the thumb's position has to be + // corrected + SyncVerThumb(); + ShowVerSBar(); + ShowCursor( true ); +} + +void SvImpLBox::EntryCollapsed( SvTreeListEntry* pEntry ) +{ + if( !m_pView->IsEntryVisible( pEntry ) ) + return; + + ShowCursor( false ); + + if( !m_pMostRightEntry || m_pTree->IsChild( pEntry,m_pMostRightEntry ) ) + { + FindMostRight(); + } + + if( m_pStartEntry ) + { + tools::Long nOldThumbPos = m_aVerSBar->GetThumbPos(); + sal_uLong nVisList = m_pView->GetVisibleCount(); + m_aVerSBar->SetRange( Range(0, nVisList-1) ); + tools::Long nNewThumbPos = m_aVerSBar->GetThumbPos(); + if( nNewThumbPos != nOldThumbPos ) + { + m_pStartEntry = m_pView->First(); + sal_uInt16 nDistance = static_cast<sal_uInt16>(nNewThumbPos); + if( nDistance ) + m_pStartEntry = m_pView->NextVisible(m_pStartEntry, nDistance); + if( GetUpdateMode() ) + m_pView->Invalidate(); + } + else + SyncVerThumb(); + ShowVerSBar(); + } + // has the cursor been collapsed? + if( m_pTree->IsChild( pEntry, m_pCursor ) ) + SetCursor( pEntry ); + if( GetUpdateMode() ) + ShowVerSBar(); + ShowCursor( true ); + if( GetUpdateMode() && m_pCursor ) + m_pView->Select( m_pCursor ); +} + +void SvImpLBox::CollapsingEntry( SvTreeListEntry* pEntry ) +{ + if( !m_pView->IsEntryVisible( pEntry ) || !m_pStartEntry ) + return; + + SelAllDestrAnch( false ); // deselect all + + // is the collapsed cursor visible? + tools::Long nY = GetEntryLine( pEntry ); + if( IsLineVisible(nY) ) + { + if( GetUpdateMode() ) + InvalidateEntriesFrom( nY ); + } + else + { + if( m_pTree->IsChild(pEntry, m_pStartEntry) ) + { + m_pStartEntry = pEntry; + if( GetUpdateMode() ) + m_pView->Invalidate(); + } + } +} + + +void SvImpLBox::SetNodeBmpWidth( const Image& rBmp ) +{ + const Size aSize( rBmp.GetSizePixel() ); + m_nNodeBmpWidth = aSize.Width(); +} + +void SvImpLBox::SetNodeBmpTabDistance() +{ + m_nNodeBmpTabDistance = -m_pView->GetIndent(); + if( m_pView->nContextBmpWidthMax ) + { + // only if the first dynamic tab is centered (we currently assume that) + Size aSize = GetExpandedNodeBmp().GetSizePixel(); + m_nNodeBmpTabDistance -= aSize.Width() / 2; + } +} + + +// corrects the cursor when using SingleSelection + +void SvImpLBox::EntrySelected( SvTreeListEntry* pEntry, bool bSelect ) +{ + if( m_nFlags & LBoxFlags::IgnoreSelect ) + return; + + m_nFlags &= ~LBoxFlags::DeselectAll; + if( bSelect && + m_aSelEng.GetSelectionMode() == SelectionMode::Single && + pEntry != m_pCursor ) + { + SetCursor( pEntry ); + DBG_ASSERT(m_pView->GetSelectionCount()==1,"selection count?"); + } + + if( GetUpdateMode() && m_pView->IsEntryVisible(pEntry) ) + { + tools::Long nY = GetEntryLine( pEntry ); + if( IsLineVisible( nY ) ) + { + ShowCursor(false); + InvalidateEntry(pEntry); + ShowCursor(true); + } + } +} + + +void SvImpLBox::RemovingEntry( SvTreeListEntry* pEntry ) +{ + CallEventListeners( VclEventId::ListboxItemRemoved , pEntry ); + + DestroyAnchor(); + + if( !m_pView->IsEntryVisible( pEntry ) ) + { + // if parent is collapsed => bye! + m_nFlags |= LBoxFlags::RemovedEntryInvisible; + return; + } + + if( pEntry == m_pMostRightEntry || ( + pEntry->HasChildren() && m_pView->IsExpanded(pEntry) && + m_pTree->IsChild(pEntry, m_pMostRightEntry))) + { + m_nFlags |= LBoxFlags::RemovedRecalcMostRight; + } + + SvTreeListEntry* pOldStartEntry = m_pStartEntry; + + SvTreeListEntry* pParent = m_pView->GetModel()->GetParent(pEntry); + + if (pParent && m_pView->GetModel()->GetChildList(pParent).size() == 1) + { + DBG_ASSERT( m_pView->IsExpanded( pParent ), "Parent not expanded"); + pParent->SetFlags( pParent->GetFlags() | SvTLEntryFlags::NO_NODEBMP); + InvalidateEntry( pParent ); + } + + if( m_pCursor && m_pTree->IsChild( pEntry, m_pCursor) ) + m_pCursor = pEntry; + if( m_pStartEntry && m_pTree->IsChild(pEntry,m_pStartEntry) ) + m_pStartEntry = pEntry; + + SvTreeListEntry* pTemp; + if( m_pCursor && m_pCursor == pEntry ) + { + if( m_bSimpleTravel ) + m_pView->Select( m_pCursor, false ); + ShowCursor( false ); // focus rectangle gone + // NextSibling, because we also delete the children of the cursor + pTemp = m_pCursor->NextSibling(); + if( !pTemp ) + pTemp = m_pView->PrevVisible(m_pCursor); + + SetCursor( pTemp, true ); + } + if( m_pStartEntry && m_pStartEntry == pEntry ) + { + pTemp = m_pStartEntry->NextSibling(); + if( !pTemp ) + pTemp = m_pView->PrevVisible(m_pStartEntry); + m_pStartEntry = pTemp; + } + if( GetUpdateMode()) + { + // if it is the last one, we have to invalidate it, so the lines are + // drawn correctly (in this case they're deleted) + if( m_pStartEntry && (m_pStartEntry != pOldStartEntry || pEntry == m_pView->GetModel()->Last()) ) + { + m_aVerSBar->SetThumbPos( m_pView->GetVisiblePos( m_pStartEntry )); + m_pView->Invalidate( GetVisibleArea() ); + } + else + InvalidateEntriesFrom( GetEntryLine( pEntry ) ); + } +} + +void SvImpLBox::EntryRemoved() +{ + if( m_nFlags & LBoxFlags::RemovedEntryInvisible ) + { + m_nFlags &= ~LBoxFlags::RemovedEntryInvisible; + return; + } + if( !m_pStartEntry ) + m_pStartEntry = m_pTree->First(); + if( !m_pCursor ) + SetCursor( m_pStartEntry, true ); + + if( m_pCursor && (m_bSimpleTravel || !m_pView->GetSelectionCount() )) + m_pView->Select( m_pCursor ); + + if( GetUpdateMode()) + { + if( m_nFlags & LBoxFlags::RemovedRecalcMostRight ) + FindMostRight(); + m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1 ) ); + FillView(); + if( m_pStartEntry ) + // if something above the thumb was deleted + m_aVerSBar->SetThumbPos( m_pView->GetVisiblePos( m_pStartEntry) ); + + ShowVerSBar(); + if( m_pCursor && m_pView->HasFocus() && !m_pView->IsSelected(m_pCursor) ) + { + if( m_pView->GetSelectionCount() ) + { + // is a neighboring entry selected? + SvTreeListEntry* pNextCursor = m_pView->PrevVisible( m_pCursor ); + if( !pNextCursor || !m_pView->IsSelected( pNextCursor )) + pNextCursor = m_pView->NextVisible( m_pCursor ); + if( !pNextCursor || !m_pView->IsSelected( pNextCursor )) + // no neighbor selected: use first selected + pNextCursor = m_pView->FirstSelected(); + SetCursor( pNextCursor ); + MakeVisible( m_pCursor ); + } + else + m_pView->Select( m_pCursor ); + } + ShowCursor( true ); + } + m_nFlags &= ~LBoxFlags::RemovedRecalcMostRight; +} + + +void SvImpLBox::MovingEntry( SvTreeListEntry* pEntry ) +{ + bool bDeselAll(m_nFlags & LBoxFlags::DeselectAll); + SelAllDestrAnch( false ); // DeselectAll(); + if( !bDeselAll ) + m_nFlags &= ~LBoxFlags::DeselectAll; + + if( pEntry == m_pCursor ) + ShowCursor( false ); + if( IsEntryInView( pEntry ) ) + m_pView->Invalidate(); + if( pEntry != m_pStartEntry ) + return; + + SvTreeListEntry* pNew = nullptr; + if( !pEntry->HasChildren() ) + { + pNew = m_pView->NextVisible(m_pStartEntry); + if( !pNew ) + pNew = m_pView->PrevVisible(m_pStartEntry); + } + else + { + pNew = pEntry->NextSibling(); + if( !pNew ) + pNew = pEntry->PrevSibling(); + } + m_pStartEntry = pNew; +} + +void SvImpLBox::EntryMoved( SvTreeListEntry* pEntry ) +{ + UpdateContextBmpWidthVectorFromMovedEntry( pEntry ); + + if ( !m_pStartEntry ) + // this might happen if the only entry in the view is moved to its very same position + // #i97346# + m_pStartEntry = m_pView->First(); + + m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1)); + sal_uInt16 nFirstPos = static_cast<sal_uInt16>(m_pTree->GetAbsPos( m_pStartEntry )); + sal_uInt16 nNewPos = static_cast<sal_uInt16>(m_pTree->GetAbsPos( pEntry )); + FindMostRight(); + if( nNewPos < nFirstPos ) // HACK! + m_pStartEntry = pEntry; + SyncVerThumb(); + if( pEntry == m_pCursor ) + { + if( m_pView->IsEntryVisible( m_pCursor ) ) + ShowCursor( true ); + else + { + SvTreeListEntry* pParent = pEntry; + do { + pParent = m_pTree->GetParent( pParent ); + } + while( !m_pView->IsEntryVisible( pParent ) ); + SetCursor( pParent ); + } + } + if( IsEntryInView( pEntry ) ) + m_pView->Invalidate(); +} + + +void SvImpLBox::EntryInserted( SvTreeListEntry* pEntry ) +{ + if( !GetUpdateMode() ) + return; + + SvTreeListEntry* pParent = m_pTree->GetParent(pEntry); + if (pParent && m_pTree->GetChildList(pParent).size() == 1) + // draw plus sign + m_pTree->InvalidateEntry( pParent ); + + if( !m_pView->IsEntryVisible( pEntry ) ) + return; + bool bDeselAll(m_nFlags & LBoxFlags::DeselectAll); + if( bDeselAll ) + SelAllDestrAnch( false ); + else + DestroyAnchor(); + // nFlags &= (~LBoxFlags::DeselectAll); +// ShowCursor( false ); // if cursor is moved lower + tools::Long nY = GetEntryLine( pEntry ); + bool bEntryVisible = IsLineVisible( nY ); + if( bEntryVisible ) + { + ShowCursor( false ); // if cursor is moved lower + nY -= m_pView->GetEntryHeight(); // because of lines + InvalidateEntriesFrom( nY ); + } + else if( m_pStartEntry && nY < GetEntryLine(m_pStartEntry) ) + { + // Check if the view is filled completely. If not, then adjust + // pStartEntry and the Cursor (automatic scrolling). + sal_uInt16 nLast = static_cast<sal_uInt16>(m_pView->GetVisiblePos(m_pView->LastVisible())); + sal_uInt16 nThumb = static_cast<sal_uInt16>(m_pView->GetVisiblePos( m_pStartEntry )); + sal_uInt16 nCurDispEntries = nLast-nThumb+1; + if( nCurDispEntries < m_nVisibleCount ) + { + // set at the next paint event + m_pStartEntry = nullptr; + SetCursor( nullptr ); + m_pView->Invalidate(); + } + } + else if( !m_pStartEntry ) + m_pView->Invalidate(); + + SetMostRight( pEntry ); + m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1)); + SyncVerThumb(); // if something was inserted before the thumb + ShowVerSBar(); + ShowCursor( true ); + if( m_pStartEntry != m_pView->First() && (m_nFlags & LBoxFlags::Filling) ) + m_pView->PaintImmediately(); +} + + +// ******************************************************************** +// Event handler +// ******************************************************************** + + +// ****** Control the control animation + +bool SvImpLBox::ButtonDownCheckCtrl(const MouseEvent& rMEvt, SvTreeListEntry* pEntry) +{ + SvLBoxItem* pItem = m_pView->GetItem(pEntry,rMEvt.GetPosPixel().X(),&m_pActiveTab); + if (pItem && pItem->GetType() == SvLBoxItemType::Button) + { + m_pActiveButton = static_cast<SvLBoxButton*>(pItem); + m_pActiveEntry = pEntry; + if( m_pCursor == m_pActiveEntry ) + m_pView->HideFocus(); + m_pView->CaptureMouse(); + m_pActiveButton->SetStateHilighted( true ); + InvalidateEntry(m_pActiveEntry); + return true; + } + else + m_pActiveButton = nullptr; + return false; +} + +bool SvImpLBox::MouseMoveCheckCtrl(const MouseEvent& rMEvt, SvTreeListEntry const * pEntry) +{ + if( m_pActiveButton ) + { + tools::Long nMouseX = rMEvt.GetPosPixel().X(); + if( pEntry == m_pActiveEntry && + m_pView->GetItem(m_pActiveEntry, nMouseX) == m_pActiveButton ) + { + if( !m_pActiveButton->IsStateHilighted() ) + { + m_pActiveButton->SetStateHilighted(true ); + InvalidateEntry(m_pActiveEntry); + } + } + else + { + if( m_pActiveButton->IsStateHilighted() ) + { + m_pActiveButton->SetStateHilighted(false ); + InvalidateEntry(m_pActiveEntry); + } + } + return true; + } + return false; +} + +bool SvImpLBox::ButtonUpCheckCtrl( const MouseEvent& rMEvt ) +{ + if( m_pActiveButton ) + { + m_pView->ReleaseMouse(); + SvTreeListEntry* pEntry = GetClickedEntry( rMEvt.GetPosPixel() ); + m_pActiveButton->SetStateHilighted( false ); + tools::Long nMouseX = rMEvt.GetPosPixel().X(); + if (pEntry == m_pActiveEntry && m_pView->GetItem(m_pActiveEntry, nMouseX) == m_pActiveButton) + m_pActiveButton->ClickHdl(m_pActiveEntry); + InvalidateEntry(m_pActiveEntry); + if (m_pCursor == m_pActiveEntry) + ShowCursor(true); + m_pActiveButton = nullptr; + m_pActiveEntry = nullptr; + m_pActiveTab = nullptr; + return true; + } + return false; +} + +// ******* Control plus/minus button for expanding/collapsing + +// false == no expand/collapse button hit +bool SvImpLBox::IsNodeButton( const Point& rPosPixel, const SvTreeListEntry* pEntry ) const +{ + if( !pEntry->HasChildren() && !pEntry->HasChildrenOnDemand() ) + return false; + + SvLBoxTab* pFirstDynamicTab = m_pView->GetFirstDynamicTab(); + if( !pFirstDynamicTab ) + return false; + + tools::Long nMouseX = rPosPixel.X(); + // convert to document coordinates + Point aOrigin( m_pView->GetMapMode().GetOrigin() ); + nMouseX -= aOrigin.X(); + + tools::Long nX = m_pView->GetTabPos( pEntry, pFirstDynamicTab); + nX += m_nNodeBmpTabDistance; + if( nMouseX < nX ) + return false; + nX += m_nNodeBmpWidth; + return nMouseX <= nX; +} + +// false == hit no node button +bool SvImpLBox::ButtonDownCheckExpand( const MouseEvent& rMEvt, SvTreeListEntry* pEntry ) +{ + bool bRet = false; + + if ( m_pView->IsEditingActive() && pEntry == m_pView->pEdEntry ) + // inplace editing -> nothing to do + bRet = true; + else if ( IsNodeButton( rMEvt.GetPosPixel(), pEntry ) ) + { + if ( m_pView->IsExpanded( pEntry ) ) + { + m_pView->EndEditing( true ); + m_pView->Collapse( pEntry ); + } + else + { + // you can expand an entry, which is in editing + m_pView->Expand( pEntry ); + } + bRet = true; + } + + return bRet; +} + +void SvImpLBox::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( !rMEvt.IsLeft() && !rMEvt.IsRight()) + return; + + m_aEditIdle.Stop(); + Point aPos( rMEvt.GetPosPixel()); + + if( aPos.X() > m_aOutputSize.Width() || aPos.Y() > m_aOutputSize.Height() ) + return; + + if( !m_pCursor ) + m_pCursor = m_pStartEntry; + m_nFlags &= ~LBoxFlags::Filling; + m_pView->GrabFocus(); + //fdo#82270 Grabbing focus can invalidate the entries, re-fetch + SvTreeListEntry* pEntry = GetEntry(aPos); + // the entry can still be invalid! + if( !pEntry || !m_pView->GetViewData( pEntry )) + return; + + tools::Long nY = GetEntryLine( pEntry ); + // Node-Button? + if( ButtonDownCheckExpand( rMEvt, pEntry ) ) + return; + + if( !EntryReallyHit(pEntry,aPos,nY)) + return; + + SvLBoxItem* pXItem = m_pView->GetItem( pEntry, aPos.X() ); + if( pXItem ) + { + SvLBoxTab* pXTab = m_pView->GetTab( pEntry, pXItem ); + if ( !rMEvt.IsMod1() && !rMEvt.IsMod2() && rMEvt.IsLeft() && pXTab->IsEditable() + && pEntry == m_pView->FirstSelected() && nullptr == m_pView->NextSelected( pEntry ) ) + // #i8234# FirstSelected() and NextSelected() ensures, that inplace editing is only triggered, when only one entry is selected + m_nFlags |= LBoxFlags::StartEditTimer; + if ( !m_pView->IsSelected( pEntry ) ) + m_nFlags &= ~LBoxFlags::StartEditTimer; + } + + + if( (rMEvt.GetClicks() % 2) == 0) + { + m_nFlags &= ~LBoxFlags::StartEditTimer; + m_pView->pHdlEntry = pEntry; + if( !m_pView->DoubleClickHdl() ) + { + // Handler signals nothing to be done anymore, bail out, 'this' may + // even be dead and destroyed. + return; + } + else + { + // if the entry was deleted within the handler + pEntry = GetClickedEntry( aPos ); + if( !pEntry ) + return; + if( pEntry != m_pView->pHdlEntry ) + { + // select anew & bye + if( !m_bSimpleTravel && !m_aSelEng.IsAlwaysAdding()) + SelAllDestrAnch( false ); // DeselectAll(); + SetCursor( pEntry ); + + return; + } + if( pEntry->HasChildren() || pEntry->HasChildrenOnDemand() ) + { + if( m_pView->IsExpanded(pEntry) ) + m_pView->Collapse( pEntry ); + else + m_pView->Expand( pEntry ); + if( pEntry == m_pCursor ) // only if Entryitem was clicked + // (Nodebutton is not an Entryitem!) + m_pView->Select( m_pCursor ); + return; + } + } + } + else + { + // CheckButton? (TreeListBox: Check + Info) + if( ButtonDownCheckCtrl(rMEvt, pEntry) ) + return; + // Inplace-Editing? + } + if ( m_aSelEng.GetSelectionMode() != SelectionMode::NONE + && !rMEvt.IsRight() ) // tdf#128824 + m_aSelEng.SelMouseButtonDown( rMEvt ); +} + +void SvImpLBox::MouseButtonUp( const MouseEvent& rMEvt) +{ + if ( !ButtonUpCheckCtrl( rMEvt ) && ( m_aSelEng.GetSelectionMode() != SelectionMode::NONE ) ) + m_aSelEng.SelMouseButtonUp( rMEvt ); + if( m_nFlags & LBoxFlags::StartEditTimer ) + { + m_nFlags &= ~LBoxFlags::StartEditTimer; + m_aEditClickPos = rMEvt.GetPosPixel(); + m_aEditIdle.Start(); + } + + if (m_pView->mbActivateOnSingleClick) + { + Point aPos(rMEvt.GetPosPixel()); + SvTreeListEntry* pEntry = GetEntry(aPos); + // tdf#143245 ActivateOnSingleClick only + // if the 'up' is at the active entry + // typically selected by the 'down' + if (!pEntry || pEntry != m_pCursor) + return; + m_pView->DoubleClickHdl(); + } +} + +void SvImpLBox::MouseMove( const MouseEvent& rMEvt) +{ + Point aPos = rMEvt.GetPosPixel(); + SvTreeListEntry* pEntry = GetClickedEntry(aPos); + if ( MouseMoveCheckCtrl( rMEvt, pEntry ) || ( m_aSelEng.GetSelectionMode() == SelectionMode::NONE ) ) + return; + + m_aSelEng.SelMouseMove(rMEvt); + if (m_pView->mbHoverSelection) + { + if (aPos.X() < 0 || aPos.Y() < 0 || aPos.X() > m_aOutputSize.Width() || aPos.Y() > m_aOutputSize.Height()) + pEntry = nullptr; + else + pEntry = GetEntry(aPos); + if (!pEntry) + m_pView->SelectAll(false); + else if (!m_pView->IsSelected(pEntry) && IsSelectable(pEntry)) + { + m_pView->mbSelectingByHover = true; + m_pView->Select(pEntry); + m_pView->mbSelectingByHover = false; + } + } +} + +void SvImpLBox::ExpandAll() +{ + sal_uInt16 nRefDepth = m_pTree->GetDepth(m_pCursor); + SvTreeListEntry* pCur = m_pTree->Next(m_pCursor); + while (pCur && m_pTree->GetDepth(pCur) > nRefDepth) + { + if (pCur->HasChildren() && !m_pView->IsExpanded(pCur)) + m_pView->Expand(pCur); + pCur = m_pTree->Next(pCur); + } +} + +void SvImpLBox::CollapseTo(SvTreeListEntry* pParentToCollapse) +{ + // collapse all parents until we get to the given parent to collapse + if (!pParentToCollapse) + return; + + sal_uInt16 nRefDepth; + // special case explorer: if the root only has a single + // entry, don't collapse the root entry + if (m_pTree->GetChildList(nullptr).size() < 2) + { + nRefDepth = 1; + pParentToCollapse = m_pCursor; + while (m_pTree->GetParent(pParentToCollapse) + && m_pTree->GetDepth(m_pTree->GetParent(pParentToCollapse)) > 0) + { + pParentToCollapse = m_pTree->GetParent(pParentToCollapse); + } + } + else + nRefDepth = m_pTree->GetDepth(pParentToCollapse); + + if (m_pView->IsExpanded(pParentToCollapse)) + m_pView->Collapse(pParentToCollapse); + SvTreeListEntry* pCur = m_pTree->Next(pParentToCollapse); + while (pCur && m_pTree->GetDepth(pCur) > nRefDepth) + { + if (pCur->HasChildren() && m_pView->IsExpanded(pCur)) + m_pView->Collapse(pCur); + pCur = m_pTree->Next(pCur); + } +} + +bool SvImpLBox::KeyInput( const KeyEvent& rKEvt) +{ + m_aEditIdle.Stop(); + const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); + + if( rKeyCode.IsMod2() ) + return false; // don't evaluate Alt key + + m_nFlags &= ~LBoxFlags::Filling; + + if( !m_pCursor ) + m_pCursor = m_pStartEntry; + if( !m_pCursor ) + return false; + + bool bKeyUsed = true; + + sal_uInt16 nDelta = static_cast<sal_uInt16>(m_aVerSBar->GetPageSize()); + sal_uInt16 aCode = rKeyCode.GetCode(); + + bool bShift = rKeyCode.IsShift(); + bool bMod1 = rKeyCode.IsMod1(); + + SvTreeListEntry* pNewCursor; + + switch( aCode ) + { + case KEY_UP: + if( !IsEntryInView( m_pCursor ) ) + MakeVisible( m_pCursor ); + + pNewCursor = m_pCursor; + do + { + pNewCursor = m_pView->PrevVisible(pNewCursor); + } while( pNewCursor && !IsSelectable(pNewCursor) ); + + // if there is no next entry, take the current one + // this ensures that in case of _one_ entry in the list, this entry is selected when pressing + // the cursor key + if (!pNewCursor) + pNewCursor = m_pCursor; + + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on + if( !IsEntryInView( pNewCursor ) ) + KeyUp( false ); + break; + + case KEY_DOWN: + if( !IsEntryInView( m_pCursor ) ) + MakeVisible( m_pCursor ); + + pNewCursor = m_pCursor; + do + { + pNewCursor = m_pView->NextVisible(pNewCursor); + } while( pNewCursor && !IsSelectable(pNewCursor) ); + + // if there is no next entry, take the current one + // this ensures that in case of _one_ entry in the list, this entry is selected when pressing + // the cursor key + // 06.09.20001 - 83416 - frank.schoenheit@sun.com + if ( !pNewCursor && m_pCursor ) + pNewCursor = m_pCursor; + + if( pNewCursor ) + { + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + if( IsEntryInView( pNewCursor ) ) + SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on + else + { + if( m_pCursor ) + m_pView->Select( m_pCursor, false ); + KeyDown( false ); + SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on + } + } + else + KeyDown( false ); // because scrollbar range might still + // allow scrolling + break; + + case KEY_RIGHT: + { + if( m_bSubLstOpLR ) + { + // only try to expand if sublist is expandable, + // otherwise ignore the key press + if( IsExpandable() && !m_pView->IsExpanded( m_pCursor ) ) + m_pView->Expand( m_pCursor ); + } + else if (m_aHorSBar->IsVisible()) + { + tools::Long nThumb = m_aHorSBar->GetThumbPos(); + nThumb += m_aHorSBar->GetLineSize(); + tools::Long nOldThumb = m_aHorSBar->GetThumbPos(); + m_aHorSBar->SetThumbPos( nThumb ); + nThumb = nOldThumb; + nThumb -= m_aHorSBar->GetThumbPos(); + nThumb *= -1; + if( nThumb ) + { + KeyLeftRight( nThumb ); + } + } + else + bKeyUsed = false; + break; + } + + case KEY_LEFT: + { + if (m_aHorSBar->IsVisible()) + { + tools::Long nThumb = m_aHorSBar->GetThumbPos(); + nThumb -= m_aHorSBar->GetLineSize(); + tools::Long nOldThumb = m_aHorSBar->GetThumbPos(); + m_aHorSBar->SetThumbPos( nThumb ); + nThumb = nOldThumb; + nThumb -= m_aHorSBar->GetThumbPos(); + if( nThumb ) + { + KeyLeftRight( -nThumb ); + } + else if( m_bSubLstOpLR ) + { + if( IsExpandable() && m_pView->IsExpanded( m_pCursor ) ) + m_pView->Collapse( m_pCursor ); + else + { + pNewCursor = m_pView->GetParent( m_pCursor ); + if( pNewCursor ) + SetCursor( pNewCursor ); + } + } + } + else if( m_bSubLstOpLR ) + { + if( IsExpandable() && m_pView->IsExpanded( m_pCursor ) ) + m_pView->Collapse( m_pCursor ); + else + { + pNewCursor = m_pView->GetParent( m_pCursor ); + if( pNewCursor ) + SetCursor( pNewCursor ); + } + } + else + bKeyUsed = false; + break; + } + + case KEY_PAGEUP: + if( !bMod1 ) + { + pNewCursor = m_pView->PrevVisible(m_pCursor, nDelta); + + while( nDelta && pNewCursor && !IsSelectable(pNewCursor) ) + { + pNewCursor = m_pView->NextVisible(pNewCursor); + nDelta--; + } + + if( nDelta ) + { + DBG_ASSERT(pNewCursor && pNewCursor!=m_pCursor, "Cursor?"); + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + if( IsEntryInView( pNewCursor ) ) + SetCursor( pNewCursor ); + else + { + SetCursor( pNewCursor ); + KeyUp( true ); + } + } + } + else + bKeyUsed = false; + break; + + case KEY_PAGEDOWN: + if( !bMod1 ) + { + pNewCursor= m_pView->NextVisible(m_pCursor, nDelta); + + while( nDelta && pNewCursor && !IsSelectable(pNewCursor) ) + { + pNewCursor = m_pView->PrevVisible(pNewCursor); + nDelta--; + } + + if( nDelta && pNewCursor ) + { + DBG_ASSERT(pNewCursor && pNewCursor!=m_pCursor, "Cursor?"); + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + if( IsEntryInView( pNewCursor ) ) + SetCursor( pNewCursor ); + else + { + SetCursor( pNewCursor ); + KeyDown( true ); + } + } + else + KeyDown( false ); // see also: KEY_DOWN + } + else + bKeyUsed = false; + break; + + case KEY_SPACE: + if ( m_pView->GetSelectionMode() != SelectionMode::NONE ) + { + if ( bMod1 ) + { + if ( m_pView->GetSelectionMode() == SelectionMode::Multiple && !bShift ) + // toggle selection + m_pView->Select( m_pCursor, !m_pView->IsSelected( m_pCursor ) ); + } + else if ( !bShift /*&& !bMod1*/ ) + { + if ( m_aSelEng.IsAddMode() ) + { + // toggle selection + m_pView->Select( m_pCursor, !m_pView->IsSelected( m_pCursor ) ); + } + else if ( !m_pView->IsSelected( m_pCursor ) ) + { + SelAllDestrAnch( false ); + m_pView->Select( m_pCursor ); + } + else + bKeyUsed = false; + } + else + bKeyUsed = false; + } + else + bKeyUsed = false; + break; + + case KEY_RETURN: + bKeyUsed = !m_pView->DoubleClickHdl(); + break; + + case KEY_F2: + if( !bShift && !bMod1 ) + { + m_aEditClickPos = Point( -1, -1 ); + EditTimerCall( nullptr ); + } + else + bKeyUsed = false; + break; + + case KEY_F8: + if( bShift && m_pView->GetSelectionMode()==SelectionMode::Multiple && + !(m_nStyle & WB_SIMPLEMODE)) + { + if( m_aSelEng.IsAlwaysAdding() ) + m_aSelEng.AddAlways( false ); + else + m_aSelEng.AddAlways( true ); + } + else + bKeyUsed = false; + break; + + case KEY_ADD: + if (!m_pView->IsExpanded(m_pCursor)) + m_pView->Expand(m_pCursor); + if (bMod1) + ExpandAll(); + break; + + case KEY_A: + if( bMod1 ) + SelAllDestrAnch( true ); + else + bKeyUsed = false; + break; + + case KEY_SUBTRACT: + if (m_pView->IsExpanded(m_pCursor)) + m_pView->Collapse(m_pCursor); + if (bMod1) + CollapseTo(m_pTree->GetRootLevelParent(m_pCursor)); + break; + + case KEY_MULTIPLY: + if( bMod1 ) + { + // only try to expand/collapse if sublist is expandable, + // otherwise ignore the key press + if( IsExpandable() ) + { + if (!m_pView->IsAllExpanded(m_pCursor)) + { + m_pView->Expand(m_pCursor); + ExpandAll(); + } + else + CollapseTo(m_pCursor); + } + } + else + bKeyUsed = false; + break; + + case KEY_DIVIDE : + if( bMod1 ) + SelAllDestrAnch( true ); + else + bKeyUsed = false; + break; + + case KEY_COMMA : + if( bMod1 ) + SelAllDestrAnch( false ); + else + bKeyUsed = false; + break; + + case KEY_HOME : + pNewCursor = m_pView->GetModel()->First(); + + while( pNewCursor && !IsSelectable(pNewCursor) ) + { + pNewCursor = m_pView->NextVisible(pNewCursor); + } + + if( pNewCursor && pNewCursor != m_pCursor ) + { +// SelAllDestrAnch( false ); + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + SetCursor( pNewCursor ); + if( !IsEntryInView( pNewCursor ) ) + MakeVisible( pNewCursor ); + } + else + bKeyUsed = false; + break; + + case KEY_END : + pNewCursor = m_pView->GetModel()->Last(); + + while( pNewCursor && !IsSelectable(pNewCursor) ) + { + pNewCursor = m_pView->PrevVisible(pNewCursor); + } + + if( pNewCursor && pNewCursor != m_pCursor) + { +// SelAllDestrAnch( false ); + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + SetCursor( pNewCursor ); + if( !IsEntryInView( pNewCursor ) ) + MakeVisible( pNewCursor ); + } + else + bKeyUsed = false; + break; + + case KEY_ESCAPE: + case KEY_TAB: + case KEY_DELETE: + case KEY_BACKSPACE: + // must not be handled because this quits dialogs and does other magic things... + // if there are other single keys which should not be handled, they can be added here + bKeyUsed = false; + break; + + default: + // is there any reason why we should eat the events here? The only place where this is called + // is from SvTreeListBox::KeyInput. If we set bKeyUsed to true here, then the key input + // is just silenced. However, we want SvLBox::KeyInput to get a chance, to do the QuickSelection + // handling. + // (The old code here which intentionally set bKeyUsed to sal_True said this was because of "quick search" + // handling, but actually there was no quick search handling anymore. We just re-implemented it.) + // #i31275# / 2009-06-16 / frank.schoenheit@sun.com + bKeyUsed = false; + break; + } + return bKeyUsed; +} + +void SvImpLBox::GetFocus() +{ + if( m_pCursor ) + { + m_pView->SetEntryFocus( m_pCursor, true ); + ShowCursor( true ); +// auskommentiert wg. deselectall +// if( bSimpleTravel && !pView->IsSelected(pCursor) ) +// pView->Select( pCursor, true ); + } + if( m_nStyle & WB_HIDESELECTION ) + { + SvTreeListEntry* pEntry = m_pView->FirstSelected(); + while( pEntry ) + { + InvalidateEntry( pEntry ); + pEntry = m_pView->NextSelected( pEntry ); + } + } +} + +void SvImpLBox::LoseFocus() +{ + m_aEditIdle.Stop(); + if( m_pCursor ) + m_pView->SetEntryFocus( m_pCursor,false ); + ShowCursor( false ); + + if( m_nStyle & WB_HIDESELECTION ) + { + SvTreeListEntry* pEntry = m_pView ? m_pView->FirstSelected() : nullptr; + while( pEntry ) + { + InvalidateEntry( pEntry ); + pEntry = m_pView->NextSelected( pEntry ); + } + } +} + + +// ******************************************************************** +// SelectionEngine +// ******************************************************************** + +void SvImpLBox::SelectEntry( SvTreeListEntry* pEntry, bool bSelect ) +{ + m_pView->Select( pEntry, bSelect ); +} + +ImpLBSelEng::ImpLBSelEng( SvImpLBox* pImpl, SvTreeListBox* pV ) +{ + pImp = pImpl; + pView = pV; +} + +ImpLBSelEng::~ImpLBSelEng() +{ +} + +void ImpLBSelEng::BeginDrag() +{ + pImp->BeginDrag(); +} + +void ImpLBSelEng::CreateAnchor() +{ + pImp->m_pAnchor = pImp->m_pCursor; +} + +void ImpLBSelEng::DestroyAnchor() +{ + pImp->m_pAnchor = nullptr; +} + +void ImpLBSelEng::SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor) +{ + SvTreeListEntry* pNewCursor = pImp->MakePointVisible( rPoint ); + if( pNewCursor ) + { + // at SimpleTravel, the SetCursor is selected and the select handler is + // called + //if( !bDontSelectAtCursor && !pImp->bSimpleTravel ) + // pImp->SelectEntry( pNewCursor, true ); + pImp->SetCursor( pNewCursor, bDontSelectAtCursor ); + } +} + +bool ImpLBSelEng::IsSelectionAtPoint( const Point& rPoint ) +{ + SvTreeListEntry* pEntry = pImp->MakePointVisible( rPoint ); + if( pEntry ) + return pView->IsSelected(pEntry); + return false; +} + +void ImpLBSelEng::DeselectAtPoint( const Point& rPoint ) +{ + SvTreeListEntry* pEntry = pImp->MakePointVisible( rPoint ); + if( !pEntry ) + return; + pImp->SelectEntry( pEntry, false ); +} + +void ImpLBSelEng::DeselectAll() +{ + pImp->SelAllDestrAnch( false, false ); // don't reset SelectionEngine! + pImp->m_nFlags &= ~LBoxFlags::DeselectAll; +} + +// *********************************************************************** +// Selection +// *********************************************************************** + +void SvImpLBox::SetAnchorSelection(SvTreeListEntry* pOldCursor,SvTreeListEntry* pNewCursor) +{ + SvTreeListEntry* pEntry; + sal_uLong nAnchorVisPos = m_pView->GetVisiblePos( m_pAnchor ); + sal_uLong nOldVisPos = m_pView->GetVisiblePos( pOldCursor ); + sal_uLong nNewVisPos = m_pView->GetVisiblePos( pNewCursor ); + + if( nOldVisPos > nAnchorVisPos || + ( nAnchorVisPos==nOldVisPos && nNewVisPos > nAnchorVisPos) ) + { + if( nNewVisPos > nOldVisPos ) + { + pEntry = pOldCursor; + while( pEntry && pEntry != pNewCursor ) + { + m_pView->Select( pEntry ); + pEntry = m_pView->NextVisible(pEntry); + } + if( pEntry ) + m_pView->Select( pEntry ); + return; + } + + if( nNewVisPos < nAnchorVisPos ) + { + pEntry = m_pAnchor; + while( pEntry && pEntry != pOldCursor ) + { + m_pView->Select( pEntry, false ); + pEntry = m_pView->NextVisible(pEntry); + } + if( pEntry ) + m_pView->Select( pEntry, false ); + + pEntry = pNewCursor; + while( pEntry && pEntry != m_pAnchor ) + { + m_pView->Select( pEntry ); + pEntry = m_pView->NextVisible(pEntry); + } + if( pEntry ) + m_pView->Select( pEntry ); + return; + } + + if( nNewVisPos < nOldVisPos ) + { + pEntry = m_pView->NextVisible(pNewCursor); + while( pEntry && pEntry != pOldCursor ) + { + m_pView->Select( pEntry, false ); + pEntry = m_pView->NextVisible(pEntry); + } + if( pEntry ) + m_pView->Select( pEntry, false ); + return; + } + } + else + { + if( nNewVisPos < nOldVisPos ) // enlarge selection + { + pEntry = pNewCursor; + while( pEntry && pEntry != pOldCursor ) + { + m_pView->Select( pEntry ); + pEntry = m_pView->NextVisible(pEntry); + } + if( pEntry ) + m_pView->Select( pEntry ); + return; + } + + if( nNewVisPos > nAnchorVisPos ) + { + pEntry = pOldCursor; + while( pEntry && pEntry != m_pAnchor ) + { + m_pView->Select( pEntry, false ); + pEntry = m_pView->NextVisible(pEntry); + } + if( pEntry ) + m_pView->Select( pEntry, false ); + pEntry = m_pAnchor; + while( pEntry && pEntry != pNewCursor ) + { + m_pView->Select( pEntry ); + pEntry = m_pView->NextVisible(pEntry); + } + if( pEntry ) + m_pView->Select( pEntry ); + return; + } + + if( nNewVisPos > nOldVisPos ) + { + pEntry = pOldCursor; + while( pEntry && pEntry != pNewCursor ) + { + m_pView->Select( pEntry, false ); + pEntry = m_pView->NextVisible(pEntry); + } + return; + } + } +} + +void SvImpLBox::SelAllDestrAnch( + bool bSelect, bool bDestroyAnchor, bool bSingleSelToo ) +{ + SvTreeListEntry* pEntry; + m_nFlags &= ~LBoxFlags::DeselectAll; + if( bSelect && m_bSimpleTravel ) + { + if( m_pCursor && !m_pView->IsSelected( m_pCursor )) + { + m_pView->Select( m_pCursor ); + } + return; + } + if( !bSelect && m_pView->GetSelectionCount() == 0 ) + { + if( m_bSimpleTravel && ( !GetUpdateMode() || !m_pCursor) ) + m_nFlags |= LBoxFlags::DeselectAll; + return; + } + if( bSelect && m_pView->GetSelectionCount() == m_pView->GetEntryCount()) + return; + if( !bSingleSelToo && m_bSimpleTravel ) + return; + + if( !bSelect && m_pView->GetSelectionCount()==1 && m_pCursor && + m_pView->IsSelected( m_pCursor )) + { + m_pView->Select( m_pCursor, false ); + if( bDestroyAnchor ) + DestroyAnchor(); // delete anchor & reset SelectionEngine + else + m_pAnchor = nullptr; // always delete internal anchor + return; + } + + if( m_bSimpleTravel && !m_pCursor && !GetUpdateMode() ) + m_nFlags |= LBoxFlags::DeselectAll; + + ShowCursor( false ); + bool bUpdate = GetUpdateMode(); + + m_nFlags |= LBoxFlags::IgnoreSelect; // EntryInserted should not do anything + pEntry = m_pTree->First(); + while( pEntry ) + { + if( m_pView->Select( pEntry, bSelect ) ) + { + if( bUpdate && m_pView->IsEntryVisible(pEntry) ) + { + tools::Long nY = GetEntryLine( pEntry ); + if( IsLineVisible( nY ) ) + InvalidateEntry(pEntry); + } + } + pEntry = m_pTree->Next( pEntry ); + } + m_nFlags &= ~LBoxFlags::IgnoreSelect; + + if( bDestroyAnchor ) + DestroyAnchor(); // delete anchor & reset SelectionEngine + else + m_pAnchor = nullptr; // always delete internal anchor + ShowCursor( true ); +} + +void SvImpLBox::SetSelectionMode( SelectionMode eSelMode ) +{ + m_aSelEng.SetSelectionMode( eSelMode); + if( eSelMode == SelectionMode::Single ) + m_bSimpleTravel = true; + else + m_bSimpleTravel = false; + if( (m_nStyle & WB_SIMPLEMODE) && (eSelMode == SelectionMode::Multiple) ) + m_aSelEng.AddAlways( true ); +} + +// *********************************************************************** +// Drag & Drop +// *********************************************************************** + +void SvImpLBox::SetDragDropMode( DragDropMode eDDMode ) +{ + if( eDDMode != DragDropMode::NONE ) + { + m_aSelEng.ExpandSelectionOnMouseMove( false ); + m_aSelEng.EnableDrag( true ); + } + else + { + m_aSelEng.ExpandSelectionOnMouseMove(); + m_aSelEng.EnableDrag( false ); + } +} + +void SvImpLBox::BeginDrag() +{ + m_nFlags &= ~LBoxFlags::Filling; + m_pView->StartDrag( 0, m_aSelEng.GetMousePosPixel() ); +} + +void SvImpLBox::PaintDDCursor(SvTreeListEntry* pEntry, bool bShow) +{ + if (pEntry) + { + + SvViewDataEntry* pViewData = m_pView->GetViewData(pEntry); + pViewData->SetDragTarget(bShow); +#ifdef MACOSX + // in MacOS we need to draw directly (as we are synchronous) or no invalidation happens + m_pView->PaintEntry1(*pEntry, GetEntryLine(pEntry), *m_pView->GetOutDev()); +#else + InvalidateEntry(pEntry); +#endif + } +} + +void SvImpLBox::Command( const CommandEvent& rCEvt ) +{ + CommandEventId nCommand = rCEvt.GetCommand(); + + if( nCommand == CommandEventId::ContextMenu ) + m_aEditIdle.Stop(); + + // scroll mouse event? + if (nCommand == CommandEventId::Wheel || + nCommand == CommandEventId::StartAutoScroll || + nCommand == CommandEventId::AutoScroll || + nCommand == CommandEventId::Gesture) + { + if (m_pView->HandleScrollCommand(rCEvt, m_aHorSBar.get(), m_aVerSBar.get())) + return; + } + + const Point& rPos = rCEvt.GetMousePosPixel(); + if( rPos.X() < m_aOutputSize.Width() && rPos.Y() < m_aOutputSize.Height() ) + m_aSelEng.Command( rCEvt ); +} + +tools::Rectangle SvImpLBox::GetVisibleArea() const +{ + Point aPos( m_pView->GetMapMode().GetOrigin() ); + aPos.setX( aPos.X() * -1 ); + tools::Rectangle aRect( aPos, m_aOutputSize ); + return aRect; +} + +void SvImpLBox::Invalidate() +{ + m_pView->GetOutDev()->SetClipRegion(); +} + +void SvImpLBox::SetCurEntry( SvTreeListEntry* pEntry ) +{ + if ( ( m_aSelEng.GetSelectionMode() != SelectionMode::Single ) + && ( m_aSelEng.GetSelectionMode() != SelectionMode::NONE ) + ) + SelAllDestrAnch( false ); + if ( pEntry ) + MakeVisible( pEntry ); + SetCursor( pEntry ); + if ( pEntry && ( m_aSelEng.GetSelectionMode() != SelectionMode::NONE ) ) + m_pView->Select( pEntry ); +} + +IMPL_LINK_NOARG(SvImpLBox, EditTimerCall, Timer *, void) +{ + if( !m_pView->IsInplaceEditingEnabled() ) + return; + + bool bIsMouseTriggered = m_aEditClickPos.X() >= 0; + if ( bIsMouseTriggered ) + { + Point aCurrentMousePos = m_pView->GetPointerPosPixel(); + if ( ( std::abs( aCurrentMousePos.X() - m_aEditClickPos.X() ) > 5 ) + || ( std::abs( aCurrentMousePos.Y() - m_aEditClickPos.Y() ) > 5 ) + ) + { + return; + } + } + + SvTreeListEntry* pEntry = GetCurEntry(); + if( pEntry ) + { + ShowCursor( false ); + m_pView->ImplEditEntry( pEntry ); + ShowCursor( true ); + } +} + +bool SvImpLBox::RequestHelp( const HelpEvent& rHEvt ) +{ + if( rHEvt.GetMode() & HelpEventMode::QUICK ) + { + Point aPos( m_pView->ScreenToOutputPixel( rHEvt.GetMousePosPixel() )); + if( !GetVisibleArea().Contains( aPos )) + return false; + + SvTreeListEntry* pEntry = GetEntry( aPos ); + if( pEntry ) + { + // recalculate text rectangle + SvLBoxTab* pTab; + SvLBoxItem* pItem = m_pView->GetItem( pEntry, aPos.X(), &pTab ); + if (!pItem || pItem->GetType() != SvLBoxItemType::String) + return false; + + aPos = GetEntryPosition( pEntry ); + aPos.setX( m_pView->GetTabPos( pEntry, pTab ) ); //pTab->GetPos(); + Size aSize(pItem->GetWidth(m_pView, pEntry), pItem->GetHeight(m_pView, pEntry)); + SvLBoxTab* pNextTab = NextTab( pTab ); + bool bItemClipped = false; + // is the item cut off by its right neighbor? + if( pNextTab && m_pView->GetTabPos(pEntry,pNextTab) < aPos.X()+aSize.Width() ) + { + aSize.setWidth( pNextTab->GetPos() - pTab->GetPos() ); + bItemClipped = true; + } + tools::Rectangle aItemRect( aPos, aSize ); + + tools::Rectangle aViewRect( GetVisibleArea() ); + + if( bItemClipped || !aViewRect.Contains( aItemRect ) ) + { + // clip the right edge of the item at the edge of the view + //if( aItemRect.Right() > aViewRect.Right() ) + // aItemRect.Right() = aViewRect.Right(); + + Point aPt = m_pView->OutputToScreenPixel( aItemRect.TopLeft() ); + aItemRect.SetLeft( aPt.X() ); + aItemRect.SetTop( aPt.Y() ); + aPt = m_pView->OutputToScreenPixel( aItemRect.BottomRight() ); + aItemRect.SetRight( aPt.X() ); + aItemRect.SetBottom( aPt.Y() ); + + Help::ShowQuickHelp( m_pView, aItemRect, + static_cast<SvLBoxString*>(pItem)->GetText(), QuickHelpFlags::Left | QuickHelpFlags::VCenter ); + return true; + } + } + } + return false; +} + +SvLBoxTab* SvImpLBox::NextTab( SvLBoxTab const * pTab ) +{ + sal_uInt16 nTabCount = m_pView->TabCount(); + if( nTabCount <= 1 ) + return nullptr; + for( int nTab=0; nTab < (nTabCount-1); nTab++) + { + if( m_pView->aTabs[nTab].get() == pTab ) + return m_pView->aTabs[nTab+1].get(); + } + return nullptr; +} + +void SvImpLBox::SetUpdateMode( bool bMode ) +{ + if( m_bUpdateMode != bMode ) + { + m_bUpdateMode = bMode; + if( m_bUpdateMode ) + UpdateAll( false ); + } +} + +void SvImpLBox::SetMostRight( SvTreeListEntry* pEntry ) +{ + if( m_pView->nTreeFlags & SvTreeFlags::RECALCTABS ) + { + m_nFlags |= LBoxFlags::IgnoreChangedTabs; + m_pView->SetTabs(); + m_nFlags &= ~LBoxFlags::IgnoreChangedTabs; + } + + sal_uInt16 nLastTab = m_pView->aTabs.size() - 1; + sal_uInt16 nLastItem = pEntry->ItemCount() - 1; + if( m_pView->aTabs.empty() || nLastItem == USHRT_MAX ) + return; + + if( nLastItem < nLastTab ) + nLastTab = nLastItem; + + SvLBoxTab* pTab = m_pView->aTabs[ nLastTab ].get(); + SvLBoxItem& rItem = pEntry->GetItem( nLastTab ); + + tools::Long nTabPos = m_pView->GetTabPos( pEntry, pTab ); + + tools::Long nMaxRight = GetOutputSize().Width(); + Point aPos( m_pView->GetMapMode().GetOrigin() ); + aPos.setX( aPos.X() * -1 ); // conversion document coordinates + nMaxRight = nMaxRight + aPos.X() - 1; + + tools::Long nNextTab = nTabPos < nMaxRight ? nMaxRight : nMaxRight + 50; + tools::Long nTabWidth = nNextTab - nTabPos + 1; + auto nItemSize = rItem.GetWidth(m_pView,pEntry); + tools::Long nOffset = pTab->CalcOffset( nItemSize, nTabWidth ); + + tools::Long nRight = nTabPos + nOffset + nItemSize; + if( nRight > m_nMostRight ) + { + m_nMostRight = nRight; + m_pMostRightEntry = pEntry; + } +} + +void SvImpLBox::FindMostRight() +{ + m_nMostRight = -1; + m_pMostRightEntry = nullptr; + if( !m_pView->GetModel() ) + return; + + SvTreeListEntry* pEntry = m_pView->FirstVisible(); + while( pEntry ) + { + SetMostRight( pEntry ); + pEntry = m_pView->NextVisible( pEntry ); + } +} + +void SvImpLBox::FindMostRight( SvTreeListEntry* pParent ) +{ + if( !pParent ) + FindMostRight(); + else + FindMostRight_Impl( pParent ); +} + +void SvImpLBox::FindMostRight_Impl( SvTreeListEntry* pParent ) +{ + SvTreeListEntries& rList = m_pTree->GetChildList( pParent ); + + size_t nCount = rList.size(); + for( size_t nCur = 0; nCur < nCount; nCur++ ) + { + SvTreeListEntry* pChild = rList[nCur].get(); + SetMostRight( pChild ); + if( pChild->HasChildren() && m_pView->IsExpanded( pChild )) + FindMostRight_Impl( pChild ); + } +} + +void SvImpLBox::NotifyTabsChanged() +{ + if( GetUpdateMode() && !(m_nFlags & LBoxFlags::IgnoreChangedTabs ) && + m_nCurUserEvent == nullptr ) + { + m_nCurUserEvent = Application::PostUserEvent(LINK(this,SvImpLBox,MyUserEvent)); + } +} + +bool SvImpLBox::IsExpandable() const +{ + return m_pCursor->HasChildren() || m_pCursor->HasChildrenOnDemand(); +} + +IMPL_LINK(SvImpLBox, MyUserEvent, void*, pArg, void ) +{ + m_nCurUserEvent = nullptr; + if( !pArg ) + { + m_pView->Invalidate(); + m_pView->PaintImmediately(); + } + else + { + FindMostRight(); + ShowVerSBar(); + m_pView->Invalidate( GetVisibleArea() ); + } +} + + +void SvImpLBox::StopUserEvent() +{ + if( m_nCurUserEvent != nullptr ) + { + Application::RemoveUserEvent( m_nCurUserEvent ); + m_nCurUserEvent = nullptr; + } +} + +void SvImpLBox::implInitDefaultNodeImages() +{ + if ( s_pDefCollapsed ) + // assume that all or nothing is initialized + return; + + s_pDefCollapsed = new Image(StockImage::Yes, RID_BMP_TREENODE_COLLAPSED); + s_pDefExpanded = new Image(StockImage::Yes, RID_BMP_TREENODE_EXPANDED); +} + + +const Image& SvImpLBox::GetDefaultExpandedNodeImage( ) +{ + implInitDefaultNodeImages(); + return *s_pDefExpanded; +} + + +const Image& SvImpLBox::GetDefaultCollapsedNodeImage( ) +{ + implInitDefaultNodeImages(); + return *s_pDefCollapsed; +} + + +void SvImpLBox::CallEventListeners( VclEventId nEvent, void* pData ) +{ + if ( m_pView ) + m_pView->CallImplEventListeners( nEvent, pData); +} + + +bool SvImpLBox::IsSelectable( const SvTreeListEntry* pEntry ) const +{ + if( pEntry ) + { + SvViewDataEntry* pViewDataNewCur = m_pView->GetViewDataEntry(pEntry); + return (pViewDataNewCur == nullptr) || pViewDataNewCur->IsSelectable(); + } + else + { + return false; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/svlbitm.cxx b/vcl/source/treelist/svlbitm.cxx new file mode 100644 index 000000000..3e0243a8d --- /dev/null +++ b/vcl/source/treelist/svlbitm.cxx @@ -0,0 +1,550 @@ +/* -*- 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 <vcl/toolkit/treelistbox.hxx> +#include <vcl/toolkit/svlbitm.hxx> +#include <vcl/toolkit/treelistentry.hxx> +#include <vcl/toolkit/viewdataentry.hxx> +#include <vcl/svapp.hxx> +#include <vcl/toolkit/button.hxx> +#include <vcl/decoview.hxx> +#include <vcl/salnativewidgets.hxx> +#include <vcl/settings.hxx> + +struct SvLBoxButtonData_Impl +{ + SvTreeListEntry* pEntry; + SvLBoxButton* pBox; + bool bDefaultImages; + bool bShowRadioButton; + + SvLBoxButtonData_Impl() : pEntry(nullptr), pBox(nullptr), bDefaultImages(false), bShowRadioButton(false) {} +}; + +void SvLBoxButtonData::InitData( bool _bRadioBtn, const Control* pCtrl ) +{ + nWidth = nHeight = 0; + + aBmps.resize(int(SvBmp::HITRISTATE)+1); + + bDataOk = false; + pImpl->bDefaultImages = true; + pImpl->bShowRadioButton = _bRadioBtn; + + SetDefaultImages( pCtrl ); +} + +SvLBoxButtonData::SvLBoxButtonData( const Control* pControlForSettings, bool _bRadioBtn ) + : pImpl( new SvLBoxButtonData_Impl ) +{ + InitData( _bRadioBtn, pControlForSettings ); +} + +SvLBoxButtonData::~SvLBoxButtonData() +{ +} + +void SvLBoxButtonData::CallLink() +{ + aLink.Call( this ); +} + +SvBmp SvLBoxButtonData::GetIndex( SvItemStateFlags nItemState ) +{ + SvBmp nIdx; + if (nItemState == SvItemStateFlags::UNCHECKED) + nIdx = SvBmp::UNCHECKED; + else if (nItemState == SvItemStateFlags::CHECKED) + nIdx = SvBmp::CHECKED; + else if (nItemState == SvItemStateFlags::TRISTATE) + nIdx = SvBmp::TRISTATE; + else if (nItemState == (SvItemStateFlags::UNCHECKED | SvItemStateFlags::HILIGHTED)) + nIdx = SvBmp::HIUNCHECKED; + else if (nItemState == (SvItemStateFlags::CHECKED | SvItemStateFlags::HILIGHTED)) + nIdx = SvBmp::HICHECKED; + else if (nItemState == (SvItemStateFlags::TRISTATE | SvItemStateFlags::HILIGHTED)) + nIdx = SvBmp::HITRISTATE; + else + nIdx = SvBmp::UNCHECKED; + return nIdx; +} + +void SvLBoxButtonData::SetWidthAndHeight() +{ + Size aSize = aBmps[int(SvBmp::UNCHECKED)].GetSizePixel(); + nWidth = aSize.Width(); + nHeight = aSize.Height(); + bDataOk = true; +} + +void SvLBoxButtonData::StoreButtonState(SvTreeListEntry* pActEntry, SvLBoxButton* pActBox) +{ + pImpl->pEntry = pActEntry; + pImpl->pBox = pActBox; +} + +SvButtonState SvLBoxButtonData::ConvertToButtonState( SvItemStateFlags nItemFlags ) +{ + nItemFlags &= SvItemStateFlags::UNCHECKED | + SvItemStateFlags::CHECKED | + SvItemStateFlags::TRISTATE; + switch( nItemFlags ) + { + case SvItemStateFlags::UNCHECKED: + return SvButtonState::Unchecked; + case SvItemStateFlags::CHECKED: + return SvButtonState::Checked; + case SvItemStateFlags::TRISTATE: + return SvButtonState::Tristate; + default: + return SvButtonState::Unchecked; + } +} + +SvTreeListEntry* SvLBoxButtonData::GetActEntry() const +{ + assert(pImpl && "-SvLBoxButtonData::GetActEntry(): don't use me that way!"); + return pImpl->pEntry; +} + +SvLBoxButton* SvLBoxButtonData::GetActBox() const +{ + assert(pImpl && "-SvLBoxButtonData::GetActBox(): don't use me that way!"); + return pImpl->pBox; +} + +void SvLBoxButtonData::SetDefaultImages( const Control* pCtrl ) +{ + const AllSettings& rSettings = pCtrl? pCtrl->GetSettings() : Application::GetSettings(); + + if ( pImpl->bShowRadioButton ) + { + SetImage(SvBmp::UNCHECKED, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::Default ) ); + SetImage(SvBmp::CHECKED, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::Checked ) ); + SetImage(SvBmp::HICHECKED, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::Checked | DrawButtonFlags::Pressed ) ); + SetImage(SvBmp::HIUNCHECKED, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::Default | DrawButtonFlags::Pressed ) ); + SetImage(SvBmp::TRISTATE, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::DontKnow ) ); + SetImage(SvBmp::HITRISTATE, RadioButton::GetRadioImage( rSettings, DrawButtonFlags::DontKnow | DrawButtonFlags::Pressed ) ); + } + else + { + SetImage(SvBmp::UNCHECKED, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::Default ) ); + SetImage(SvBmp::CHECKED, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::Checked ) ); + SetImage(SvBmp::HICHECKED, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::Checked | DrawButtonFlags::Pressed ) ); + SetImage(SvBmp::HIUNCHECKED, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::Default | DrawButtonFlags::Pressed ) ); + SetImage(SvBmp::TRISTATE, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::DontKnow ) ); + SetImage(SvBmp::HITRISTATE, CheckBox::GetCheckImage( rSettings, DrawButtonFlags::DontKnow | DrawButtonFlags::Pressed ) ); + } +} + +bool SvLBoxButtonData::HasDefaultImages() const +{ + return pImpl->bDefaultImages; +} + +bool SvLBoxButtonData::IsRadio() const { + return pImpl->bShowRadioButton; +} + +// *************************************************************** +// class SvLBoxString +// *************************************************************** + + +SvLBoxString::SvLBoxString(const OUString& rStr) + : mbEmphasized(false) + , mbCustom(false) + , mfAlign(0.0) + , maText(rStr) +{ +} + +SvLBoxString::SvLBoxString() + : mbEmphasized(false) + , mbCustom(false) + , mfAlign(0.0) +{ +} + +SvLBoxString::~SvLBoxString() +{ +} + +SvLBoxItemType SvLBoxString::GetType() const +{ + return SvLBoxItemType::String; +} + +namespace +{ + void drawSeparator(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRegion) + { + Color aOldLineColor(rRenderContext.GetLineColor()); + const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings(); + Point aTmpPos = rRegion.TopLeft(); + Size aSize = rRegion.GetSize(); + aTmpPos.AdjustY(aSize.Height() / 2 ); + rRenderContext.SetLineColor(rStyle.GetShadowColor()); + rRenderContext.DrawLine(aTmpPos, Point(aSize.Width() + aTmpPos.X(), aTmpPos.Y())); + rRenderContext.SetLineColor(aOldLineColor); + } +} + +void SvLBoxString::Paint( + const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext, + const SvViewDataEntry* /*pView*/, const SvTreeListEntry& rEntry) +{ + DrawTextFlags nStyle = (rDev.IsEnabled() && !mbDisabled) ? DrawTextFlags::NONE : DrawTextFlags::Disable; + if (bool(rEntry.GetFlags() & SvTLEntryFlags::IS_SEPARATOR)) + { + Point aStartPos(0, rPos.Y() - 2); + tools::Rectangle aRegion(aStartPos, Size(rDev.GetSizePixel().Width(), 4)); + drawSeparator(rRenderContext, aRegion); + return; + } + + Size aSize; + if (rDev.TextCenterAndClipEnabled()) + { + nStyle |= DrawTextFlags::PathEllipsis | DrawTextFlags::Center; + aSize.setWidth( rDev.GetEntryWidth() ); + } + else + { + if (mfAlign < 0.5 ) + { + nStyle |= DrawTextFlags::Left; + aSize.setWidth(GetWidth(&rDev, &rEntry)); + } + else if (mfAlign == 0.5) + { + nStyle |= DrawTextFlags::Center; + aSize.setWidth(rDev.GetBoundingRect(&rEntry).getWidth()); + } + else if (mfAlign > 0.5) + { + nStyle |= DrawTextFlags::Right; + aSize.setWidth(rDev.GetBoundingRect(&rEntry).getWidth()); + } + } + aSize.setHeight(GetHeight(&rDev, &rEntry)); + + if (mbEmphasized) + { + rRenderContext.Push(); + vcl::Font aFont(rRenderContext.GetFont()); + aFont.SetWeight(WEIGHT_BOLD); + rRenderContext.SetFont(aFont); + } + + tools::Rectangle aRect(rPos, aSize); + + if (mbCustom) + rDev.DrawCustomEntry(rRenderContext, aRect, rEntry); + else + rRenderContext.DrawText(aRect, maText, nStyle); + + if (mbEmphasized) + rRenderContext.Pop(); +} + +std::unique_ptr<SvLBoxItem> SvLBoxString::Clone(SvLBoxItem const * pSource) const +{ + std::unique_ptr<SvLBoxString> pNew(new SvLBoxString); + + const SvLBoxString* pOther = static_cast<const SvLBoxString*>(pSource); + pNew->maText = pOther->maText; + pNew->mbEmphasized = pOther->mbEmphasized; + pNew->mbCustom = pOther->mbCustom; + pNew->mfAlign = pOther->mfAlign; + + return std::unique_ptr<SvLBoxItem>(pNew.release()); +} + +void SvLBoxString::InitViewData( + SvTreeListBox* pView, SvTreeListEntry* pEntry, SvViewDataItem* pViewData) +{ + if( !pViewData ) + pViewData = pView->GetViewDataItem( pEntry, this ); + + if (bool(pEntry->GetFlags() & SvTLEntryFlags::IS_SEPARATOR)) + { + pViewData->mnWidth = -1; + pViewData->mnHeight = 0; + return; + } + + if (mbEmphasized) + { + pView->GetOutDev()->Push(); + vcl::Font aFont( pView->GetFont()); + aFont.SetWeight(WEIGHT_BOLD); + pView->Control::SetFont( aFont ); + } + + if (mbCustom) + { + Size aSize = pView->MeasureCustomEntry(*pView->GetOutDev(), *pEntry); + pViewData->mnWidth = aSize.Width(); + pViewData->mnHeight = aSize.Height(); + } + else + { + pViewData->mnWidth = -1; // calc on demand + pViewData->mnHeight = pView->GetTextHeight(); + } + + if (mbEmphasized) + pView->GetOutDev()->Pop(); +} + +int SvLBoxString::CalcWidth(const SvTreeListBox* pView) const +{ + return pView->GetTextWidth(maText); +} + +// *************************************************************** +// class SvLBoxButton +// *************************************************************** + + +SvLBoxButton::SvLBoxButton( SvLBoxButtonData* pBData ) + : isVis(true) + , pData(pBData) + , nItemFlags(SvItemStateFlags::NONE) +{ + SetStateUnchecked(); +} + +SvLBoxButton::SvLBoxButton() + : isVis(false) + , pData(nullptr) + , nItemFlags(SvItemStateFlags::NONE) +{ + SetStateUnchecked(); +} + +SvLBoxButton::~SvLBoxButton() +{ +} + +SvLBoxItemType SvLBoxButton::GetType() const +{ + return SvLBoxItemType::Button; +} + +void SvLBoxButton::ClickHdl( SvTreeListEntry* pEntry ) +{ + if ( IsStateChecked() ) + SetStateUnchecked(); + else + SetStateChecked(); + pData->StoreButtonState(pEntry, this); + pData->CallLink(); +} + +void SvLBoxButton::Paint( + const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext, + const SvViewDataEntry* /*pView*/, const SvTreeListEntry& /*rEntry*/) +{ + SvBmp nIndex = SvLBoxButtonData::GetIndex(nItemFlags); + DrawImageFlags nStyle = rDev.IsEnabled() ? DrawImageFlags::NONE : DrawImageFlags::Disable; + + //Native drawing + bool bNativeOK = false; + ControlType eCtrlType = (pData->IsRadio())? ControlType::Radiobutton : ControlType::Checkbox; + if ( rRenderContext.IsNativeControlSupported( eCtrlType, ControlPart::Entire) ) + { + Size aSize(pData->Width(), pData->Height()); + ImplAdjustBoxSize(aSize, eCtrlType, rRenderContext); + ImplControlValue aControlValue; + tools::Rectangle aCtrlRegion( rPos, aSize ); + ControlState nState = ControlState::NONE; + + //states ControlState::DEFAULT, ControlState::PRESSED and ControlState::ROLLOVER are not implemented + if (IsStateHilighted()) + nState |= ControlState::FOCUSED; + if (nStyle != DrawImageFlags::Disable) + nState |= ControlState::ENABLED; + if (IsStateChecked()) + aControlValue.setTristateVal(ButtonValue::On); + else if (IsStateUnchecked()) + aControlValue.setTristateVal(ButtonValue::Off); + else if (IsStateTristate()) + aControlValue.setTristateVal( ButtonValue::Mixed ); + + if (isVis) + bNativeOK = rRenderContext.DrawNativeControl(eCtrlType, ControlPart::Entire, + aCtrlRegion, nState, aControlValue, OUString()); + } + + if (!bNativeOK && isVis) + rRenderContext.DrawImage(rPos, pData->GetImage(nIndex), nStyle); +} + +std::unique_ptr<SvLBoxItem> SvLBoxButton::Clone(SvLBoxItem const * pSource) const +{ + std::unique_ptr<SvLBoxButton> pNew(new SvLBoxButton); + pNew->pData = static_cast<SvLBoxButton const *>(pSource)->pData; + return std::unique_ptr<SvLBoxItem>(pNew.release()); +} + +void SvLBoxButton::ImplAdjustBoxSize(Size& io_rSize, ControlType i_eType, vcl::RenderContext const & rRenderContext) +{ + if (!rRenderContext.IsNativeControlSupported( i_eType, ControlPart::Entire) ) + return; + + ImplControlValue aControlValue; + tools::Rectangle aCtrlRegion( Point( 0, 0 ), io_rSize ); + + aControlValue.setTristateVal( ButtonValue::On ); + + tools::Rectangle aNativeBounds, aNativeContent; + bool bNativeOK = rRenderContext.GetNativeControlRegion( i_eType, + ControlPart::Entire, + aCtrlRegion, + ControlState::ENABLED, + aControlValue, + aNativeBounds, + aNativeContent ); + if( bNativeOK ) + { + Size aContentSize( aNativeContent.GetSize() ); + // leave a little space around the box image (looks better) + if( aContentSize.Height() + 2 > io_rSize.Height() ) + io_rSize.setHeight( aContentSize.Height() + 2 ); + if( aContentSize.Width() + 2 > io_rSize.Width() ) + io_rSize.setWidth( aContentSize.Width() + 2 ); + } +} + +void SvLBoxButton::InitViewData(SvTreeListBox* pView,SvTreeListEntry* pEntry, SvViewDataItem* pViewData) +{ + if( !pViewData ) + pViewData = pView->GetViewDataItem( pEntry, this ); + Size aSize( pData->Width(), pData->Height() ); + + ControlType eCtrlType = (pData->IsRadio())? ControlType::Radiobutton : ControlType::Checkbox; + if ( pView ) + ImplAdjustBoxSize(aSize, eCtrlType, *pView->GetOutDev()); + pViewData->mnWidth = aSize.Width(); + pViewData->mnHeight = aSize.Height(); +} + +// *************************************************************** +// class SvLBoxContextBmp +// *************************************************************** + +struct SvLBoxContextBmp_Impl +{ + Image m_aImage1; + Image m_aImage2; + + bool m_bExpanded; +}; + +// *************************************************************** + +SvLBoxContextBmp::SvLBoxContextBmp(const Image& aBmp1, const Image& aBmp2, + bool bExpanded) + :m_pImpl( new SvLBoxContextBmp_Impl ) +{ + + m_pImpl->m_bExpanded = bExpanded; + SetModeImages( aBmp1, aBmp2 ); +} + +SvLBoxContextBmp::SvLBoxContextBmp() + : m_pImpl( new SvLBoxContextBmp_Impl ) +{ + m_pImpl->m_bExpanded = false; +} + +SvLBoxContextBmp::~SvLBoxContextBmp() +{ +} + +SvLBoxItemType SvLBoxContextBmp::GetType() const +{ + return SvLBoxItemType::ContextBmp; +} + +void SvLBoxContextBmp::SetModeImages( const Image& _rBitmap1, const Image& _rBitmap2 ) +{ + m_pImpl->m_aImage1 = _rBitmap1; + m_pImpl->m_aImage2 = _rBitmap2; +} + +Image& SvLBoxContextBmp::implGetImageStore( bool _bFirst ) +{ + + // OJ: #i27071# wrong mode so we just return the normal images + return _bFirst ? m_pImpl->m_aImage1 : m_pImpl->m_aImage2; +} + +void SvLBoxContextBmp::InitViewData( SvTreeListBox* pView,SvTreeListEntry* pEntry, + SvViewDataItem* pViewData) +{ + if( !pViewData ) + pViewData = pView->GetViewDataItem( pEntry, this ); + Size aSize = m_pImpl->m_aImage1.GetSizePixel(); + pViewData->mnWidth = aSize.Width(); + pViewData->mnHeight = aSize.Height(); +} + +void SvLBoxContextBmp::Paint( + const Point& _rPos, SvTreeListBox& _rDev, vcl::RenderContext& rRenderContext, + const SvViewDataEntry* pView, const SvTreeListEntry& rEntry) +{ + + // get the image. + const Image& rImage = implGetImageStore(pView->IsExpanded() != m_pImpl->m_bExpanded); + + bool _bSemiTransparent = bool( SvTLEntryFlags::SEMITRANSPARENT & rEntry.GetFlags( ) ); + // draw + DrawImageFlags nStyle = _rDev.IsEnabled() ? DrawImageFlags::NONE : DrawImageFlags::Disable; + if (_bSemiTransparent) + nStyle |= DrawImageFlags::SemiTransparent; + rRenderContext.DrawImage(_rPos, rImage, nStyle); +} + +std::unique_ptr<SvLBoxItem> SvLBoxContextBmp::Clone(SvLBoxItem const * pSource) const +{ + std::unique_ptr<SvLBoxContextBmp> pNew(new SvLBoxContextBmp); + pNew->m_pImpl->m_aImage1 = static_cast< SvLBoxContextBmp const * >( pSource )->m_pImpl->m_aImage1; + pNew->m_pImpl->m_aImage2 = static_cast< SvLBoxContextBmp const * >( pSource )->m_pImpl->m_aImage2; + pNew->m_pImpl->m_bExpanded = static_cast<SvLBoxContextBmp const *>(pSource)->m_pImpl->m_bExpanded; + return std::unique_ptr<SvLBoxItem>(pNew.release()); +} + +tools::Long SvLBoxButtonData::Width() +{ + if ( !bDataOk ) + SetWidthAndHeight(); + return nWidth; +} + +tools::Long SvLBoxButtonData::Height() +{ + if ( !bDataOk ) + SetWidthAndHeight(); + return nHeight; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/svtabbx.cxx b/vcl/source/treelist/svtabbx.cxx new file mode 100644 index 000000000..ec403682a --- /dev/null +++ b/vcl/source/treelist/svtabbx.cxx @@ -0,0 +1,1078 @@ +/* -*- 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 <vcl/svtaccessiblefactory.hxx> +#include <vcl/accessiblefactory.hxx> +#include <vcl/toolkit/svtabbx.hxx> +#include <vcl/headbar.hxx> +#include <vcl/toolkit/svlbitm.hxx> +#include <vcl/toolkit/treelistentry.hxx> +#include <unotools/accessiblestatesethelper.hxx> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> +#include <strings.hrc> +#include <svdata.hxx> +#include <memory> +#include <tools/json_writer.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::accessibility; + +constexpr SvLBoxTabFlags MYTABMASK = + SvLBoxTabFlags::ADJUST_RIGHT | SvLBoxTabFlags::ADJUST_LEFT | SvLBoxTabFlags::ADJUST_CENTER | SvLBoxTabFlags::FORCE; + +static void lcl_DumpEntryAndSiblings(tools::JsonWriter& rJsonWriter, + SvTreeListEntry* pEntry, + SvTabListBox* pTabListBox, + bool bCheckButtons) +{ + while (pEntry) + { + auto aNode = rJsonWriter.startStruct(); + + // simple listbox value + const SvLBoxItem* pIt = pEntry->GetFirstItem(SvLBoxItemType::String); + if (pIt) + rJsonWriter.put("text", static_cast<const SvLBoxString*>(pIt)->GetText()); + + // column based data + { + auto aColumns = rJsonWriter.startArray("columns"); + + for (size_t i = 0; i < pEntry->ItemCount(); i++) + { + SvLBoxItem& rItem = pEntry->GetItem(i); + if (rItem.GetType() == SvLBoxItemType::String) + { + const SvLBoxString* pStringItem = dynamic_cast<const SvLBoxString*>(&rItem); + if (pStringItem) + { + auto aColumn = rJsonWriter.startStruct(); + rJsonWriter.put("text", pStringItem->GetText()); + } + } + } + } + + // SalInstanceTreeView does not use the flag CHILDREN_ON_DEMAND + // and it creates a dummy child + const SvTreeListEntries& rChildren = pEntry->GetChildEntries(); + if (rChildren.size() == 1) + { + auto& rChild = rChildren[0]; + if (const SvLBoxItem* pChild = rChild->GetFirstItem(SvLBoxItemType::String)) + { + if (static_cast<const SvLBoxString*>(pChild)->GetText() == "<dummy>") + rJsonWriter.put("ondemand", "true"); + } + } + + if (bCheckButtons) + { + SvButtonState eCheckState = pTabListBox->GetCheckButtonState(pEntry); + if (eCheckState == SvButtonState::Unchecked) + rJsonWriter.put("state", "false"); + else if (eCheckState == SvButtonState::Checked) + rJsonWriter.put("state", "true"); + } + + if (pTabListBox->IsSelected(pEntry)) + rJsonWriter.put("selected", "true"); + + rJsonWriter.put("row", OString::number(pTabListBox->GetModel()->GetAbsPos(pEntry)).getStr()); + + SvTreeListEntry* pChild = pTabListBox->FirstChild(pEntry); + if (pChild) + { + auto childrenNode = rJsonWriter.startArray("children"); + lcl_DumpEntryAndSiblings(rJsonWriter, pChild, pTabListBox, bCheckButtons); + } + + pEntry = pEntry->NextSibling(); + } +} + +void SvTabListBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + SvTreeListBox::DumpAsPropertyTree(rJsonWriter); + + rJsonWriter.put("singleclickactivate", GetActivateOnSingleClick()); + + bool bCheckButtons = static_cast<int>(nTreeFlags & SvTreeFlags::CHKBTN); + + auto entriesNode = rJsonWriter.startArray("entries"); + lcl_DumpEntryAndSiblings(rJsonWriter, First(), this, bCheckButtons); +} + +// SvTreeListBox callback + +void SvTabListBox::SetTabs() +{ + SvTreeListBox::SetTabs(); + if( mvTabList.empty() ) + return; + + DBG_ASSERT(!mvTabList.empty(),"TabList ?"); + + // The tree listbox has now inserted its tabs into the list. Now we + // fluff up the list with additional tabs and adjust the rightmost tab + // of the tree listbox. + + // Picking the rightmost tab. + // HACK for the explorer! If ViewParent != 0, the first tab of the tree + // listbox is calculated by the tree listbox itself! This behavior is + // necessary for ButtonsOnRoot, as the explorer does not know in this + // case, which additional offset it needs to add to the tabs in this mode + // -- the tree listbox knows that, though! + /* + if( !pViewParent ) + { + SvLBoxTab* pFirstTab = (SvLBoxTab*)aTabs.GetObject( aTabs.Count()-1 ); + pFirstTab->SetPos( pTabList[0].GetPos() ); + pFirstTab->nFlags &= ~MYTABMASK; + pFirstTab->nFlags |= pTabList[0].nFlags; + } + */ + + // append all other tabs to the list + for( sal_uInt16 nCurTab = 1; nCurTab < sal_uInt16(mvTabList.size()); nCurTab++ ) + { + SvLBoxTab& rTab = mvTabList[nCurTab]; + AddTab( rTab.GetPos(), rTab.nFlags ); + } +} + +void SvTabListBox::InitEntry(SvTreeListEntry* pEntry, const OUString& rStr, + const Image& rColl, const Image& rExp) +{ + SvTreeListBox::InitEntry(pEntry, rStr, rColl, rExp); + + sal_Int32 nIndex = 0; + // TODO: verify if nTabCount is always >0 here! + const sal_uInt16 nCount = mvTabList.size() - 1; + for( sal_uInt16 nToken = 0; nToken < nCount; nToken++ ) + { + const std::u16string_view aToken = GetToken(aCurEntry, nIndex); + pEntry->AddItem(std::make_unique<SvLBoxString>(OUString(aToken))); + } +} + +SvTabListBox::SvTabListBox( vcl::Window* pParent, WinBits nBits ) + : SvTreeListBox( pParent, nBits ) +{ + SetHighlightRange(); // select full width +} + +SvTabListBox::~SvTabListBox() +{ + disposeOnce(); +} + +void SvTabListBox::dispose() +{ + mvTabList.clear(); + SvTreeListBox::dispose(); +} + +void SvTabListBox::SetTabs(sal_uInt16 nTabs, tools::Long const pTabPositions[], MapUnit eMapUnit) +{ + mvTabList.resize(nTabs); + + MapMode aMMSource( eMapUnit ); + MapMode aMMDest( MapUnit::MapPixel ); + + for( sal_uInt16 nIdx = 0; nIdx < sal_uInt16(mvTabList.size()); nIdx++, pTabPositions++ ) + { + Size aSize( *pTabPositions, 0 ); + aSize = LogicToLogic( aSize, &aMMSource, &aMMDest ); + tools::Long nNewTab = aSize.Width(); + mvTabList[nIdx].SetPos( nNewTab ); + mvTabList[nIdx].nFlags &= MYTABMASK; + } + SvTreeListBox::nTreeFlags |= SvTreeFlags::RECALCTABS; + if( IsUpdateMode() ) + Invalidate(); +} + +SvTreeListEntry* SvTabListBox::InsertEntry( const OUString& rText, SvTreeListEntry* pParent, + bool /*bChildrenOnDemand*/, + sal_uInt32 nPos, void* pUserData ) +{ + return InsertEntryToColumn( rText, pParent, nPos, 0xffff, pUserData ); +} + +SvTreeListEntry* SvTabListBox::InsertEntryToColumn(const OUString& rStr,SvTreeListEntry* pParent,sal_uInt32 nPos,sal_uInt16 nCol, + void* pUser ) +{ + OUString aStr; + if( nCol != 0xffff ) + { + while( nCol ) + { + aStr += "\t"; + nCol--; + } + } + aStr += rStr; + OUString aFirstStr( aStr ); + sal_Int32 nEnd = aFirstStr.indexOf( '\t' ); + if( nEnd != -1 ) + { + aFirstStr = aFirstStr.copy(0, nEnd); + aCurEntry = aStr.copy(++nEnd); + } + else + aCurEntry.clear(); + return SvTreeListBox::InsertEntry( aFirstStr, pParent, false, nPos, pUser ); +} + +OUString SvTabListBox::GetEntryText( SvTreeListEntry* pEntry ) const +{ + return GetEntryText( pEntry, 0xffff ); +} + +OUString SvTabListBox::GetEntryText( const SvTreeListEntry* pEntry, sal_uInt16 nCol ) +{ + DBG_ASSERT(pEntry,"GetEntryText:Invalid Entry"); + OUStringBuffer aResult; + if( pEntry ) + { + sal_uInt16 nCount = pEntry->ItemCount(); + sal_uInt16 nCur = 0; + while( nCur < nCount ) + { + const SvLBoxItem& rStr = pEntry->GetItem( nCur ); + if (rStr.GetType() == SvLBoxItemType::String) + { + if( nCol == 0xffff ) + { + if (!aResult.isEmpty()) + aResult.append("\t"); + aResult.append(static_cast<const SvLBoxString&>(rStr).GetText()); + } + else + { + if( nCol == 0 ) + return static_cast<const SvLBoxString&>(rStr).GetText(); + nCol--; + } + } + nCur++; + } + } + return aResult.makeStringAndClear(); +} + +OUString SvTabListBox::GetEntryText( sal_uInt32 nPos, sal_uInt16 nCol ) const +{ + SvTreeListEntry* pEntry = GetEntryOnPos( nPos ); + return GetEntryText( pEntry, nCol ); +} + +OUString SvTabListBox::GetCellText( sal_uInt32 nPos, sal_uInt16 nCol ) const +{ + SvTreeListEntry* pEntry = GetEntryOnPos( nPos ); + DBG_ASSERT( pEntry, "SvTabListBox::GetCellText(): Invalid Entry" ); + OUString aResult; + if (pEntry && pEntry->ItemCount() > o3tl::make_unsigned(nCol+1)) + { + const SvLBoxItem& rStr = pEntry->GetItem( nCol + 1 ); + if (rStr.GetType() == SvLBoxItemType::String) + aResult = static_cast<const SvLBoxString&>(rStr).GetText(); + } + return aResult; +} + +sal_uInt32 SvTabListBox::GetEntryPos( const SvTreeListEntry* pEntry ) const +{ + sal_uInt32 nPos = 0; + SvTreeListEntry* pTmpEntry = First(); + while( pTmpEntry ) + { + if ( pTmpEntry == pEntry ) + return nPos; + pTmpEntry = Next( pTmpEntry ); + ++nPos; + } + return 0xffffffff; +} + +// static +std::u16string_view SvTabListBox::GetToken( std::u16string_view sStr, sal_Int32& nIndex ) +{ + return o3tl::getToken(sStr, 0, '\t', nIndex); +} + +OUString SvTabListBox::GetTabEntryText( sal_uInt32 nPos, sal_uInt16 nCol ) const +{ + SvTreeListEntry* pEntry = SvTreeListBox::GetEntry( nPos ); + DBG_ASSERT( pEntry, "GetTabEntryText(): Invalid entry " ); + OUStringBuffer aResult; + if ( pEntry ) + { + sal_uInt16 nCount = pEntry->ItemCount(); + sal_uInt16 nCur = 0; + while( nCur < nCount ) + { + const SvLBoxItem& rBoxItem = pEntry->GetItem( nCur ); + if (rBoxItem.GetType() == SvLBoxItemType::String) + { + if ( nCol == 0xffff ) + { + if (!aResult.isEmpty()) + aResult.append("\t"); + aResult.append(static_cast<const SvLBoxString&>(rBoxItem).GetText()); + } + else + { + if ( nCol == 0 ) + { + OUString sRet = static_cast<const SvLBoxString&>(rBoxItem).GetText(); + if ( sRet.isEmpty() ) + sRet = VclResId( STR_SVT_ACC_EMPTY_FIELD ); + return sRet; + } + --nCol; + } + } + ++nCur; + } + } + return aResult.makeStringAndClear(); +} + +SvTreeListEntry* SvTabListBox::GetEntryOnPos( sal_uInt32 _nEntryPos ) const +{ + SvTreeListEntry* pEntry = nullptr; + sal_uInt32 i, nPos = 0, nCount = GetLevelChildCount( nullptr ); + for ( i = 0; i < nCount; ++i ) + { + SvTreeListEntry* pParent = GetEntry(i); + if ( nPos == _nEntryPos ) + { + pEntry = pParent; + break; + } + else + { + nPos++; + pEntry = GetChildOnPos( pParent, _nEntryPos, nPos ); + if ( pEntry ) + break; + } + } + + return pEntry; +} + +SvTreeListEntry* SvTabListBox::GetChildOnPos( SvTreeListEntry* _pParent, sal_uInt32 _nEntryPos, sal_uInt32& _rPos ) const +{ + sal_uInt32 i, nCount = GetLevelChildCount( _pParent ); + for ( i = 0; i < nCount; ++i ) + { + SvTreeListEntry* pParent = GetEntry( _pParent, i ); + if ( _rPos == _nEntryPos ) + return pParent; + else + { + _rPos++; + SvTreeListEntry* pEntry = GetChildOnPos( pParent, _nEntryPos, _rPos ); + if ( pEntry ) + return pEntry; + } + } + + return nullptr; +} + +void SvTabListBox::SetTabJustify( sal_uInt16 nTab, SvTabJustify eJustify) +{ + DBG_ASSERT(nTab<mvTabList.size(),"GetTabPos:Invalid Tab"); + if( nTab >= mvTabList.size() ) + return; + SvLBoxTab& rTab = mvTabList[ nTab ]; + SvLBoxTabFlags nFlags = rTab.nFlags; + nFlags &= ~MYTABMASK; + // see SvLBoxTab::CalcOffset for force, which only matters for centering + nFlags |= static_cast<SvLBoxTabFlags>(eJustify) | SvLBoxTabFlags::FORCE; + rTab.nFlags = nFlags; + SvTreeListBox::nTreeFlags |= SvTreeFlags::RECALCTABS; + if( IsUpdateMode() ) + Invalidate(); +} + +void SvTabListBox::SetTabEditable(sal_uInt16 nTab, bool bEditable) +{ + DBG_ASSERT(nTab<mvTabList.size(),"GetTabPos:Invalid Tab"); + if( nTab >= mvTabList.size() ) + return; + SvLBoxTab& rTab = mvTabList[ nTab ]; + if (bEditable) + rTab.nFlags |= SvLBoxTabFlags::EDITABLE; + else + rTab.nFlags &= ~SvLBoxTabFlags::EDITABLE; +} + +tools::Long SvTabListBox::GetLogicTab( sal_uInt16 nTab ) +{ + if( SvTreeListBox::nTreeFlags & SvTreeFlags::RECALCTABS ) + SetTabs(); + + DBG_ASSERT(nTab<mvTabList.size(),"GetTabPos:Invalid Tab"); + return aTabs[ nTab ]->GetPos(); +} + +namespace vcl +{ + struct SvHeaderTabListBoxImpl + { + VclPtr<HeaderBar> m_pHeaderBar; + AccessibleFactoryAccess m_aFactoryAccess; + + SvHeaderTabListBoxImpl() : m_pHeaderBar( nullptr ) { } + }; +} + +SvHeaderTabListBox::SvHeaderTabListBox( vcl::Window* pParent, WinBits nWinStyle ) + : SvTabListBox(pParent, nWinStyle) + , m_bFirstPaint(true) + , m_pImpl(new ::vcl::SvHeaderTabListBoxImpl) + , m_pAccessible(nullptr) +{ +} + +SvHeaderTabListBox::~SvHeaderTabListBox() +{ + disposeOnce(); +} + +void SvHeaderTabListBox::dispose() +{ + m_pImpl.reset(); + SvTabListBox::dispose(); +} + +void SvHeaderTabListBox::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) +{ + if (m_bFirstPaint) + { + m_bFirstPaint = false; + } + SvTabListBox::Paint(rRenderContext, rRect); +} + +void SvHeaderTabListBox::InitHeaderBar( HeaderBar* pHeaderBar ) +{ + DBG_ASSERT( !m_pImpl->m_pHeaderBar, "header bar already initialized" ); + DBG_ASSERT( pHeaderBar, "invalid header bar initialization" ); + m_pImpl->m_pHeaderBar = pHeaderBar; + SetScrolledHdl( LINK( this, SvHeaderTabListBox, ScrollHdl_Impl ) ); + m_pImpl->m_pHeaderBar->SetCreateAccessibleHdl( LINK( this, SvHeaderTabListBox, CreateAccessibleHdl_Impl ) ); +} + +HeaderBar* SvHeaderTabListBox::GetHeaderBar() +{ + return m_pImpl ? m_pImpl->m_pHeaderBar : nullptr; +} + +bool SvHeaderTabListBox::IsItemChecked( SvTreeListEntry* pEntry, sal_uInt16 nCol ) +{ + SvButtonState eState = SvButtonState::Unchecked; + SvLBoxButton& rItem = static_cast<SvLBoxButton&>( pEntry->GetItem( nCol + 1 ) ); + + if (rItem.GetType() == SvLBoxItemType::Button) + { + SvItemStateFlags nButtonFlags = rItem.GetButtonFlags(); + eState = SvLBoxButtonData::ConvertToButtonState( nButtonFlags ); + } + + return ( eState == SvButtonState::Checked ); +} + +SvTreeListEntry* SvHeaderTabListBox::InsertEntryToColumn( + const OUString& rStr, SvTreeListEntry* pParent, sal_uInt32 nPos, sal_uInt16 nCol, void* pUserData ) +{ + SvTreeListEntry* pEntry = SvTabListBox::InsertEntryToColumn( rStr, pParent, nPos, nCol, pUserData ); + RecalculateAccessibleChildren(); + return pEntry; +} + +sal_uInt32 SvHeaderTabListBox::Insert( + SvTreeListEntry* pEnt, SvTreeListEntry* pPar, sal_uInt32 nPos ) +{ + sal_uInt32 n = SvTabListBox::Insert( pEnt, pPar, nPos ); + RecalculateAccessibleChildren(); + return n; +} + +sal_uInt32 SvHeaderTabListBox::Insert( SvTreeListEntry* pEntry, sal_uInt32 nRootPos ) +{ + sal_uInt32 nPos = SvTabListBox::Insert( pEntry, nRootPos ); + RecalculateAccessibleChildren(); + return nPos; +} + +void SvHeaderTabListBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + SvTabListBox::DumpAsPropertyTree(rJsonWriter); + + auto aHeaders = rJsonWriter.startArray("headers"); + + HeaderBar* pHeaderBar = GetHeaderBar(); + for(sal_uInt16 i = 0; i < pHeaderBar->GetItemCount(); i++) + { + auto aNode = rJsonWriter.startStruct(); + rJsonWriter.put("text", pHeaderBar->GetItemText(pHeaderBar->GetItemId(i))); + } +} + +IMPL_LINK_NOARG(SvHeaderTabListBox, ScrollHdl_Impl, SvTreeListBox*, void) +{ + m_pImpl->m_pHeaderBar->SetOffset( -GetXOffset() ); +} + +IMPL_LINK_NOARG(SvHeaderTabListBox, CreateAccessibleHdl_Impl, HeaderBar*, void) +{ + vcl::Window* pParent = m_pImpl->m_pHeaderBar->GetAccessibleParentWindow(); + DBG_ASSERT( pParent, "SvHeaderTabListBox..CreateAccessibleHdl_Impl - accessible parent not found" ); + if ( pParent ) + { + css::uno::Reference< XAccessible > xAccParent = pParent->GetAccessible(); + if ( xAccParent.is() ) + { + Reference< XAccessible > xAccessible = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleBrowseBoxHeaderBar( + xAccParent, *this, AccessibleBrowseBoxObjType::ColumnHeaderBar ); + m_pImpl->m_pHeaderBar->SetAccessible( xAccessible ); + } + } +} + +void SvHeaderTabListBox::RecalculateAccessibleChildren() +{ + if ( !m_aAccessibleChildren.empty() ) + { + sal_uInt32 nCount = ( GetRowCount() + 1 ) * GetColumnCount(); + if ( m_aAccessibleChildren.size() < nCount ) + m_aAccessibleChildren.resize( nCount ); + else + { + DBG_ASSERT( m_aAccessibleChildren.size() == nCount, "wrong children count" ); + } + } +} + +bool SvHeaderTabListBox::IsCellCheckBox( sal_Int32 _nRow, sal_uInt16 _nColumn, TriState& _rState ) const +{ + bool bRet = false; + SvTreeListEntry* pEntry = GetEntry( _nRow ); + if ( pEntry ) + { + sal_uInt16 nItemCount = pEntry->ItemCount(); + if ( nItemCount > ( _nColumn + 1 ) ) + { + SvLBoxItem& rItem = pEntry->GetItem( _nColumn + 1 ); + if (rItem.GetType() == SvLBoxItemType::Button) + { + bRet = true; + _rState = ( ( static_cast<SvLBoxButton&>(rItem).GetButtonFlags() & SvItemStateFlags::UNCHECKED ) == SvItemStateFlags::NONE ) + ? TRISTATE_TRUE : TRISTATE_FALSE; + } + } + else + { + SAL_WARN( "svtools.contnr", "SvHeaderTabListBox::IsCellCheckBox(): column out of range" ); + } + } + return bRet; +} +sal_Int32 SvHeaderTabListBox::GetRowCount() const +{ + return GetEntryCount(); +} + +sal_uInt16 SvHeaderTabListBox::GetColumnCount() const +{ + return m_pImpl->m_pHeaderBar->GetItemCount(); +} + +sal_Int32 SvHeaderTabListBox::GetCurrRow() const +{ + sal_Int32 nRet = -1; + SvTreeListEntry* pEntry = GetCurEntry(); + if ( pEntry ) + { + sal_uInt32 nCount = GetEntryCount(); + for ( sal_uInt32 i = 0; i < nCount; ++i ) + { + if ( pEntry == GetEntry(i) ) + { + nRet = i; + break; + } + } + } + + return nRet; +} + +sal_uInt16 SvHeaderTabListBox::GetCurrColumn() const +{ + return 0; +} + +OUString SvHeaderTabListBox::GetRowDescription( sal_Int32 _nRow ) const +{ + return GetEntryText( _nRow ); +} + +OUString SvHeaderTabListBox::GetColumnDescription( sal_uInt16 _nColumn ) const +{ + return m_pImpl->m_pHeaderBar->GetItemText( m_pImpl->m_pHeaderBar->GetItemId( _nColumn ) ); +} + +bool SvHeaderTabListBox::HasRowHeader() const +{ + return false; +} + +bool SvHeaderTabListBox::GoToCell( sal_Int32 /*_nRow*/, sal_uInt16 /*_nColumn*/ ) +{ + return false; +} + +void SvHeaderTabListBox::SetNoSelection() +{ + SvTreeListBox::SelectAll(false); +} + +void SvHeaderTabListBox::SelectAll() +{ + SvTreeListBox::SelectAll(true); +} + +void SvHeaderTabListBox::SelectRow( sal_Int32 _nRow, bool _bSelect, bool ) +{ + Select( GetEntry( _nRow ), _bSelect ); +} + +void SvHeaderTabListBox::SelectColumn( sal_uInt16, bool ) +{ +} + +sal_Int32 SvHeaderTabListBox::GetSelectedRowCount() const +{ + return GetSelectionCount(); +} + +sal_Int32 SvHeaderTabListBox::GetSelectedColumnCount() const +{ + return 0; +} + +bool SvHeaderTabListBox::IsRowSelected( sal_Int32 _nRow ) const +{ + SvTreeListEntry* pEntry = GetEntry( _nRow ); + return ( pEntry && IsSelected( pEntry ) ); +} + +bool SvHeaderTabListBox::IsColumnSelected( sal_Int32 ) const +{ + return false; +} + +void SvHeaderTabListBox::GetAllSelectedRows( css::uno::Sequence< sal_Int32 >& ) const +{ +} + +void SvHeaderTabListBox::GetAllSelectedColumns( css::uno::Sequence< sal_Int32 >& ) const +{ +} + +bool SvHeaderTabListBox::IsCellVisible( sal_Int32, sal_uInt16 ) const +{ + return true; +} + +OUString SvHeaderTabListBox::GetAccessibleCellText( sal_Int32 _nRow, sal_uInt16 _nColumnPos ) const +{ + return GetTabEntryText(_nRow, _nColumnPos); +} + +tools::Rectangle SvHeaderTabListBox::calcHeaderRect( bool _bIsColumnBar, bool _bOnScreen ) +{ + tools::Rectangle aRect; + if ( _bIsColumnBar ) + { + vcl::Window* pParent = nullptr; + if ( !_bOnScreen ) + pParent = m_pImpl->m_pHeaderBar->GetAccessibleParentWindow(); + + aRect = m_pImpl->m_pHeaderBar->GetWindowExtentsRelative( pParent ); + } + return aRect; +} + +tools::Rectangle SvHeaderTabListBox::calcTableRect( bool _bOnScreen ) +{ + vcl::Window* pParent = nullptr; + if ( !_bOnScreen ) + pParent = GetAccessibleParentWindow(); + + tools::Rectangle aRect( GetWindowExtentsRelative( pParent ) ); + return aRect; +} + +tools::Rectangle SvHeaderTabListBox::GetFieldRectPixelAbs( sal_Int32 _nRow, sal_uInt16 _nColumn, bool _bIsHeader, bool _bOnScreen ) +{ + DBG_ASSERT( !_bIsHeader || 0 == _nRow, "invalid parameters" ); + tools::Rectangle aRect; + SvTreeListEntry* pEntry = GetEntry( _nRow ); + if ( pEntry ) + { + aRect = _bIsHeader ? calcHeaderRect( true, false ) : GetBoundingRect( pEntry ); + Point aTopLeft = aRect.TopLeft(); + DBG_ASSERT( m_pImpl->m_pHeaderBar->GetItemCount() > _nColumn, "invalid column" ); + tools::Rectangle aItemRect = m_pImpl->m_pHeaderBar->GetItemRect( m_pImpl->m_pHeaderBar->GetItemId( _nColumn ) ); + aTopLeft.setX( aItemRect.Left() ); + Size aSize = aItemRect.GetSize(); + aRect = tools::Rectangle( aTopLeft, aSize ); + vcl::Window* pParent = nullptr; + if ( !_bOnScreen ) + pParent = GetAccessibleParentWindow(); + aTopLeft = aRect.TopLeft(); + aTopLeft += GetWindowExtentsRelative( pParent ).TopLeft(); + aRect = tools::Rectangle( aTopLeft, aRect.GetSize() ); + } + + return aRect; +} + +Reference< XAccessible > SvHeaderTabListBox::CreateAccessibleCell( sal_Int32 _nRow, sal_uInt16 _nColumnPos ) +{ + OSL_ENSURE( m_pAccessible, "Invalid call: Accessible is null" ); + + Reference< XAccessible > xChild; + + TriState eState = TRISTATE_INDET; + bool bIsCheckBox = IsCellCheckBox( _nRow, _nColumnPos, eState ); + if ( bIsCheckBox ) + xChild = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleCheckBoxCell( + m_pAccessible->getHeaderBar(), *this, nullptr, _nRow, _nColumnPos, eState, false ); + else + xChild = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleBrowseBoxTableCell( + m_pAccessible->getHeaderBar(), *this, nullptr, _nRow, _nColumnPos, OFFSET_NONE ); + + return xChild; +} + +Reference< XAccessible > SvHeaderTabListBox::CreateAccessibleRowHeader( sal_Int32 ) +{ + Reference< XAccessible > xHeader; + return xHeader; +} + +Reference< XAccessible > SvHeaderTabListBox::CreateAccessibleColumnHeader( sal_uInt16 _nColumn ) +{ + // first call? -> initial list + if ( m_aAccessibleChildren.empty() ) + { + const sal_uInt16 nColumnCount = GetColumnCount(); + m_aAccessibleChildren.assign( nColumnCount, Reference< XAccessible >() ); + } + + // get header + Reference< XAccessible > xChild = m_aAccessibleChildren[ _nColumn ]; + // already exists? + if ( !xChild.is() && m_pAccessible ) + { + // no -> create new header cell + xChild = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleBrowseBoxHeaderCell( + _nColumn, m_pAccessible->getHeaderBar(), + *this, nullptr, AccessibleBrowseBoxObjType::ColumnHeaderCell + ); + + // insert into list + m_aAccessibleChildren[ _nColumn ] = xChild; + } + return xChild; +} + +sal_Int32 SvHeaderTabListBox::GetAccessibleControlCount() const +{ + return -1; +} + +Reference< XAccessible > SvHeaderTabListBox::CreateAccessibleControl( sal_Int32 ) +{ + Reference< XAccessible > xControl; + return xControl; +} + +bool SvHeaderTabListBox::ConvertPointToControlIndex( sal_Int32&, const Point& ) +{ + return false; +} + +bool SvHeaderTabListBox::ConvertPointToCellAddress( sal_Int32&, sal_uInt16&, const Point& ) +{ + return false; +} + +bool SvHeaderTabListBox::ConvertPointToRowHeader( sal_Int32&, const Point& ) +{ + return false; +} + +bool SvHeaderTabListBox::ConvertPointToColumnHeader( sal_uInt16&, const Point& ) +{ + return false; +} + +OUString SvHeaderTabListBox::GetAccessibleObjectName( AccessibleBrowseBoxObjType _eType, sal_Int32 _nPos ) const +{ + OUString aRetText; + switch( _eType ) + { + case AccessibleBrowseBoxObjType::BrowseBox: + case AccessibleBrowseBoxObjType::Table: + case AccessibleBrowseBoxObjType::ColumnHeaderBar: + // should be empty now (see #i63983) + aRetText.clear(); + break; + + case AccessibleBrowseBoxObjType::TableCell: + { + // here we need a valid pos, we can not handle -1 + if ( _nPos >= 0 ) + { + sal_uInt16 nColumnCount = GetColumnCount(); + if (nColumnCount > 0) + { + sal_Int32 nRow = _nPos / nColumnCount; + sal_uInt16 nColumn = static_cast< sal_uInt16 >( _nPos % nColumnCount ); + aRetText = GetCellText( nRow, nColumn ); + } + } + break; + } + case AccessibleBrowseBoxObjType::CheckBoxCell: + { + break; // checkbox cells have no name + } + case AccessibleBrowseBoxObjType::ColumnHeaderCell: + { + aRetText = m_pImpl->m_pHeaderBar->GetItemText( m_pImpl->m_pHeaderBar->GetItemId( static_cast<sal_uInt16>(_nPos) ) ); + break; + } + + case AccessibleBrowseBoxObjType::RowHeaderBar: + case AccessibleBrowseBoxObjType::RowHeaderCell: + aRetText = "error"; + break; + + default: + OSL_FAIL("BrowseBox::GetAccessibleName: invalid enum!"); + } + return aRetText; +} + +OUString SvHeaderTabListBox::GetAccessibleObjectDescription( AccessibleBrowseBoxObjType _eType, sal_Int32 _nPos ) const +{ + OUString aRetText; + + if( _eType == AccessibleBrowseBoxObjType::TableCell && _nPos != -1 ) + { + sal_uInt16 nColumnCount = GetColumnCount(); + if (nColumnCount > 0) + { + sal_Int32 nRow = _nPos / nColumnCount; + sal_uInt16 nColumn = static_cast< sal_uInt16 >( _nPos % nColumnCount ); + + OUString aText( VclResId(STR_SVT_ACC_DESC_TABLISTBOX) ); + aText = aText.replaceFirst( "%1", OUString::number( nRow ) ); + OUString sColHeader = m_pImpl->m_pHeaderBar->GetItemText( m_pImpl->m_pHeaderBar->GetItemId( nColumn ) ); + if ( sColHeader.isEmpty() ) + sColHeader = OUString::number( nColumn ); + aText = aText.replaceFirst( "%2", sColHeader ); + aRetText = aText; + } + } + + return aRetText; +} + +void SvHeaderTabListBox::FillAccessibleStateSet( ::utl::AccessibleStateSetHelper& _rStateSet, AccessibleBrowseBoxObjType _eType ) const +{ + switch( _eType ) + { + case AccessibleBrowseBoxObjType::BrowseBox: + case AccessibleBrowseBoxObjType::Table: + { + _rStateSet.AddState( AccessibleStateType::FOCUSABLE ); + if ( HasFocus() ) + _rStateSet.AddState( AccessibleStateType::FOCUSED ); + if ( IsActive() ) + _rStateSet.AddState( AccessibleStateType::ACTIVE ); + if ( IsEnabled() ) + { + _rStateSet.AddState( AccessibleStateType::ENABLED ); + _rStateSet.AddState( AccessibleStateType::SENSITIVE ); + } + if ( IsReallyVisible() ) + _rStateSet.AddState( AccessibleStateType::VISIBLE ); + if ( _eType == AccessibleBrowseBoxObjType::Table ) + { + + _rStateSet.AddState( AccessibleStateType::MANAGES_DESCENDANTS ); + _rStateSet.AddState( AccessibleStateType::MULTI_SELECTABLE ); + } + break; + } + + case AccessibleBrowseBoxObjType::ColumnHeaderBar: + { + sal_Int32 nCurRow = GetCurrRow(); + sal_uInt16 nCurColumn = GetCurrColumn(); + if ( IsCellVisible( nCurRow, nCurColumn ) ) + _rStateSet.AddState( AccessibleStateType::VISIBLE ); + if ( IsEnabled() ) + _rStateSet.AddState( AccessibleStateType::ENABLED ); + _rStateSet.AddState( AccessibleStateType::TRANSIENT ); + break; + } + + case AccessibleBrowseBoxObjType::RowHeaderCell: + case AccessibleBrowseBoxObjType::ColumnHeaderCell: + { + _rStateSet.AddState( AccessibleStateType::VISIBLE ); + _rStateSet.AddState( AccessibleStateType::FOCUSABLE ); + _rStateSet.AddState( AccessibleStateType::TRANSIENT ); + if ( IsEnabled() ) + _rStateSet.AddState( AccessibleStateType::ENABLED ); + break; + } + default: + break; + } +} + +void SvHeaderTabListBox::FillAccessibleStateSetForCell( ::utl::AccessibleStateSetHelper& _rStateSet, sal_Int32 _nRow, sal_uInt16 _nColumn ) const +{ + _rStateSet.AddState( AccessibleStateType::SELECTABLE ); + _rStateSet.AddState( AccessibleStateType::TRANSIENT ); + + if ( IsCellVisible( _nRow, _nColumn ) ) + { + _rStateSet.AddState( AccessibleStateType::VISIBLE ); + _rStateSet.AddState( AccessibleStateType::ENABLED ); + } + + if ( IsRowSelected( _nRow ) ) + { + _rStateSet.AddState( AccessibleStateType::ACTIVE ); + _rStateSet.AddState( AccessibleStateType::SELECTED ); + } + if ( IsEnabled() ) + _rStateSet.AddState( AccessibleStateType::ENABLED ); +} + +void SvHeaderTabListBox::GrabTableFocus() +{ + GrabFocus(); +} + +bool SvHeaderTabListBox::GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr, int nIndex, int nLen, std::vector< tools::Rectangle >& rVector ) +{ + return GetOutDev()->GetGlyphBoundRects( rOrigin, rStr, nIndex, nLen, rVector ); +} + +tools::Rectangle SvHeaderTabListBox::GetWindowExtentsRelative(const vcl::Window *pRelativeWindow) const +{ + return Control::GetWindowExtentsRelative( pRelativeWindow ); +} + +void SvHeaderTabListBox::GrabFocus() +{ + Control::GrabFocus(); +} + +Reference< XAccessible > SvHeaderTabListBox::GetAccessible() +{ + return Control::GetAccessible(); +} + +vcl::Window* SvHeaderTabListBox::GetAccessibleParentWindow() const +{ + return Control::GetAccessibleParentWindow(); +} + +vcl::Window* SvHeaderTabListBox::GetWindowInstance() +{ + return this; +} + +Reference< XAccessible > SvHeaderTabListBox::CreateAccessible() +{ + vcl::Window* pParent = GetAccessibleParentWindow(); + DBG_ASSERT( pParent, "SvHeaderTabListBox::::CreateAccessible - accessible parent not found" ); + + Reference< XAccessible > xAccessible; + if ( m_pAccessible ) xAccessible = m_pAccessible->getMyself(); + + if( pParent && !m_pAccessible ) + { + Reference< XAccessible > xAccParent = pParent->GetAccessible(); + if ( xAccParent.is() ) + { + m_pAccessible = m_pImpl->m_aFactoryAccess.getFactory().createAccessibleTabListBox( xAccParent, *this ); + if ( m_pAccessible ) + xAccessible = m_pAccessible->getMyself(); + } + } + return xAccessible; +} + +tools::Rectangle SvHeaderTabListBox::GetFieldCharacterBounds(sal_Int32,sal_Int32,sal_Int32) +{ + return tools::Rectangle(); +} + +sal_Int32 SvHeaderTabListBox::GetFieldIndexAtPoint(sal_Int32 _nRow,sal_Int32 _nColumnPos,const Point& _rPoint) +{ + OUString sText = GetAccessibleCellText( _nRow, static_cast< sal_uInt16 >( _nColumnPos ) ); + std::vector< tools::Rectangle > aRects; + if ( GetGlyphBoundRects(Point(0,0), sText, 0, sText.getLength(), aRects) ) + { + sal_Int32 nPos = 0; + for (auto const& rectangle : aRects) + { + if( rectangle.Contains(_rPoint) ) + return nPos; + ++nPos; + } + } + + return -1; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/transfer.cxx b/vcl/source/treelist/transfer.cxx new file mode 100644 index 000000000..4506fa2ff --- /dev/null +++ b/vcl/source/treelist/transfer.cxx @@ -0,0 +1,2227 @@ +/* -*- 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 . + */ + +#ifdef _WIN32 +#include <prewin.h> +#include <postwin.h> +#include <shlobj.h> +#endif +#include <o3tl/char16_t2wchar_t.hxx> +#include <osl/mutex.hxx> +#include <rtl/uri.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <tools/urlobj.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <sot/exchange.hxx> +#include <sot/storage.hxx> +#include <vcl/bitmap.hxx> +#include <vcl/filter/SvmReader.hxx> +#include <vcl/filter/SvmWriter.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/graph.hxx> +#include <vcl/cvtgrf.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <comphelper/fileformat.h> +#include <comphelper/processfactory.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/sequence.hxx> +#include <sot/filelist.hxx> +#include <cppuhelper/implbase.hxx> + +#include <comphelper/seqstream.hxx> +#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp> +#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp> +#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp> +#include <com/sun/star/datatransfer/XMimeContentType.hpp> +#include <com/sun/star/datatransfer/XTransferable2.hpp> +#include <com/sun/star/frame/Desktop.hpp> + +#include <svl/urlbmk.hxx> +#include <vcl/inetimg.hxx> +#include <vcl/wmf.hxx> +#include <vcl/imap.hxx> +#include <vcl/transfer.hxx> +#include <rtl/strbuf.hxx> +#include <cstdio> +#include <vcl/dibtools.hxx> +#include <vcl/filter/PngImageReader.hxx> +#include <vcl/pngwrite.hxx> +#include <vcl/graphicfilter.hxx> +#include <memory> +#include <utility> +#include <vcl/TypeSerializer.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::datatransfer; +using namespace ::com::sun::star::datatransfer::clipboard; +using namespace ::com::sun::star::datatransfer::dnd; +using namespace std::literals::string_view_literals; + + +#define TOD_SIG1 0x01234567 +#define TOD_SIG2 0x89abcdef + +SvStream& WriteTransferableObjectDescriptor( SvStream& rOStm, const TransferableObjectDescriptor& rObjDesc ) +{ + const sal_uInt32 nFirstPos = rOStm.Tell(), nViewAspect = rObjDesc.mnViewAspect; + const sal_uInt32 nSig1 = TOD_SIG1, nSig2 = TOD_SIG2; + + rOStm.SeekRel( 4 ); + WriteSvGlobalName( rOStm, rObjDesc.maClassName ); + rOStm.WriteUInt32( nViewAspect ); + rOStm.WriteInt32( rObjDesc.maSize.Width() ); + rOStm.WriteInt32( rObjDesc.maSize.Height() ); + rOStm.WriteInt32( rObjDesc.maDragStartPos.X() ); + rOStm.WriteInt32( rObjDesc.maDragStartPos.Y() ); + rOStm.WriteUniOrByteString( rObjDesc.maTypeName, osl_getThreadTextEncoding() ); + rOStm.WriteUniOrByteString( rObjDesc.maDisplayName, osl_getThreadTextEncoding() ); + rOStm.WriteUInt32( nSig1 ).WriteUInt32( nSig2 ); + + const sal_uInt32 nLastPos = rOStm.Tell(); + + rOStm.Seek( nFirstPos ); + rOStm.WriteUInt32( nLastPos - nFirstPos ); + rOStm.Seek( nLastPos ); + + return rOStm; +} + + +// the reading of the parameter is done using the special service css::datatransfer::MimeContentType, +// a similar approach should be implemented for creation of the mimetype string; +// for now the set of acceptable characters has to be hardcoded, in future it should be part of the service that creates the mimetype + +static OUString ImplGetParameterString( const TransferableObjectDescriptor& rObjDesc ) +{ + const OUString aClassName( rObjDesc.maClassName.GetHexName() ); + OUString aParams; + + if( !aClassName.isEmpty() ) + { + aParams += ";classname=\"" + aClassName + "\""; + } + + if( !rObjDesc.maTypeName.isEmpty() ) + { + aParams += ";typename=\"" + rObjDesc.maTypeName + "\""; + } + + if( !rObjDesc.maDisplayName.isEmpty() ) + { + // the display name might contain unacceptable characters, encode all of them + // this seems to be the only parameter currently that might contain such characters + static constexpr auto pToAccept = rtl::createUriCharClass( + u8"()<>@,;:/[]?=!#$&'*+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~. "); + + aParams += ";displayname=\"" + + rtl::Uri::encode( + rObjDesc.maDisplayName, pToAccept.data(), rtl_UriEncodeIgnoreEscapes, + RTL_TEXTENCODING_UTF8) + + "\""; + } + + aParams += ";viewaspect=\"" + OUString::number(rObjDesc.mnViewAspect) + + "\";width=\"" + OUString::number(rObjDesc.maSize.Width()) + + "\";height=\"" + OUString::number(rObjDesc.maSize.Height()) + + "\";posx=\"" + OUString::number(rObjDesc.maDragStartPos.X()) + + "\";posy=\"" + OUString::number(rObjDesc.maDragStartPos.X()) + "\""; + + return aParams; +} + + +static void ImplSetParameterString( TransferableObjectDescriptor& rObjDesc, const DataFlavorEx& rFlavorEx ) +{ + Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + + try + { + Reference< XMimeContentTypeFactory > xMimeFact = MimeContentTypeFactory::create( xContext ); + + Reference< XMimeContentType > xMimeType( xMimeFact->createMimeContentType( rFlavorEx.MimeType ) ); + + if( xMimeType.is() ) + { + static const OUStringLiteral aClassNameString( u"classname" ); + static const OUStringLiteral aTypeNameString( u"typename" ); + static const OUStringLiteral aDisplayNameString( u"displayname" ); + static const OUStringLiteral aViewAspectString( u"viewaspect" ); + static const OUStringLiteral aWidthString( u"width" ); + static const OUStringLiteral aHeightString( u"height" ); + static const OUStringLiteral aPosXString( u"posx" ); + static const OUStringLiteral aPosYString( u"posy" ); + + if( xMimeType->hasParameter( aClassNameString ) ) + { + rObjDesc.maClassName.MakeId( xMimeType->getParameterValue( aClassNameString ) ); + } + + if( xMimeType->hasParameter( aTypeNameString ) ) + { + rObjDesc.maTypeName = xMimeType->getParameterValue( aTypeNameString ); + } + + if( xMimeType->hasParameter( aDisplayNameString ) ) + { + // the display name might contain unacceptable characters, in this case they should be encoded + // this seems to be the only parameter currently that might contain such characters + rObjDesc.maDisplayName = ::rtl::Uri::decode( xMimeType->getParameterValue( aDisplayNameString ), rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 ); + } + + if( xMimeType->hasParameter( aViewAspectString ) ) + { + rObjDesc.mnViewAspect = static_cast< sal_uInt16 >( xMimeType->getParameterValue( aViewAspectString ).toInt32() ); + } + + if( xMimeType->hasParameter( aWidthString ) ) + { + rObjDesc.maSize.setWidth( xMimeType->getParameterValue( aWidthString ).toInt32() ); + } + + if( xMimeType->hasParameter( aHeightString ) ) + { + rObjDesc.maSize.setHeight( xMimeType->getParameterValue( aHeightString ).toInt32() ); + } + + if( xMimeType->hasParameter( aPosXString ) ) + { + rObjDesc.maDragStartPos.setX( xMimeType->getParameterValue( aPosXString ).toInt32() ); + } + + if( xMimeType->hasParameter( aPosYString ) ) + { + rObjDesc.maDragStartPos.setY( xMimeType->getParameterValue( aPosYString ).toInt32() ); + } + } + } + catch( const css::uno::Exception& ) + { + } +} + + +TransferableHelper::TerminateListener::TerminateListener( TransferableHelper& rTransferableHelper ) : + mrParent( rTransferableHelper ) +{ +} + + +TransferableHelper::TerminateListener::~TerminateListener() +{ +} + + +void SAL_CALL TransferableHelper::TerminateListener::disposing( const EventObject& ) +{ +} + + +void SAL_CALL TransferableHelper::TerminateListener::queryTermination( const EventObject& ) +{ +} + + +void SAL_CALL TransferableHelper::TerminateListener::notifyTermination( const EventObject& ) +{ + mrParent.ImplFlush(); +} + +OUString SAL_CALL TransferableHelper::TerminateListener::getImplementationName() +{ + return "com.sun.star.comp.svt.TransferableHelperTerminateListener"; +} + +sal_Bool SAL_CALL TransferableHelper::TerminateListener::supportsService(const OUString& /*rServiceName*/) +{ + return false; +} + +css::uno::Sequence<OUString> TransferableHelper::TerminateListener::getSupportedServiceNames() +{ + return {}; +} + +TransferableHelper::~TransferableHelper() +{ + css::uno::Reference<css::frame::XTerminateListener> listener; + { + const SolarMutexGuard aGuard; + std::swap(listener, mxTerminateListener); + } + if (listener.is()) { + Desktop::create(comphelper::getProcessComponentContext())->removeTerminateListener( + listener); + } +} + +Any SAL_CALL TransferableHelper::getTransferData( const DataFlavor& rFlavor ) +{ + return getTransferData2(rFlavor, OUString()); +} + +Any SAL_CALL TransferableHelper::getTransferData2( const DataFlavor& rFlavor, const OUString& rDestDoc ) +{ + if( !maAny.hasValue() || maFormats.empty() || ( maLastFormat != rFlavor.MimeType ) ) + { + const SolarMutexGuard aGuard; + + maLastFormat = rFlavor.MimeType; + maAny = Any(); + + try + { + DataFlavor aSubstFlavor; + bool bDone = false; + + // add formats if not already done + if (maFormats.empty()) + AddSupportedFormats(); + + // check alien formats first and try to get a substitution format + if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aSubstFlavor ) && + TransferableDataHelper::IsEqual( aSubstFlavor, rFlavor ) ) + { + GetData(aSubstFlavor, rDestDoc); + bDone = maAny.hasValue(); + } + else if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::BMP, aSubstFlavor ) + && TransferableDataHelper::IsEqual( aSubstFlavor, rFlavor ) + && SotExchange::GetFormatDataFlavor(SotClipboardFormatId::BITMAP, aSubstFlavor)) + { + GetData(aSubstFlavor, rDestDoc); + bDone = true; + } + else if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EMF, aSubstFlavor ) && + TransferableDataHelper::IsEqual( aSubstFlavor, rFlavor ) && + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::GDIMETAFILE, aSubstFlavor ) ) + { + GetData(aSubstFlavor, rDestDoc); + + if( maAny.hasValue() ) + { + Sequence< sal_Int8 > aSeq; + + if( maAny >>= aSeq ) + { + GDIMetaFile aMtf; + { + SvMemoryStream aSrcStm( aSeq.getArray(), aSeq.getLength(), StreamMode::WRITE | StreamMode::TRUNC ); + SvmReader aReader( aSrcStm ); + aReader.Read( aMtf ); + } + + Graphic aGraphic( aMtf ); + SvMemoryStream aDstStm( 65535, 65535 ); + + if( GraphicConverter::Export( aDstStm, aGraphic, ConvertDataFormat::EMF ) == ERRCODE_NONE ) + { + maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aDstStm.GetData() ), + aDstStm.TellEnd() ); + bDone = true; + } + } + } + } + else if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::WMF, aSubstFlavor ) && + TransferableDataHelper::IsEqual( aSubstFlavor, rFlavor ) && + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::GDIMETAFILE, aSubstFlavor ) ) + { + GetData(aSubstFlavor, rDestDoc); + + if( maAny.hasValue() ) + { + Sequence< sal_Int8 > aSeq; + + if( maAny >>= aSeq ) + { + GDIMetaFile aMtf; + { + SvMemoryStream aSrcStm( aSeq.getArray(), aSeq.getLength(), StreamMode::WRITE | StreamMode::TRUNC ); + SvmReader aReader( aSrcStm ); + aReader.Read( aMtf ); + } + + SvMemoryStream aDstStm( 65535, 65535 ); + + // taking wmf without file header + if ( ConvertGDIMetaFileToWMF( aMtf, aDstStm, nullptr, false ) ) + { + maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aDstStm.GetData() ), + aDstStm.TellEnd() ); + bDone = true; + } + } + } + } + + // reset Any if substitute doesn't work + if( !bDone && maAny.hasValue() ) + maAny = Any(); + + // if any is not yet filled, use standard format + if( !maAny.hasValue() ) + GetData(rFlavor, rDestDoc); + } + catch( const css::uno::Exception& ) + { + } + + if( !maAny.hasValue() ) + throw UnsupportedFlavorException(); + } + + return maAny; +} + +sal_Bool SAL_CALL TransferableHelper::isComplex() +{ + // By default everything is complex, until proven otherwise + // in the respective document type transferable handler. + return true; +} + +Sequence< DataFlavor > SAL_CALL TransferableHelper::getTransferDataFlavors() +{ + const SolarMutexGuard aGuard; + + try + { + if(maFormats.empty()) + AddSupportedFormats(); + } + catch( const css::uno::Exception& ) + { + } + + return comphelper::containerToSequence<DataFlavor>(maFormats); +} + + +sal_Bool SAL_CALL TransferableHelper::isDataFlavorSupported( const DataFlavor& rFlavor ) +{ + const SolarMutexGuard aGuard; + + try + { + if (maFormats.empty()) + AddSupportedFormats(); + } + catch( const css::uno::Exception& ) + { + } + + for (auto const& format : maFormats) + { + if( TransferableDataHelper::IsEqual( format, rFlavor ) ) + { + return true; + } + } + + return false; +} + + +void SAL_CALL TransferableHelper::lostOwnership( const Reference< XClipboard >&, const Reference< XTransferable >& ) +{ + const SolarMutexGuard aGuard; + + try + { + if( mxTerminateListener.is() ) + { + Reference< XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() ); + xDesktop->removeTerminateListener( mxTerminateListener ); + + mxTerminateListener.clear(); + } + + ObjectReleased(); + } + catch( const css::uno::Exception& ) + { + } +} + + +void SAL_CALL TransferableHelper::disposing( const EventObject& ) +{ +} + + +void SAL_CALL TransferableHelper::dragDropEnd( const DragSourceDropEvent& rDSDE ) +{ + const SolarMutexGuard aGuard; + + try + { + DragFinished( rDSDE.DropSuccess ? ( rDSDE.DropAction & ~DNDConstants::ACTION_DEFAULT ) : DNDConstants::ACTION_NONE ); + ObjectReleased(); + } + catch( const css::uno::Exception& ) + { + } +} + + +void SAL_CALL TransferableHelper::dragEnter( const DragSourceDragEvent& ) +{ +} + + +void SAL_CALL TransferableHelper::dragExit( const DragSourceEvent& ) +{ +} + + +void SAL_CALL TransferableHelper::dragOver( const DragSourceDragEvent& ) +{ +} + + +void SAL_CALL TransferableHelper::dropActionChanged( const DragSourceDragEvent& ) +{ +} + + +sal_Int64 SAL_CALL TransferableHelper::getSomething( const Sequence< sal_Int8 >& rId ) +{ + return comphelper::getSomethingImpl(rId, this); +} + + +void TransferableHelper::ImplFlush() +{ + if( !mxClipboard.is() ) + return; + + Reference< XFlushableClipboard > xFlushableClipboard( mxClipboard, UNO_QUERY ); + SolarMutexReleaser aReleaser; + + try + { + if( xFlushableClipboard.is() ) + xFlushableClipboard->flushClipboard(); + } + catch( const css::uno::Exception& ) + { + OSL_FAIL( "Could not flush clipboard" ); + } +} + + +void TransferableHelper::AddFormat( SotClipboardFormatId nFormat ) +{ + DataFlavor aFlavor; + + if( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) ) + AddFormat( aFlavor ); +} + + +void TransferableHelper::AddFormat( const DataFlavor& rFlavor ) +{ + bool bAdd = true; + + for (auto & format : maFormats) + { + if( TransferableDataHelper::IsEqual( format, rFlavor ) ) + { + // update MimeType for SotClipboardFormatId::OBJECTDESCRIPTOR in every case + if ((SotClipboardFormatId::OBJECTDESCRIPTOR == format.mnSotId) && mxObjDesc) + { + DataFlavor aObjDescFlavor; + + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::OBJECTDESCRIPTOR, aObjDescFlavor ); + format.MimeType = aObjDescFlavor.MimeType; + format.MimeType += ::ImplGetParameterString(*mxObjDesc); + } + + bAdd = false; + break; + } + } + + if( !bAdd ) + return; + + DataFlavorEx aFlavorEx; + + aFlavorEx.MimeType = rFlavor.MimeType; + aFlavorEx.HumanPresentableName = rFlavor.HumanPresentableName; + aFlavorEx.DataType = rFlavor.DataType; + aFlavorEx.mnSotId = SotExchange::RegisterFormat( rFlavor ); + + if ((SotClipboardFormatId::OBJECTDESCRIPTOR == aFlavorEx.mnSotId) && mxObjDesc) + aFlavorEx.MimeType += ::ImplGetParameterString(*mxObjDesc); + + maFormats.push_back(aFlavorEx); + + if( SotClipboardFormatId::BITMAP == aFlavorEx.mnSotId ) + { + AddFormat( SotClipboardFormatId::PNG ); + AddFormat( SotClipboardFormatId::BMP ); + } + else if( SotClipboardFormatId::GDIMETAFILE == aFlavorEx.mnSotId ) + { + AddFormat( SotClipboardFormatId::EMF ); + AddFormat( SotClipboardFormatId::WMF ); + } +} + + +void TransferableHelper::RemoveFormat( SotClipboardFormatId nFormat ) +{ + DataFlavor aFlavor; + + if( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) ) + RemoveFormat( aFlavor ); +} + + +void TransferableHelper::RemoveFormat( const DataFlavor& rFlavor ) +{ + DataFlavorExVector::iterator aIter(maFormats.begin()); + + while (aIter != maFormats.end()) + { + if( TransferableDataHelper::IsEqual( *aIter, rFlavor ) ) + aIter = maFormats.erase(aIter); + else + ++aIter; + } +} + + +bool TransferableHelper::HasFormat( SotClipboardFormatId nFormat ) +{ + return std::any_of(maFormats.begin(), maFormats.end(), + [&](const DataFlavorEx& data) { return data.mnSotId == nFormat; }); +} + + +void TransferableHelper::ClearFormats() +{ + maFormats.clear(); + maAny.clear(); +} + + +bool TransferableHelper::SetAny( const Any& rAny ) +{ + maAny = rAny; + return maAny.hasValue(); +} + + +bool TransferableHelper::SetString( const OUString& rString ) +{ + maAny <<= rString; + return maAny.hasValue(); +} + + +bool TransferableHelper::SetBitmapEx( const BitmapEx& rBitmapEx, const DataFlavor& rFlavor ) +{ + if( !rBitmapEx.IsEmpty() ) + { + SvMemoryStream aMemStm( 65535, 65535 ); + + if(rFlavor.MimeType.equalsIgnoreAsciiCase("image/png")) + { + // write a PNG + css::uno::Sequence<css::beans::PropertyValue> aFilterData; + +#ifdef IOS + // Use faster compression on slow devices + aFilterData.realloc(aFilterData.getLength() + 1); + aFilterData.getArray()[aFilterData.getLength() - 1].Name = "Compression"; + + // We "know" that this gets passed to zlib's deflateInit2_(). 1 means best speed. For a + // typical 15 megapixel image from a DSLR, we are talking about a difference of 17 s for + // the default compression level vs 4 s for best speed, on an iPad Pro from 2017. + // + // Sure, the best would be to not have to re-encode the image at all, but have access to + // the original JPEG or PNG when there is a such. + + aFilterData.getArray()[aFilterData.getLength() - 1].Value <<= 1; +#endif + vcl::PNGWriter aPNGWriter(rBitmapEx, &aFilterData); + + aPNGWriter.Write(aMemStm); + } + else + { + // explicitly use Bitmap::Write with bCompressed = sal_False and bFileHeader = sal_True + WriteDIB(rBitmapEx.GetBitmap(), aMemStm, false, true); + } + + maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() ); + } + + return maAny.hasValue(); +} + + +bool TransferableHelper::SetGDIMetaFile( const GDIMetaFile& rMtf ) +{ + if( rMtf.GetActionSize() ) + { + SvMemoryStream aMemStm( 65535, 65535 ); + + SvmWriter aWriter( aMemStm ); + aWriter.Write( rMtf ); + maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() ); + } + + return maAny.hasValue(); +} + + +bool TransferableHelper::SetGraphic( const Graphic& rGraphic ) +{ + if( rGraphic.GetType() != GraphicType::NONE ) + { + SvMemoryStream aMemStm( 65535, 65535 ); + + aMemStm.SetVersion( SOFFICE_FILEFORMAT_50 ); + aMemStm.SetCompressMode( SvStreamCompressFlags::NATIVE ); + + TypeSerializer aSerializer(aMemStm); + aSerializer.writeGraphic(rGraphic); + + maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() ); + } + + return maAny.hasValue(); +} + + +bool TransferableHelper::SetImageMap( const ImageMap& rIMap ) +{ + SvMemoryStream aMemStm( 8192, 8192 ); + + aMemStm.SetVersion( SOFFICE_FILEFORMAT_50 ); + rIMap.Write( aMemStm ); + maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() ); + + return maAny.hasValue(); +} + + +bool TransferableHelper::SetTransferableObjectDescriptor( const TransferableObjectDescriptor& rDesc ) +{ + PrepareOLE( rDesc ); + + SvMemoryStream aMemStm( 1024, 1024 ); + + WriteTransferableObjectDescriptor( aMemStm, rDesc ); + maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.Tell() ); + + return maAny.hasValue(); + } + + +bool TransferableHelper::SetINetBookmark( const INetBookmark& rBmk, + const css::datatransfer::DataFlavor& rFlavor ) +{ + rtl_TextEncoding eSysCSet = osl_getThreadTextEncoding(); + + switch( SotExchange::GetFormat( rFlavor ) ) + { + case SotClipboardFormatId::SOLK: + { + OString sURL(OUStringToOString(rBmk.GetURL(), eSysCSet)); + OString sDesc(OUStringToOString(rBmk.GetDescription(), eSysCSet)); + OStringBuffer sOut; + sOut.append(sURL.getLength()); + sOut.append("@" + sURL); + sOut.append(sDesc.getLength()); + sOut.append("@" + sDesc); + + Sequence< sal_Int8 > aSeq(sOut.getLength()); + memcpy(aSeq.getArray(), sOut.getStr(), sOut.getLength()); + maAny <<= aSeq; + } + break; + + case SotClipboardFormatId::STRING: + case SotClipboardFormatId::UNIFORMRESOURCELOCATOR: + maAny <<= rBmk.GetURL(); + break; + + case SotClipboardFormatId::NETSCAPE_BOOKMARK: + { + Sequence< sal_Int8 > aSeq( 2048 ); + char* pSeq = reinterpret_cast< char* >( aSeq.getArray() ); + + // strncpy fills the rest with nulls, as we need + strncpy( pSeq, OUStringToOString(rBmk.GetURL(), eSysCSet).getStr(), 1024 ); + strncpy( pSeq + 1024, OUStringToOString(rBmk.GetDescription(), eSysCSet).getStr(), 1024 ); + + maAny <<= aSeq; + } + break; + +#ifdef _WIN32 + case SotClipboardFormatId::FILEGRPDESCRIPTOR: + { + Sequence< sal_Int8 > aSeq( sizeof( FILEGROUPDESCRIPTORW ) ); + FILEGROUPDESCRIPTORW* pFDesc = reinterpret_cast<FILEGROUPDESCRIPTORW*>(aSeq.getArray()); + FILEDESCRIPTORW& rFDesc1 = pFDesc->fgd[ 0 ]; + + pFDesc->cItems = 1; + memset( &rFDesc1, 0, sizeof( rFDesc1 ) ); + rFDesc1.dwFlags = FD_LINKUI; + + OUStringBuffer aStr(rBmk.GetDescription()); + for( size_t nChar = 0; (nChar = std::u16string_view(aStr).find_first_of(u"\\/:*?\"<>|"sv, nChar)) != std::u16string_view::npos; ) + aStr.remove(nChar, 1); + + aStr.insert(0, "Shortcut to "); + aStr.append(".URL"); + wcscpy( rFDesc1.cFileName, o3tl::toW(aStr.getStr()) ); + + maAny <<= aSeq; + } + break; + + case SotClipboardFormatId::FILECONTENT: + { + maAny <<= "[InternetShortcut]\x0aURL=" + rBmk.GetURL(); + } + break; +#endif + + default: + break; + } + + return maAny.hasValue(); +} + + +bool TransferableHelper::SetINetImage( const INetImage& rINtImg, + const css::datatransfer::DataFlavor& rFlavor ) +{ + SvMemoryStream aMemStm( 1024, 1024 ); + + aMemStm.SetVersion( SOFFICE_FILEFORMAT_50 ); + rINtImg.Write( aMemStm, SotExchange::GetFormat( rFlavor ) ); + + maAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.TellEnd() ); + + return maAny.hasValue(); +} + + +bool TransferableHelper::SetObject( void* pUserObject, sal_uInt32 nUserObjectId, const DataFlavor& rFlavor ) +{ + tools::SvRef<SotTempStream> xStm( new SotTempStream( OUString() ) ); + + xStm->SetVersion( SOFFICE_FILEFORMAT_50 ); + + if( pUserObject && WriteObject( xStm, pUserObject, nUserObjectId, rFlavor ) ) + { + const sal_uInt32 nLen = xStm->TellEnd(); + Sequence< sal_Int8 > aSeq( nLen ); + + xStm->Seek( STREAM_SEEK_TO_BEGIN ); + xStm->ReadBytes(aSeq.getArray(), nLen); + + if( nLen && ( SotExchange::GetFormat( rFlavor ) == SotClipboardFormatId::STRING ) ) + { + //JP 24.7.2001: as I know was this only for the writer application and this + // writes now UTF16 format into the stream + //JP 6.8.2001: and now it writes UTF8 because then exist no problem with + // little / big endians! - Bug 88121 + maAny <<= OUString( reinterpret_cast< const char* >( aSeq.getConstArray() ), nLen - 1, RTL_TEXTENCODING_UTF8 ); + } + else + maAny <<= aSeq; + } + + return maAny.hasValue(); +} + + +bool TransferableHelper::WriteObject( tools::SvRef<SotTempStream>&, void*, sal_uInt32, const DataFlavor& ) +{ + OSL_FAIL( "TransferableHelper::WriteObject( ... ) not implemented" ); + return false; +} + + +void TransferableHelper::DragFinished( sal_Int8 ) +{ +} + + +void TransferableHelper::ObjectReleased() +{ +} + + +void TransferableHelper::PrepareOLE( const TransferableObjectDescriptor& rObjDesc ) +{ + mxObjDesc.reset(new TransferableObjectDescriptor(rObjDesc)); + + if( HasFormat( SotClipboardFormatId::OBJECTDESCRIPTOR ) ) + AddFormat( SotClipboardFormatId::OBJECTDESCRIPTOR ); +} + +void TransferableHelper::CopyToClipboard(const Reference<XClipboard>& rClipboard) const +{ + if( rClipboard.is() ) + mxClipboard = rClipboard; + + if( !mxClipboard.is() || mxTerminateListener.is() ) + return; + + try + { + TransferableHelper* pThis = const_cast< TransferableHelper* >( this ); + pThis->mxTerminateListener = new TerminateListener( *pThis ); + Reference< XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() ); + xDesktop->addTerminateListener( pThis->mxTerminateListener ); + + mxClipboard->setContents( pThis, pThis ); + } + catch( const css::uno::Exception& ) + { + } +} + +void TransferableHelper::CopyToClipboard( vcl::Window *pWindow ) const +{ + DBG_ASSERT( pWindow, "Window pointer is NULL" ); + Reference< XClipboard > xClipboard; + + if( pWindow ) + xClipboard = pWindow->GetClipboard(); + + CopyToClipboard(xClipboard); +} + +void TransferableHelper::CopyToSelection(const Reference<XClipboard>& rSelection) const +{ + if( !rSelection.is() || mxTerminateListener.is() ) + return; + + try + { + TransferableHelper* pThis = const_cast< TransferableHelper* >( this ); + pThis->mxTerminateListener = new TerminateListener( *pThis ); + Reference< XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() ); + xDesktop->addTerminateListener( pThis->mxTerminateListener ); + + rSelection->setContents( pThis, pThis ); + } + catch( const css::uno::Exception& ) + { + } +} + +void TransferableHelper::CopyToPrimarySelection() const +{ + CopyToSelection(GetSystemPrimarySelection()); +} + +void TransferableHelper::StartDrag( vcl::Window* pWindow, sal_Int8 nDnDSourceActions ) + +{ + DBG_ASSERT( pWindow, "Window pointer is NULL" ); + Reference< XDragSource > xDragSource( pWindow->GetDragSource() ); + + if( !xDragSource.is() ) + return; + + /* + * #96792# release mouse before actually starting DnD. + * This is necessary for the X11 DnD implementation to work. + */ + if( pWindow->IsMouseCaptured() ) + pWindow->ReleaseMouse(); + + const Point aPt( pWindow->GetPointerPosPixel() ); + + // On macOS we are forced to execute 'startDrag' synchronously + // contrary to the XDragSource interface specification because + // we can receive drag events from the system only in the main + // thread +#if !defined(MACOSX) + SolarMutexReleaser aReleaser; +#endif + + try + { + DragGestureEvent aEvt; + aEvt.DragAction = DNDConstants::ACTION_COPY; + aEvt.DragOriginX = aPt.X(); + aEvt.DragOriginY = aPt.Y(); + aEvt.DragSource = xDragSource; + + xDragSource->startDrag( aEvt, nDnDSourceActions, DND_POINTER_NONE, DND_IMAGE_NONE, this, this ); + } + catch( const css::uno::Exception& ) + { + } +} + +void TransferableHelper::ClearPrimarySelection() +{ + Reference< XClipboard > xSelection(GetSystemPrimarySelection()); + + if( xSelection.is() ) + xSelection->setContents( nullptr, nullptr ); +} + +const Sequence< sal_Int8 >& TransferableHelper::getUnoTunnelId() +{ + static const comphelper::UnoIdInit theTransferableHelperUnoTunnelId; + return theTransferableHelperUnoTunnelId.getSeq(); +} + +namespace { + +class TransferableClipboardNotifier : public ::cppu::WeakImplHelper< XClipboardListener > +{ +private: + ::osl::Mutex& mrMutex; + Reference< XClipboardNotifier > mxNotifier; + TransferableDataHelper* mpListener; + +protected: + // XClipboardListener + virtual void SAL_CALL changedContents( const clipboard::ClipboardEvent& event ) override; + + // XEventListener + virtual void SAL_CALL disposing( const EventObject& Source ) override; + +public: + TransferableClipboardNotifier( const Reference< XClipboard >& _rxClipboard, TransferableDataHelper& _rListener, ::osl::Mutex& _rMutex ); + + /// determines whether we're currently listening + bool isListening() const { return mpListener != nullptr; } + + /// makes the instance non-functional + void dispose(); +}; + +} + +TransferableClipboardNotifier::TransferableClipboardNotifier( const Reference< XClipboard >& _rxClipboard, TransferableDataHelper& _rListener, ::osl::Mutex& _rMutex ) + :mrMutex( _rMutex ) + ,mxNotifier( _rxClipboard, UNO_QUERY ) + ,mpListener( &_rListener ) +{ + osl_atomic_increment( &m_refCount ); + { + if ( mxNotifier.is() ) + mxNotifier->addClipboardListener( this ); + else + // born dead + mpListener = nullptr; + } + osl_atomic_decrement( &m_refCount ); +} + + +void SAL_CALL TransferableClipboardNotifier::changedContents( const clipboard::ClipboardEvent& event ) +{ + SolarMutexGuard aSolarGuard; + // the SolarMutex here is necessary, since + // - we cannot call mpListener without our own mutex locked + // - Rebind respectively InitFormats (called by Rebind) will + // try to lock the SolarMutex, too + ::osl::MutexGuard aGuard( mrMutex ); + if( mpListener ) + mpListener->Rebind( event.Contents ); +} + + +void SAL_CALL TransferableClipboardNotifier::disposing( const EventObject& ) +{ + // clipboard is being disposed. Hmm. Okay, become disfunctional myself. + dispose(); +} + + +void TransferableClipboardNotifier::dispose() +{ + ::osl::MutexGuard aGuard( mrMutex ); + + Reference< XClipboardListener > xKeepMeAlive( this ); + + if ( mxNotifier.is() ) + mxNotifier->removeClipboardListener( this ); + mxNotifier.clear(); + + mpListener = nullptr; +} + +struct TransferableDataHelper_Impl +{ + ::osl::Mutex maMutex; + rtl::Reference<TransferableClipboardNotifier> mxClipboardListener; + + TransferableDataHelper_Impl() + { + } +}; + +TransferableDataHelper::TransferableDataHelper() + : mxObjDesc(new TransferableObjectDescriptor) + , mxImpl(new TransferableDataHelper_Impl) +{ +} + +TransferableDataHelper::TransferableDataHelper(const Reference< css::datatransfer::XTransferable >& rxTransferable) + : mxTransfer(rxTransferable) + , mxObjDesc(new TransferableObjectDescriptor) + , mxImpl(new TransferableDataHelper_Impl) +{ + InitFormats(); +} + +TransferableDataHelper::TransferableDataHelper(const TransferableDataHelper& rDataHelper) + : mxTransfer(rDataHelper.mxTransfer) + , mxClipboard(rDataHelper.mxClipboard) + , maFormats(rDataHelper.maFormats) + , mxObjDesc(new TransferableObjectDescriptor(*rDataHelper.mxObjDesc)) + , mxImpl(new TransferableDataHelper_Impl) +{ +} + +TransferableDataHelper::TransferableDataHelper(TransferableDataHelper&& rDataHelper) noexcept + : mxTransfer(std::move(rDataHelper.mxTransfer)) + , mxClipboard(std::move(rDataHelper.mxClipboard)) + , maFormats(std::move(rDataHelper.maFormats)) + , mxObjDesc(std::move(rDataHelper.mxObjDesc)) + , mxImpl(new TransferableDataHelper_Impl) +{ +} + +TransferableDataHelper& TransferableDataHelper::operator=( const TransferableDataHelper& rDataHelper ) +{ + if ( this != &rDataHelper ) + { + ::osl::MutexGuard aGuard(mxImpl->maMutex); + + const bool bWasClipboardListening = mxImpl->mxClipboardListener.is(); + + if (bWasClipboardListening) + StopClipboardListening(); + + mxTransfer = rDataHelper.mxTransfer; + maFormats = rDataHelper.maFormats; + mxObjDesc.reset(new TransferableObjectDescriptor(*rDataHelper.mxObjDesc)); + mxClipboard = rDataHelper.mxClipboard; + + if (bWasClipboardListening) + StartClipboardListening(); + } + + return *this; +} + +TransferableDataHelper& TransferableDataHelper::operator=(TransferableDataHelper&& rDataHelper) +{ + ::osl::MutexGuard aGuard(mxImpl->maMutex); + + const bool bWasClipboardListening = mxImpl->mxClipboardListener.is(); + + if (bWasClipboardListening) + StopClipboardListening(); + + mxTransfer = std::move(rDataHelper.mxTransfer); + maFormats = std::move(rDataHelper.maFormats); + mxObjDesc = std::move(rDataHelper.mxObjDesc); + mxClipboard = std::move(rDataHelper.mxClipboard); + + if (bWasClipboardListening) + StartClipboardListening(); + + return *this; +} + +TransferableDataHelper::~TransferableDataHelper() +{ + StopClipboardListening( ); + { + ::osl::MutexGuard aGuard(mxImpl->maMutex); + maFormats.clear(); + mxObjDesc.reset(); + } +} + +void TransferableDataHelper::FillDataFlavorExVector( const Sequence< DataFlavor >& rDataFlavorSeq, + DataFlavorExVector& rDataFlavorExVector ) +{ + try + { + Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + Reference< XMimeContentTypeFactory > xMimeFact = MimeContentTypeFactory::create( xContext ); + DataFlavorEx aFlavorEx; + static const OUStringLiteral aCharsetStr( u"charset" ); + + + for (auto const& rFlavor : rDataFlavorSeq) + { + Reference< XMimeContentType > xMimeType; + + try + { + if( !rFlavor.MimeType.isEmpty() ) + xMimeType = xMimeFact->createMimeContentType( rFlavor.MimeType ); + } + catch( const css::uno::Exception& ) + { + } + + aFlavorEx.MimeType = rFlavor.MimeType; + aFlavorEx.HumanPresentableName = rFlavor.HumanPresentableName; + aFlavorEx.DataType = rFlavor.DataType; + aFlavorEx.mnSotId = SotExchange::RegisterFormat( rFlavor ); + + rDataFlavorExVector.push_back( aFlavorEx ); + + // add additional formats for special mime types + if(SotClipboardFormatId::BMP == aFlavorEx.mnSotId || SotClipboardFormatId::PNG == aFlavorEx.mnSotId || SotClipboardFormatId::JPEG == aFlavorEx.mnSotId) + { + if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::BITMAP, aFlavorEx ) ) + { + aFlavorEx.mnSotId = SotClipboardFormatId::BITMAP; + rDataFlavorExVector.push_back( aFlavorEx ); + } + } + else if( SotClipboardFormatId::WMF == aFlavorEx.mnSotId || SotClipboardFormatId::EMF == aFlavorEx.mnSotId ) + { + if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::GDIMETAFILE, aFlavorEx ) ) + { + aFlavorEx.mnSotId = SotClipboardFormatId::GDIMETAFILE; + rDataFlavorExVector.push_back( aFlavorEx ); + } + } + else if ( SotClipboardFormatId::HTML_SIMPLE == aFlavorEx.mnSotId ) + { + // #104735# HTML_SIMPLE may also be inserted without comments + aFlavorEx.mnSotId = SotClipboardFormatId::HTML_NO_COMMENT; + rDataFlavorExVector.push_back( aFlavorEx ); + } + else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/plain" ) ) + { + // add, if it is a UTF-8 byte buffer + if( xMimeType->hasParameter( aCharsetStr ) ) + { + if( xMimeType->getParameterValue( aCharsetStr ).equalsIgnoreAsciiCase( "unicode" ) || + xMimeType->getParameterValue( aCharsetStr ).equalsIgnoreAsciiCase( "utf-16" ) ) + { + rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::STRING; + + } + } + } + else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/rtf" ) ) + { + rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::RTF; + } + else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/richtext" ) ) + { + rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::RICHTEXT; + } + else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/html" ) ) + + { + rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::HTML; + } + else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "text/uri-list" ) ) + { + rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::FILE_LIST; + } + else if( xMimeType.is() && xMimeType->getFullMediaType().equalsIgnoreAsciiCase( "application/x-openoffice-objectdescriptor-xml" ) ) + { + rDataFlavorExVector[ rDataFlavorExVector.size() - 1 ].mnSotId = SotClipboardFormatId::OBJECTDESCRIPTOR; + } + } + } + catch( const css::uno::Exception& ) + { + } +} + +void TransferableDataHelper::InitFormats() +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard(mxImpl->maMutex); + + maFormats.clear(); + mxObjDesc.reset(new TransferableObjectDescriptor); + + if( !mxTransfer.is() ) + return; + + TransferableDataHelper::FillDataFlavorExVector(mxTransfer->getTransferDataFlavors(), maFormats); + + for (auto const& format : maFormats) + { + if( SotClipboardFormatId::OBJECTDESCRIPTOR == format.mnSotId ) + { + ImplSetParameterString(*mxObjDesc, format); + break; + } + } +} + + +bool TransferableDataHelper::HasFormat( SotClipboardFormatId nFormat ) const +{ + ::osl::MutexGuard aGuard(mxImpl->maMutex); + return std::any_of(maFormats.begin(), maFormats.end(), + [&](const DataFlavorEx& data) { return data.mnSotId == nFormat; }); +} + +bool TransferableDataHelper::HasFormat( const DataFlavor& rFlavor ) const +{ + ::osl::MutexGuard aGuard(mxImpl->maMutex); + for (auto const& format : maFormats) + { + if( TransferableDataHelper::IsEqual( rFlavor, format ) ) + return true; + } + + return false; +} + +sal_uInt32 TransferableDataHelper::GetFormatCount() const +{ + ::osl::MutexGuard aGuard(mxImpl->maMutex); + return maFormats.size(); +} + +SotClipboardFormatId TransferableDataHelper::GetFormat( sal_uInt32 nFormat ) const +{ + ::osl::MutexGuard aGuard(mxImpl->maMutex); + DBG_ASSERT(nFormat < maFormats.size(), "TransferableDataHelper::GetFormat: invalid format index"); + return( ( nFormat < maFormats.size() ) ? maFormats[ nFormat ].mnSotId : SotClipboardFormatId::NONE ); +} + +DataFlavor TransferableDataHelper::GetFormatDataFlavor( sal_uInt32 nFormat ) const +{ + ::osl::MutexGuard aGuard(mxImpl->maMutex); + DBG_ASSERT(nFormat < maFormats.size(), "TransferableDataHelper::GetFormat: invalid format index"); + + DataFlavor aRet; + + if (nFormat < maFormats.size()) + aRet = maFormats[nFormat]; + + return aRet; +} + + +Reference< XTransferable > TransferableDataHelper::GetXTransferable() const +{ + Reference< XTransferable > xRet; + + if( mxTransfer.is() ) + { + try + { + xRet = mxTransfer; + + // do a dummy call to check, if this interface is valid (nasty) + xRet->getTransferDataFlavors(); + + } + catch( const css::uno::Exception& ) + { + xRet.clear(); + } + } + + return xRet; +} + + +Any TransferableDataHelper::GetAny( SotClipboardFormatId nFormat, const OUString& rDestDoc ) const +{ + Any aReturn; + + DataFlavor aFlavor; + if ( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) ) + aReturn = GetAny(aFlavor, rDestDoc); + + return aReturn; +} + +Any TransferableDataHelper::GetAny( const DataFlavor& rFlavor, const OUString& rDestDoc ) const +{ + ::osl::MutexGuard aGuard(mxImpl->maMutex); + Any aRet; + + try + { + if( mxTransfer.is() ) + { + const SotClipboardFormatId nRequestFormat = SotExchange::GetFormat( rFlavor ); + + Reference<css::datatransfer::XTransferable2> xTransfer2(mxTransfer, UNO_QUERY); + + if( nRequestFormat != SotClipboardFormatId::NONE ) + { + // try to get alien format first + for (auto const& format : maFormats) + { + if( ( nRequestFormat == format.mnSotId ) && !rFlavor.MimeType.equalsIgnoreAsciiCase( format.MimeType ) ) + { +// tdf#133365: only release solar mutex on Windows +#ifdef _WIN32 + // tdf#133527: first, make sure that we actually hold the mutex + SolarMutexGuard g; + // Our own thread may handle the nested IDataObject::GetData call, + // and try to acquire solar mutex + SolarMutexReleaser r; +#endif // _WIN32 + + if (xTransfer2.is()) + aRet = xTransfer2->getTransferData2(format, rDestDoc); + else + aRet = mxTransfer->getTransferData(format); + } + + if( aRet.hasValue() ) + break; + } + } + + if( !aRet.hasValue() ) + { +// tdf#133365: only release solar mutex on Windows +#ifdef _WIN32 + // tdf#133527: first, make sure that we actually hold the mutex + SolarMutexGuard g; + // Our own thread may handle the nested IDataObject::GetData call, + // and try to acquire solar mutex + SolarMutexReleaser r; +#endif // _WIN32 + + if (xTransfer2.is()) + aRet = xTransfer2->getTransferData2(rFlavor, rDestDoc); + else + aRet = mxTransfer->getTransferData(rFlavor); + } + } + } + catch( const css::uno::Exception& ) + { + } + + return aRet; +} + + +bool TransferableDataHelper::GetString( SotClipboardFormatId nFormat, OUString& rStr ) const +{ + DataFlavor aFlavor; + return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetString( aFlavor, rStr ) ); +} + + +bool TransferableDataHelper::GetString( const DataFlavor& rFlavor, OUString& rStr ) const +{ + Any aAny = GetAny(rFlavor, OUString()); + bool bRet = false; + + if( aAny.hasValue() ) + { + OUString aOUString; + Sequence< sal_Int8 > aSeq; + + if( aAny >>= aOUString ) + { + rStr = aOUString; + bRet = true; + } + else if( aAny >>= aSeq ) + { + + const char* pChars = reinterpret_cast< const char* >( aSeq.getConstArray() ); + sal_Int32 nLen = aSeq.getLength(); + + //JP 10.10.2001: 92930 - don't copy the last zero character into the string. + //DVO 2002-05-27: strip _all_ trailing zeros + while( nLen && ( 0 == *( pChars + nLen - 1 ) ) ) + --nLen; + + rStr = OUString( pChars, nLen, osl_getThreadTextEncoding() ); + bRet = true; + } + } + + return bRet; +} + + +bool TransferableDataHelper::GetBitmapEx( SotClipboardFormatId nFormat, BitmapEx& rBmpEx ) const +{ + if(SotClipboardFormatId::BITMAP == nFormat) + { + // try to get PNG first + DataFlavor aFlavor; + + if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PNG, aFlavor)) + { + if(GetBitmapEx(aFlavor, rBmpEx)) + { + return true; + } + } + + // then JPEG + if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::JPEG, aFlavor)) + { + if(GetBitmapEx(aFlavor, rBmpEx)) + { + return true; + } + } + } + + DataFlavor aFlavor; + return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetBitmapEx( aFlavor, rBmpEx ) ); +} + + +bool TransferableDataHelper::GetBitmapEx( const DataFlavor& rFlavor, BitmapEx& rBmpEx ) const +{ + tools::SvRef<SotTempStream> xStm; + DataFlavor aSubstFlavor; + bool bRet(GetSotStorageStream(rFlavor, xStm)); + bool bSuppressPNG(false); // #122982# If PNG stream not accessed, but BMP one, suppress trying to load PNG + bool bSuppressJPEG(false); + + if(!bRet && HasFormat(SotClipboardFormatId::PNG) && SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PNG, aSubstFlavor)) + { + // when no direct success, try if PNG is available + bRet = GetSotStorageStream(aSubstFlavor, xStm); + bSuppressJPEG = bRet; + } + + if(!bRet && HasFormat(SotClipboardFormatId::JPEG) && SotExchange::GetFormatDataFlavor(SotClipboardFormatId::JPEG, aSubstFlavor)) + { + bRet = GetSotStorageStream(aSubstFlavor, xStm); + bSuppressPNG = bRet; + } + + if(!bRet && HasFormat(SotClipboardFormatId::BMP) && SotExchange::GetFormatDataFlavor(SotClipboardFormatId::BMP, aSubstFlavor)) + { + // when no direct success, try if BMP is available + bRet = GetSotStorageStream(aSubstFlavor, xStm); + bSuppressPNG = bRet; + bSuppressJPEG = bRet; + } + + if(bRet) + { + if(!bSuppressPNG && rFlavor.MimeType.equalsIgnoreAsciiCase("image/png")) + { + // it's a PNG, import to BitmapEx + vcl::PngImageReader aPNGReader(*xStm); + rBmpEx = aPNGReader.read(); + } + else if(!bSuppressJPEG && rFlavor.MimeType.equalsIgnoreAsciiCase("image/jpeg")) + { + // it's a JPEG, import to BitmapEx + GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); + Graphic aGraphic; + if (rFilter.ImportGraphic(aGraphic, u"", *xStm) == ERRCODE_NONE) + rBmpEx = aGraphic.GetBitmapEx(); + } + + if(rBmpEx.IsEmpty()) + { + Bitmap aBitmap; + AlphaMask aMask; + + // explicitly use Bitmap::Read with bFileHeader = sal_True + // #i124085# keep DIBV5 for read from clipboard, but should not happen + ReadDIBV5(aBitmap, aMask, *xStm); + + if(aMask.GetBitmap().IsEmpty()) + { + rBmpEx = aBitmap; + } + else + { + rBmpEx = BitmapEx(aBitmap, aMask); + } + } + + bRet = (ERRCODE_NONE == xStm->GetError() && !rBmpEx.IsEmpty()); + + /* SJ: #110748# At the moment we are having problems with DDB inserted as DIB. The + problem is, that some graphics are inserted much too big because the nXPelsPerMeter + and nYPelsPerMeter of the bitmap fileheader isn't including the correct value. + Due to this reason the following code assumes that bitmaps with a logical size + greater than 50 cm aren't having the correct mapmode set. + + The following code should be removed if DDBs and DIBs are supported via clipboard + properly. + */ + if(bRet) + { + const MapMode aMapMode(rBmpEx.GetPrefMapMode()); + + if(MapUnit::MapPixel != aMapMode.GetMapUnit()) + { + const Size aSize(OutputDevice::LogicToLogic(rBmpEx.GetPrefSize(), aMapMode, MapMode(MapUnit::Map100thMM))); + + // #i122388# This wrongly corrects in the given case; changing from 5000 100th mm to + // the described 50 cm (which is 50000 100th mm) + if((aSize.Width() > 50000) || (aSize.Height() > 50000)) + { + rBmpEx.SetPrefMapMode(MapMode(MapUnit::MapPixel)); + + // #i122388# also adapt size by applying the mew MapMode + const Size aNewSize(o3tl::convert(aSize, o3tl::Length::mm100, o3tl::Length::pt)); + rBmpEx.SetPrefSize(aNewSize); + } + } + } + } + + return bRet; +} + + +bool TransferableDataHelper::GetGDIMetaFile(SotClipboardFormatId nFormat, GDIMetaFile& rMtf, size_t nMaxActions) const +{ + DataFlavor aFlavor; + return SotExchange::GetFormatDataFlavor(nFormat, aFlavor) && + GetGDIMetaFile(aFlavor, rMtf) && + (nMaxActions == 0 || rMtf.GetActionSize() < nMaxActions); +} + + +bool TransferableDataHelper::GetGDIMetaFile( const DataFlavor& rFlavor, GDIMetaFile& rMtf ) const +{ + tools::SvRef<SotTempStream> xStm; + DataFlavor aSubstFlavor; + bool bRet = false; + + if( GetSotStorageStream( rFlavor, xStm ) ) + { + SvmReader aReader( *xStm ); + aReader.Read( rMtf ); + bRet = ( xStm->GetError() == ERRCODE_NONE ); + } + + if( !bRet && + HasFormat( SotClipboardFormatId::EMF ) && + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EMF, aSubstFlavor ) && + GetSotStorageStream( aSubstFlavor, xStm ) ) + { + Graphic aGraphic; + + if( GraphicConverter::Import( *xStm, aGraphic ) == ERRCODE_NONE ) + { + rMtf = aGraphic.GetGDIMetaFile(); + bRet = true; + } + } + + if( !bRet && + HasFormat( SotClipboardFormatId::WMF ) && + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::WMF, aSubstFlavor ) && + GetSotStorageStream( aSubstFlavor, xStm ) ) + { + Graphic aGraphic; + + if( GraphicConverter::Import( *xStm, aGraphic ) == ERRCODE_NONE ) + { + rMtf = aGraphic.GetGDIMetaFile(); + bRet = true; + } + } + + return bRet; +} + + +bool TransferableDataHelper::GetGraphic( SotClipboardFormatId nFormat, Graphic& rGraphic ) const +{ + if(SotClipboardFormatId::BITMAP == nFormat) + { + // try to get PNG first + DataFlavor aFlavor; + + if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PNG, aFlavor)) + { + if(GetGraphic(aFlavor, rGraphic)) + { + return true; + } + } + } + + DataFlavor aFlavor; + return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetGraphic( aFlavor, rGraphic ) ); +} + + +bool TransferableDataHelper::GetGraphic( const css::datatransfer::DataFlavor& rFlavor, Graphic& rGraphic ) const +{ + DataFlavor aFlavor; + bool bRet = false; + + if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PNG, aFlavor) && + TransferableDataHelper::IsEqual(aFlavor, rFlavor)) + { + // try to get PNG first + BitmapEx aBmpEx; + + bRet = GetBitmapEx( aFlavor, aBmpEx ); + if( bRet ) + rGraphic = aBmpEx; + } + else if(SotExchange::GetFormatDataFlavor(SotClipboardFormatId::PDF, aFlavor) && + TransferableDataHelper::IsEqual(aFlavor, rFlavor)) + { + Graphic aGraphic; + tools::SvRef<SotTempStream> xStm; + if (GetSotStorageStream(rFlavor, xStm)) + { + if (GraphicConverter::Import(*xStm, aGraphic) == ERRCODE_NONE) + { + rGraphic = aGraphic; + bRet = true; + } + } + } + else if (SotExchange::GetFormatDataFlavor(SotClipboardFormatId::JPEG, aFlavor) && TransferableDataHelper::IsEqual(aFlavor, rFlavor)) + { + BitmapEx aBitmapEx; + + bRet = GetBitmapEx(aFlavor, aBitmapEx); + if (bRet) + rGraphic = aBitmapEx; + } + else if(SotExchange::GetFormatDataFlavor( SotClipboardFormatId::BITMAP, aFlavor ) && + TransferableDataHelper::IsEqual( aFlavor, rFlavor ) ) + { + BitmapEx aBmpEx; + + bRet = GetBitmapEx( aFlavor, aBmpEx ); + if( bRet ) + rGraphic = aBmpEx; + } + else if( SotExchange::GetFormatDataFlavor( SotClipboardFormatId::GDIMETAFILE, aFlavor ) && + TransferableDataHelper::IsEqual( aFlavor, rFlavor ) ) + { + GDIMetaFile aMtf; + + bRet = GetGDIMetaFile( aFlavor, aMtf ); + if( bRet ) + rGraphic = aMtf; + } + else + { + tools::SvRef<SotTempStream> xStm; + + if( GetSotStorageStream( rFlavor, xStm ) ) + { + TypeSerializer aSerializer(*xStm); + aSerializer.readGraphic(rGraphic); + bRet = ( xStm->GetError() == ERRCODE_NONE ); + } + } + + return bRet; +} + + +bool TransferableDataHelper::GetImageMap( SotClipboardFormatId nFormat, ImageMap& rIMap ) const +{ + DataFlavor aFlavor; + return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetImageMap( aFlavor, rIMap ) ); +} + + +bool TransferableDataHelper::GetImageMap( const css::datatransfer::DataFlavor& rFlavor, ImageMap& rIMap ) const +{ + tools::SvRef<SotTempStream> xStm; + bool bRet = GetSotStorageStream( rFlavor, xStm ); + + if( bRet ) + { + rIMap.Read( *xStm ); + bRet = ( xStm->GetError() == ERRCODE_NONE ); + } + + return bRet; +} + + +bool TransferableDataHelper::GetTransferableObjectDescriptor( SotClipboardFormatId nFormat, TransferableObjectDescriptor& rDesc ) +{ + DataFlavor aFlavor; + return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetTransferableObjectDescriptor( rDesc ) ); +} + + +bool TransferableDataHelper::GetTransferableObjectDescriptor( TransferableObjectDescriptor& rDesc ) +{ + rDesc = *mxObjDesc; + return true; +} + + +bool TransferableDataHelper::GetINetBookmark( SotClipboardFormatId nFormat, INetBookmark& rBmk ) const +{ + DataFlavor aFlavor; + return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetINetBookmark( aFlavor, rBmk ) ); +} + + +bool TransferableDataHelper::GetINetBookmark( const css::datatransfer::DataFlavor& rFlavor, INetBookmark& rBmk ) const +{ + if( !HasFormat( rFlavor )) + return false; + + bool bRet = false; + const SotClipboardFormatId nFormat = SotExchange::GetFormat( rFlavor ); + switch( nFormat ) + { + case SotClipboardFormatId::SOLK: + case SotClipboardFormatId::UNIFORMRESOURCELOCATOR: + { + OUString aString; + if( GetString( rFlavor, aString ) ) + { + if( SotClipboardFormatId::UNIFORMRESOURCELOCATOR == nFormat ) + { + rBmk = INetBookmark( aString, aString ); + bRet = true; + } + else + { + OUString aURL, aDesc; + sal_Int32 nStart = aString.indexOf( '@' ), nLen = aString.toInt32(); + + if( !nLen && aString[ 0 ] != '0' ) + { + SAL_INFO( "svtools", "SOLK: 1. len=0" ); + } + if( nStart == -1 || nLen > aString.getLength() - nStart - 3 ) + { + SAL_INFO( "svtools", "SOLK: 1. illegal start or wrong len" ); + } + aURL = aString.copy( nStart + 1, nLen ); + + aString = aString.replaceAt( 0, nStart + 1 + nLen, u"" ); + nStart = aString.indexOf( '@' ); + nLen = aString.toInt32(); + + if( !nLen && aString[ 0 ] != '0' ) + { + SAL_INFO( "svtools", "SOLK: 2. len=0" ); + } + if( nStart == -1 || nLen > aString.getLength() - nStart - 1 ) + { + SAL_INFO( "svtools", "SOLK: 2. illegal start or wrong len" ); + } + aDesc = aString.copy( nStart+1, nLen ); + + rBmk = INetBookmark( aURL, aDesc ); + bRet = true; + } + } + } + break; + + case SotClipboardFormatId::NETSCAPE_BOOKMARK: + { + Sequence<sal_Int8> aSeq = GetSequence(rFlavor, OUString()); + + if (2048 == aSeq.getLength()) + { + const char* p1 = reinterpret_cast< const char* >( aSeq.getConstArray() ); + const char* p2 = reinterpret_cast< const char* >( aSeq.getConstArray() ) + 1024; + rBmk = INetBookmark( OUString( p1, strlen(p1), osl_getThreadTextEncoding() ), + OUString( p2, strlen(p2), osl_getThreadTextEncoding() ) ); + bRet = true; + } + } + break; + +#ifdef _WIN32 + case SotClipboardFormatId::FILEGRPDESCRIPTOR: + { + Sequence<sal_Int8> aSeq = GetSequence(rFlavor, OUString()); + + if (aSeq.getLength()) + { + FILEGROUPDESCRIPTORW const * pFDesc = reinterpret_cast<FILEGROUPDESCRIPTORW const *>(aSeq.getConstArray()); + + if( pFDesc->cItems ) + { + OUString aDesc( o3tl::toU(pFDesc->fgd[ 0 ].cFileName) ); + + if( ( aDesc.getLength() > 4 ) && aDesc.endsWithIgnoreAsciiCase(".URL") ) + { + std::unique_ptr<SvStream> pStream(::utl::UcbStreamHelper::CreateStream( INetURLObject( aDesc ).GetMainURL( INetURLObject::DecodeMechanism::NONE ), + StreamMode::STD_READ )); + + if( !pStream || pStream->GetError() ) + { + DataFlavor aFileContentFlavor; + + aSeq.realloc( 0 ); + pStream.reset(); + + if (SotExchange::GetFormatDataFlavor(SotClipboardFormatId::FILECONTENT, aFileContentFlavor)) + { + aSeq = GetSequence(aFileContentFlavor, OUString()); + if (aSeq.getLength()) + pStream.reset(new SvMemoryStream( const_cast<sal_Int8 *>(aSeq.getConstArray()), aSeq.getLength(), StreamMode::STD_READ )); + } + } + + if( pStream ) + { + OString aLine; + bool bInA = false, bInW = false, bAFound = false; + + while( pStream->ReadLine( aLine ) ) + { + if (aLine.startsWithIgnoreAsciiCase("[InternetShortcut", &aLine)) + { + // May be [InternetShortcut], or [InternetShortcut.A], or + // [InternetShortcut.W] (the latter has UTF-7-encoded URL) + bInW = aLine.equalsIgnoreAsciiCase(".W]"); + bInA = !bAFound && !bInW + && (aLine == "]" || aLine.equalsIgnoreAsciiCase(".A]")); + } + else if (aLine.startsWith("[")) + { + bInA = bInW = false; + } + else if ((bInA || bInW) && aLine.startsWithIgnoreAsciiCase("URL=")) + { + auto eTextEncoding = bInW ? RTL_TEXTENCODING_UTF7 + : osl_getThreadTextEncoding(); + rBmk = INetBookmark( OStringToOUString(aLine.subView(4), eTextEncoding), + aDesc.copy(0, aDesc.getLength() - 4) ); + bRet = true; + if (bInW) + break; + else + bAFound = true; // Keep looking for "W" + } + } + } + } + } + } + } + break; +#endif + default: break; + } + return bRet; +} + + +bool TransferableDataHelper::GetINetImage( SotClipboardFormatId nFormat, + INetImage& rINtImg ) const +{ + DataFlavor aFlavor; + return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetINetImage( aFlavor, rINtImg ) ); +} + + +bool TransferableDataHelper::GetINetImage( + const css::datatransfer::DataFlavor& rFlavor, + INetImage& rINtImg ) const +{ + tools::SvRef<SotTempStream> xStm; + bool bRet = GetSotStorageStream( rFlavor, xStm ); + + if( bRet ) + bRet = rINtImg.Read( *xStm, SotExchange::GetFormat( rFlavor ) ); + return bRet; +} + + +bool TransferableDataHelper::GetFileList( SotClipboardFormatId nFormat, + FileList& rFileList ) const +{ + DataFlavor aFlavor; + return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetFileList( rFileList ) ); +} + + +bool TransferableDataHelper::GetFileList( FileList& rFileList ) const +{ + tools::SvRef<SotTempStream> xStm; + bool bRet = false; + + for( sal_uInt32 i = 0, nFormatCount = GetFormatCount(); ( i < nFormatCount ) && !bRet; ++i ) + { + if( SotClipboardFormatId::FILE_LIST == GetFormat( i ) ) + { + const DataFlavor aFlavor( GetFormatDataFlavor( i ) ); + + if( GetSotStorageStream( aFlavor, xStm ) ) + { + if( aFlavor.MimeType.indexOf( "text/uri-list" ) > -1 ) + { + OStringBuffer aDiskString; + + while( xStm->ReadLine( aDiskString ) ) + if( !aDiskString.isEmpty() && aDiskString[0] != '#' ) + rFileList.AppendFile( OStringToOUString(aDiskString, RTL_TEXTENCODING_UTF8) ); + + bRet = true; + } + else + bRet = ( ReadFileList( *xStm, rFileList ).GetError() == ERRCODE_NONE ); + } + } + } + + return bRet; +} + + +Sequence<sal_Int8> TransferableDataHelper::GetSequence( SotClipboardFormatId nFormat, const OUString& rDestDoc ) const +{ + DataFlavor aFlavor; + if (!SotExchange::GetFormatDataFlavor(nFormat, aFlavor)) + return Sequence<sal_Int8>(); + + return GetSequence(aFlavor, rDestDoc); +} + +Sequence<sal_Int8> TransferableDataHelper::GetSequence( const DataFlavor& rFlavor, const OUString& rDestDoc ) const +{ + const Any aAny = GetAny(rFlavor, rDestDoc); + Sequence<sal_Int8> aSeq; + if (aAny.hasValue()) + aAny >>= aSeq; + + return aSeq; +} + + +bool TransferableDataHelper::GetSotStorageStream( SotClipboardFormatId nFormat, tools::SvRef<SotTempStream>& rxStream ) const +{ + DataFlavor aFlavor; + return( SotExchange::GetFormatDataFlavor( nFormat, aFlavor ) && GetSotStorageStream( aFlavor, rxStream ) ); +} + + +bool TransferableDataHelper::GetSotStorageStream( const DataFlavor& rFlavor, tools::SvRef<SotTempStream>& rxStream ) const +{ + Sequence<sal_Int8> aSeq = GetSequence(rFlavor, OUString()); + + if (aSeq.hasElements()) + { + rxStream = new SotTempStream( "" ); + rxStream->WriteBytes( aSeq.getConstArray(), aSeq.getLength() ); + rxStream->Seek( 0 ); + } + + return aSeq.hasElements(); +} + +Reference<XInputStream> TransferableDataHelper::GetInputStream( SotClipboardFormatId nFormat, const OUString& rDestDoc ) const +{ + DataFlavor aFlavor; + if (!SotExchange::GetFormatDataFlavor(nFormat, aFlavor)) + return Reference<XInputStream>(); + + return GetInputStream(aFlavor, rDestDoc); +} + +Reference<XInputStream> TransferableDataHelper::GetInputStream( const DataFlavor& rFlavor, const OUString& rDestDoc ) const +{ + Sequence<sal_Int8> aSeq = GetSequence(rFlavor, rDestDoc); + + if (!aSeq.hasElements()) + return Reference<XInputStream>(); + + Reference<XInputStream> xStream(new comphelper::SequenceInputStream(aSeq)); + return xStream; +} + +void TransferableDataHelper::Rebind( const Reference< XTransferable >& _rxNewContent ) +{ + mxTransfer = _rxNewContent; + InitFormats(); +} + +bool TransferableDataHelper::StartClipboardListening( ) +{ + ::osl::MutexGuard aGuard(mxImpl->maMutex); + + StopClipboardListening( ); + + mxImpl->mxClipboardListener = new TransferableClipboardNotifier(mxClipboard, *this, mxImpl->maMutex); + + return mxImpl->mxClipboardListener->isListening(); +} + +void TransferableDataHelper::StopClipboardListening( ) +{ + ::osl::MutexGuard aGuard(mxImpl->maMutex); + + if (mxImpl->mxClipboardListener.is()) + { + mxImpl->mxClipboardListener->dispose(); + mxImpl->mxClipboardListener.clear(); + } +} + +TransferableDataHelper TransferableDataHelper::CreateFromClipboard(const css::uno::Reference<css::datatransfer::clipboard::XClipboard>& rClipboard) +{ + TransferableDataHelper aRet; + + if( rClipboard.is() ) + { + try + { + Reference< XTransferable > xTransferable( rClipboard->getContents() ); + + if( xTransferable.is() ) + { + aRet = TransferableDataHelper( xTransferable ); + // also copy the clipboard + aRet.mxClipboard = rClipboard; + } + } + catch( const css::uno::Exception& ) + { + } + } + + return aRet; +} + +TransferableDataHelper TransferableDataHelper::CreateFromSystemClipboard( vcl::Window * pWindow ) +{ + DBG_ASSERT( pWindow, "Window pointer is NULL" ); + + Reference< XClipboard > xClipboard; + + if( pWindow ) + xClipboard = pWindow->GetClipboard(); + + return CreateFromClipboard(xClipboard); +} + +TransferableDataHelper TransferableDataHelper::CreateFromPrimarySelection() +{ + Reference< XClipboard > xSelection(GetSystemPrimarySelection()); + TransferableDataHelper aRet; + + if( xSelection.is() ) + { + SolarMutexReleaser aReleaser; + + try + { + Reference< XTransferable > xTransferable( xSelection->getContents() ); + + if( xTransferable.is() ) + { + aRet = TransferableDataHelper( xTransferable ); + aRet.mxClipboard = xSelection; + } + } + catch( const css::uno::Exception& ) + { + } + } + + return aRet; +} + +bool TransferableDataHelper::IsEqual( const css::datatransfer::DataFlavor& rInternalFlavor, + const css::datatransfer::DataFlavor& rRequestFlavor ) +{ + Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + bool bRet = false; + + try + { + Reference< XMimeContentTypeFactory > xMimeFact = MimeContentTypeFactory::create( xContext ); + + Reference< XMimeContentType > xRequestType1( xMimeFact->createMimeContentType( rInternalFlavor.MimeType ) ); + Reference< XMimeContentType > xRequestType2( xMimeFact->createMimeContentType( rRequestFlavor.MimeType ) ); + + if( xRequestType1.is() && xRequestType2.is() ) + { + if( xRequestType1->getFullMediaType().equalsIgnoreAsciiCase( xRequestType2->getFullMediaType() ) ) + { + if( xRequestType1->getFullMediaType().equalsIgnoreAsciiCase( "text/plain" ) ) + { + // special handling for text/plain media types + static const OUStringLiteral aCharsetString( u"charset" ); + + if( !xRequestType2->hasParameter( aCharsetString ) || + xRequestType2->getParameterValue( aCharsetString ).equalsIgnoreAsciiCase( "utf-16" ) || + xRequestType2->getParameterValue( aCharsetString ).equalsIgnoreAsciiCase( "unicode" ) ) + { + bRet = true; + } + } + else if( xRequestType1->getFullMediaType().equalsIgnoreAsciiCase( "application/x-openoffice" ) ) + { + // special handling for application/x-openoffice media types + static const OUStringLiteral aFormatString( u"windows_formatname" ); + + if( xRequestType1->hasParameter( aFormatString ) && + xRequestType2->hasParameter( aFormatString ) && + xRequestType1->getParameterValue( aFormatString ).equalsIgnoreAsciiCase( xRequestType2->getParameterValue( aFormatString ) ) ) + { + bRet = true; + } + } + else + bRet = true; + } + } + } + catch( const css::uno::Exception& ) + { + bRet = rInternalFlavor.MimeType.equalsIgnoreAsciiCase( rRequestFlavor.MimeType ); + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/transfer2.cxx b/vcl/source/treelist/transfer2.cxx new file mode 100644 index 000000000..c63770d8a --- /dev/null +++ b/vcl/source/treelist/transfer2.cxx @@ -0,0 +1,516 @@ +/* -*- 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 <config_vclplug.h> + +#include <osl/mutex.hxx> +#include <sot/exchange.hxx> +#include <tools/debug.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/datatransfer/clipboard/SystemClipboard.hpp> +#include <com/sun/star/datatransfer/dnd/XDropTargetDragContext.hpp> +#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp> +#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp> +#include <com/sun/star/uno/DeploymentException.hpp> +#include <svl/urlbmk.hxx> +#include <vcl/transfer.hxx> + +#include <svdata.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::datatransfer; +using namespace ::com::sun::star::datatransfer::clipboard; +using namespace ::com::sun::star::datatransfer::dnd; + + +DragSourceHelper::DragGestureListener::DragGestureListener( DragSourceHelper& rDragSourceHelper ) : + mrParent( rDragSourceHelper ) +{ +} + + +DragSourceHelper::DragGestureListener::~DragGestureListener() +{ +} + + +void SAL_CALL DragSourceHelper::DragGestureListener::disposing( const EventObject& ) +{ +} + + +void SAL_CALL DragSourceHelper::DragGestureListener::dragGestureRecognized( const DragGestureEvent& rDGE ) +{ + const SolarMutexGuard aGuard; + + const Point aPtPixel( rDGE.DragOriginX, rDGE.DragOriginY ); + mrParent.StartDrag( rDGE.DragAction, aPtPixel ); +} + + +DragSourceHelper::DragSourceHelper( vcl::Window* pWindow ) : + mxDragGestureRecognizer( pWindow->GetDragGestureRecognizer() ) +{ + if( mxDragGestureRecognizer.is() ) + { + mxDragGestureListener = new DragSourceHelper::DragGestureListener( *this ); + mxDragGestureRecognizer->addDragGestureListener( mxDragGestureListener ); + } +} + + +void DragSourceHelper::dispose() +{ + Reference<XDragGestureRecognizer> xTmp; + { + std::scoped_lock aGuard( maMutex ); + xTmp = std::move(mxDragGestureRecognizer); + } + if( xTmp.is() ) + xTmp->removeDragGestureListener( mxDragGestureListener ); +} + +DragSourceHelper::~DragSourceHelper() +{ + dispose(); +} + + +void DragSourceHelper::StartDrag( sal_Int8, const Point& ) +{ +} + + +DropTargetHelper::DropTargetListener::DropTargetListener( DropTargetHelper& rDropTargetHelper ) : + mrParent( rDropTargetHelper ) +{ +} + + +DropTargetHelper::DropTargetListener::~DropTargetListener() +{ +} + + +void SAL_CALL DropTargetHelper::DropTargetListener::disposing( const EventObject& ) +{ +} + + +void SAL_CALL DropTargetHelper::DropTargetListener::drop( const DropTargetDropEvent& rDTDE ) +{ + const SolarMutexGuard aGuard; + + try + { + AcceptDropEvent aAcceptEvent; + ExecuteDropEvent aExecuteEvt( rDTDE.DropAction & ~DNDConstants::ACTION_DEFAULT, Point( rDTDE.LocationX, rDTDE.LocationY ), rDTDE ); + + aExecuteEvt.mbDefault = ( ( rDTDE.DropAction & DNDConstants::ACTION_DEFAULT ) != 0 ); + + // in case of a default action, call ::AcceptDrop first and use the returned + // accepted action as the execute action in the call to ::ExecuteDrop + aAcceptEvent.mnAction = aExecuteEvt.mnAction; + aAcceptEvent.maPosPixel = aExecuteEvt.maPosPixel; + static_cast<DropTargetEvent&>(const_cast<DropTargetDragEvent&>( aAcceptEvent.maDragEvent )) = rDTDE; + const_cast<DropTargetDragEvent&>( aAcceptEvent.maDragEvent ).DropAction = rDTDE.DropAction; + const_cast<DropTargetDragEvent&>( aAcceptEvent.maDragEvent ).LocationX = rDTDE.LocationX; + const_cast<DropTargetDragEvent&>( aAcceptEvent.maDragEvent ).LocationY = rDTDE.LocationY; + const_cast<DropTargetDragEvent&>( aAcceptEvent.maDragEvent ).SourceActions = rDTDE.SourceActions; + aAcceptEvent.mbLeaving = false; + aAcceptEvent.mbDefault = aExecuteEvt.mbDefault; + + sal_Int8 nRet = mrParent.AcceptDrop( aAcceptEvent ); + + if( DNDConstants::ACTION_NONE != nRet ) + { + rDTDE.Context->acceptDrop( nRet ); + + if( aExecuteEvt.mbDefault ) + aExecuteEvt.mnAction = nRet; + + nRet = mrParent.ExecuteDrop( aExecuteEvt ); + } + + rDTDE.Context->dropComplete( DNDConstants::ACTION_NONE != nRet ); + + mpLastDragOverEvent.reset(); + } + catch( const css::uno::Exception& ) + { + } +} + + +void SAL_CALL DropTargetHelper::DropTargetListener::dragEnter( const DropTargetDragEnterEvent& rDTDEE ) +{ + const SolarMutexGuard aGuard; + + try + { + mrParent.ImplBeginDrag( rDTDEE.SupportedDataFlavors ); + } + catch( const css::uno::Exception& ) + { + } + + dragOver( rDTDEE ); +} + + +void SAL_CALL DropTargetHelper::DropTargetListener::dragOver( const DropTargetDragEvent& rDTDE ) +{ + const SolarMutexGuard aGuard; + + try + { + mpLastDragOverEvent.reset( new AcceptDropEvent( rDTDE.DropAction & ~DNDConstants::ACTION_DEFAULT, Point( rDTDE.LocationX, rDTDE.LocationY ), rDTDE ) ); + mpLastDragOverEvent->mbDefault = ( ( rDTDE.DropAction & DNDConstants::ACTION_DEFAULT ) != 0 ); + + const sal_Int8 nRet = mrParent.AcceptDrop( *mpLastDragOverEvent ); + + if( DNDConstants::ACTION_NONE == nRet ) + rDTDE.Context->rejectDrag(); + else + rDTDE.Context->acceptDrag( nRet ); + } + catch( const css::uno::Exception& ) + { + } +} + + +void SAL_CALL DropTargetHelper::DropTargetListener::dragExit( const DropTargetEvent& ) +{ + const SolarMutexGuard aGuard; + + try + { + if( mpLastDragOverEvent ) + { + mpLastDragOverEvent->mbLeaving = true; + mrParent.AcceptDrop( *mpLastDragOverEvent ); + mpLastDragOverEvent.reset(); + } + + mrParent.ImplEndDrag(); + } + catch( const css::uno::Exception& ) + { + } +} + + +void SAL_CALL DropTargetHelper::DropTargetListener::dropActionChanged( const DropTargetDragEvent& ) +{ +} + + +DropTargetHelper::DropTargetHelper( vcl::Window* pWindow ) : + mxDropTarget( pWindow->GetDropTarget() ) +{ + ImplConstruct(); +} + + +DropTargetHelper::DropTargetHelper( const Reference< XDropTarget >& rxDropTarget ) : + mxDropTarget( rxDropTarget ) +{ + ImplConstruct(); +} + + +void DropTargetHelper::dispose() +{ + Reference< XDropTarget > xTmp; + { + std::scoped_lock aGuard( maMutex ); + xTmp = std::move(mxDropTarget); + } + if( xTmp.is() ) + xTmp->removeDropTargetListener( mxDropTargetListener ); +} + +DropTargetHelper::~DropTargetHelper() +{ + dispose(); +} + + +void DropTargetHelper::ImplConstruct() +{ + if( mxDropTarget.is() ) + { + mxDropTargetListener = new DropTargetHelper::DropTargetListener( *this ); + mxDropTarget->addDropTargetListener( mxDropTargetListener ); + mxDropTarget->setActive( true ); + } +} + + +void DropTargetHelper::ImplBeginDrag( const Sequence< DataFlavor >& rSupportedDataFlavors ) +{ + maFormats.clear(); + TransferableDataHelper::FillDataFlavorExVector( rSupportedDataFlavors, maFormats ); +} + + +void DropTargetHelper::ImplEndDrag() +{ + maFormats.clear(); +} + + +sal_Int8 DropTargetHelper::AcceptDrop( const AcceptDropEvent& ) +{ + return DNDConstants::ACTION_NONE; +} + + +sal_Int8 DropTargetHelper::ExecuteDrop( const ExecuteDropEvent& ) +{ + return DNDConstants::ACTION_NONE; +} + + +bool DropTargetHelper::IsDropFormatSupported(SotClipboardFormatId nFormat) const +{ + return std::any_of(maFormats.begin(), maFormats.end(), + [&](const DataFlavorEx& data) { return data.mnSotId == nFormat; }); +} + + +// TransferDataContainer + +namespace { + +struct TDataCntnrEntry_Impl +{ + css::uno::Any aAny; + SotClipboardFormatId nId; +}; + +} + +struct TransferDataContainer_Impl +{ + std::vector< TDataCntnrEntry_Impl > aFmtList; + Link<sal_Int8,void> aFinishedLnk; + std::unique_ptr<INetBookmark> pBookmk; + + TransferDataContainer_Impl() + { + } +}; + + +TransferDataContainer::TransferDataContainer() + : pImpl( new TransferDataContainer_Impl ) +{ +} + + +TransferDataContainer::~TransferDataContainer() +{ +} + + +void TransferDataContainer::AddSupportedFormats() +{ +} + + +bool TransferDataContainer::GetData( + const css::datatransfer::DataFlavor& rFlavor, const OUString& /*rDestDoc*/ ) +{ + bool bFnd = false; + SotClipboardFormatId nFmtId = SotExchange::GetFormat( rFlavor ); + + // test first the list + for (auto const& format : pImpl->aFmtList) + { + if( nFmtId == format.nId ) + { + bFnd = SetAny( format.aAny ); + break; + } + } + + // test second the bookmark pointer + if( !bFnd ) + switch( nFmtId ) + { + case SotClipboardFormatId::STRING: + case SotClipboardFormatId::SOLK: + case SotClipboardFormatId::NETSCAPE_BOOKMARK: + case SotClipboardFormatId::FILECONTENT: + case SotClipboardFormatId::FILEGRPDESCRIPTOR: + case SotClipboardFormatId::UNIFORMRESOURCELOCATOR: + if( pImpl->pBookmk ) + bFnd = SetINetBookmark( *pImpl->pBookmk, rFlavor ); + break; + + default: break; + } + + return bFnd; +} + + +void TransferDataContainer::CopyINetBookmark( const INetBookmark& rBkmk ) +{ + if( !pImpl->pBookmk ) + pImpl->pBookmk.reset( new INetBookmark( rBkmk ) ); + else + *pImpl->pBookmk = rBkmk; + + AddFormat( SotClipboardFormatId::STRING ); + AddFormat( SotClipboardFormatId::SOLK ); + AddFormat( SotClipboardFormatId::NETSCAPE_BOOKMARK ); + AddFormat( SotClipboardFormatId::FILECONTENT ); + AddFormat( SotClipboardFormatId::FILEGRPDESCRIPTOR ); + AddFormat( SotClipboardFormatId::UNIFORMRESOURCELOCATOR ); +} + + +void TransferDataContainer::CopyAnyData( SotClipboardFormatId nFormatId, + const char* pData, sal_uLong nLen ) +{ + if( nLen ) + { + TDataCntnrEntry_Impl aEntry; + aEntry.nId = nFormatId; + + Sequence< sal_Int8 > aSeq( nLen ); + memcpy( aSeq.getArray(), pData, nLen ); + aEntry.aAny <<= aSeq; + pImpl->aFmtList.push_back( aEntry ); + AddFormat( nFormatId ); + } +} + + +void TransferDataContainer::CopyByteString( SotClipboardFormatId nFormatId, + const OString& rStr ) +{ + CopyAnyData( nFormatId, rStr.getStr(), rStr.getLength() ); +} + + +void TransferDataContainer::CopyString( SotClipboardFormatId nFmt, const OUString& rStr ) +{ + if( !rStr.isEmpty() ) + { + TDataCntnrEntry_Impl aEntry; + aEntry.nId = nFmt; + aEntry.aAny <<= rStr; + pImpl->aFmtList.push_back( aEntry ); + AddFormat( aEntry.nId ); + } +} + + +void TransferDataContainer::CopyString( const OUString& rStr ) +{ + CopyString( SotClipboardFormatId::STRING, rStr ); +} + + +bool TransferDataContainer::HasAnyData() const +{ + return !pImpl->aFmtList.empty() || + nullptr != pImpl->pBookmk; +} + + +void TransferDataContainer::StartDrag( + vcl::Window* pWindow, sal_Int8 nDragSourceActions, + const Link<sal_Int8,void>& rLnk ) +{ + pImpl->aFinishedLnk = rLnk; + TransferableHelper::StartDrag( pWindow, nDragSourceActions ); +} + + +void TransferDataContainer::DragFinished( sal_Int8 nDropAction ) +{ + pImpl->aFinishedLnk.Call( nDropAction ); +} + +Reference<XClipboard> GetSystemClipboard() +{ + // On Windows, the css.datatransfer.clipboard.SystemClipboard UNO service is implemented as a + // single-instance service (dtrans_CWinClipboard_get_implementation in + // vcl/win/dtrans/WinClipboard.cxx) that needs timely disposing to join a spawned thread + // (done in DeInitVCL, vcl/source/app/svmain.cxx), while on other platforms it is implemented as + // a multi-instance service (ClipboardFactory, vcl/source/components/dtranscomp.cxx) so we + // should not hold on to a single instance here: +#if defined _WIN32 + DBG_TESTSOLARMUTEX(); + auto const data = ImplGetSVData(); + if (!data->m_xSystemClipboard.is()) + { + try + { + data->m_xSystemClipboard = css::datatransfer::clipboard::SystemClipboard::create( + comphelper::getProcessComponentContext()); + } + catch (DeploymentException const &) {} + } + return data->m_xSystemClipboard; +#else + Reference<XClipboard> xClipboard; + try + { + xClipboard = css::datatransfer::clipboard::SystemClipboard::create( + comphelper::getProcessComponentContext()); + } + catch (DeploymentException const &) {} + return xClipboard; +#endif +} + +Reference<XClipboard> GetSystemPrimarySelection() +{ + Reference<XClipboard> xSelection; + try + { + Reference<XComponentContext> xContext(comphelper::getProcessComponentContext()); +#if USING_X11 + // A hack, making the primary selection available as an instance + // of the SystemClipboard service on X11: + Sequence< Any > args{ Any(OUString("PRIMARY")) }; + xSelection.set(xContext->getServiceManager()->createInstanceWithArgumentsAndContext( + "com.sun.star.datatransfer.clipboard.SystemClipboard", args, xContext), UNO_QUERY_THROW); +#else + static Reference< XClipboard > s_xSelection( + xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.datatransfer.clipboard.GenericClipboard", xContext), UNO_QUERY); + xSelection = s_xSelection; +#endif + } + catch (RuntimeException const &) {} + return xSelection; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/treelist.cxx b/vcl/source/treelist/treelist.cxx new file mode 100644 index 000000000..9e057e598 --- /dev/null +++ b/vcl/source/treelist/treelist.cxx @@ -0,0 +1,1524 @@ +/* -*- 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 <vcl/toolkit/treelist.hxx> +#include <vcl/toolkit/treelistentry.hxx> +#include <vcl/toolkit/viewdataentry.hxx> +#include <tools/debug.hxx> +#include <osl/diagnose.h> + +#include <memory> +#include <unordered_map> + + +typedef std::unordered_map<SvTreeListEntry*, std::unique_ptr<SvViewDataEntry>> SvDataTable; + +struct SvListView::Impl +{ + SvListView & m_rThis; + + SvDataTable m_DataTable; // Mapping SvTreeListEntry -> ViewData + + sal_uInt32 m_nVisibleCount; + sal_uInt32 m_nSelectionCount; + bool m_bVisPositionsValid; + + explicit Impl(SvListView & rThis) + : m_rThis(rThis) + , m_nVisibleCount(0) + , m_nSelectionCount(0) + , m_bVisPositionsValid(false) + {} + + void InitTable(); + void RemoveViewData( SvTreeListEntry* pParent ); + + void ActionMoving(SvTreeListEntry* pEntry); + void ActionMoved(); + void ActionInserted(SvTreeListEntry* pEntry); + void ActionInsertedTree(SvTreeListEntry* pEntry); + void ActionRemoving(SvTreeListEntry* pEntry); + void ActionClear(); +}; + + +SvTreeList::SvTreeList(SvListView& listView) : + mrOwnerListView(listView), + mbEnableInvalidate(true) +{ + nEntryCount = 0; + bAbsPositionsValid = false; + pRootItem.reset(new SvTreeListEntry); + eSortMode = SvSortMode::None; +} + +SvTreeList::~SvTreeList() +{ +} + +void SvTreeList::Broadcast( + SvListAction nActionId, + SvTreeListEntry* pEntry1, + SvTreeListEntry* pEntry2, + sal_uInt32 nPos +) +{ + mrOwnerListView.ModelNotification(nActionId, pEntry1, pEntry2, nPos); +} + +// an entry is visible if all parents are expanded +bool SvTreeList::IsEntryVisible( const SvListView* pView, SvTreeListEntry* pEntry ) const +{ + DBG_ASSERT(pView&&pEntry,"IsVisible:Invalid Params"); + bool bRetVal = false; + do + { + if ( pEntry == pRootItem.get() ) + { + bRetVal = true; + break; + } + pEntry = pEntry->pParent; + } while( pView->IsExpanded( pEntry ) ); + return bRetVal; +} + +sal_uInt16 SvTreeList::GetDepth( const SvTreeListEntry* pEntry ) const +{ + if (!pEntry) + return 0; + DBG_ASSERT(pEntry && pEntry!=pRootItem.get(),"GetDepth:Bad Entry"); + sal_uInt16 nDepth = 0; + while( pEntry && pEntry->pParent != pRootItem.get() ) + { + nDepth++; + pEntry = pEntry->pParent; + } + return nDepth; +} + +bool SvTreeList::IsAtRootDepth( const SvTreeListEntry* pEntry ) const +{ + return pEntry->pParent == pRootItem.get(); +} + +void SvTreeList::Clear() +{ + Broadcast( SvListAction::CLEARING ); + pRootItem->ClearChildren(); + nEntryCount = 0; + Broadcast( SvListAction::CLEARED ); +} + +bool SvTreeList::IsChild(const SvTreeListEntry* pParent, const SvTreeListEntry* pChild) const +{ + if ( !pParent ) + pParent = pRootItem.get(); + + if (pParent->m_Children.empty()) + return false; + + for (auto const& it : pParent->m_Children) + { + const SvTreeListEntry* pThis = it.get(); + if (pThis == pChild) + return true; + else + { + bool bIsChild = IsChild(pThis, pChild); + if (bIsChild) + return true; + } + } + return false; +} + +namespace { + +class FindByPointer +{ + const SvTreeListEntry* mpEntry; +public: + explicit FindByPointer(const SvTreeListEntry* p) : mpEntry(p) {} + + bool operator() (std::unique_ptr<SvTreeListEntry> const& rpEntry) const + { + return mpEntry == rpEntry.get(); + } +}; + +sal_uInt32 findEntryPosition(const SvTreeListEntries& rDst, const SvTreeListEntry* pEntry) +{ + SvTreeListEntries::const_iterator itPos = std::find_if(rDst.begin(), rDst.end(), FindByPointer(pEntry)); + if (itPos == rDst.end()) + return static_cast<sal_uInt32>(~0); + + return static_cast<sal_uInt32>(std::distance(rDst.begin(), itPos)); +} + +} + +sal_uInt32 SvTreeList::Move(SvTreeListEntry* pSrcEntry,SvTreeListEntry* pTargetParent,sal_uInt32 nListPos) +{ + // pDest may be 0! + DBG_ASSERT(pSrcEntry,"Entry?"); + if ( !pTargetParent ) + pTargetParent = pRootItem.get(); + DBG_ASSERT(pSrcEntry!=pTargetParent,"Move:Source=Target"); + + Broadcast( SvListAction::MOVING, pSrcEntry, pTargetParent, nListPos ); + + if ( pSrcEntry == pTargetParent ) + // You can't move an entry onto itself as the parent. Just return its + // position and bail out. + return pSrcEntry->GetChildListPos(); + + bAbsPositionsValid = false; + + SvTreeListEntries& rDst = pTargetParent->m_Children; + SvTreeListEntries& rSrc = pSrcEntry->pParent->m_Children; + + bool bSameParent = pTargetParent == pSrcEntry->pParent; + + // Find the position of the entry being moved in the source container. + SvTreeListEntries::iterator itSrcPos = rSrc.begin(), itEnd = rSrc.end(); + for (; itSrcPos != itEnd; ++itSrcPos) + { + const SvTreeListEntry* p = (*itSrcPos).get(); + if (p == pSrcEntry) + // Found + break; + } + + if (itSrcPos == itEnd) + { + OSL_FAIL("Source entry not found! This should never happen."); + return pSrcEntry->GetChildListPos(); + } + + if (bSameParent) + { + // Moving within the same parent. + + size_t nSrcPos = std::distance(rSrc.begin(), itSrcPos); + if (nSrcPos == nListPos) + // Nothing to move here. + return pSrcEntry->GetChildListPos(); + + if (nSrcPos < nListPos) + // Destination position shifts left after removing the original. + --nListPos; + + // Release the original. + std::unique_ptr<SvTreeListEntry> pOriginal(std::move(*itSrcPos)); + assert(pOriginal); + rSrc.erase(itSrcPos); + + // Determine the insertion position. + SvTreeListEntries::iterator itDstPos = rSrc.end(); + if (nListPos < rSrc.size()) + { + itDstPos = rSrc.begin(); + std::advance(itDstPos, nListPos); + } + rSrc.insert(itDstPos, std::move(pOriginal)); + } + else + { + // Moving from one parent to another. + SvTreeListEntries::iterator itDstPos = rDst.end(); + if (nListPos < rDst.size()) + { + itDstPos = rDst.begin(); + std::advance(itDstPos, nListPos); + } + std::unique_ptr<SvTreeListEntry> pOriginal(std::move(*itSrcPos)); + assert(pOriginal); + rSrc.erase(itSrcPos); + rDst.insert(itDstPos, std::move(pOriginal)); + } + + // move parent (do this only now, because we need the parent for + // deleting the old child list!) + pSrcEntry->pParent = pTargetParent; + + // correct list position in target list + SetListPositions(rDst); + if (!bSameParent) + SetListPositions(rSrc); + + sal_uInt32 nRetVal = findEntryPosition(rDst, pSrcEntry); + OSL_ENSURE(nRetVal == pSrcEntry->GetChildListPos(), "ListPos not valid"); + Broadcast( SvListAction::MOVED,pSrcEntry,pTargetParent,nRetVal); + return nRetVal; +} + +sal_uInt32 SvTreeList::Copy(SvTreeListEntry* pSrcEntry,SvTreeListEntry* pTargetParent,sal_uInt32 nListPos) +{ + // pDest may be 0! + DBG_ASSERT(pSrcEntry,"Entry?"); + if ( !pTargetParent ) + pTargetParent = pRootItem.get(); + + bAbsPositionsValid = false; + + sal_uInt32 nCloneCount = 0; + SvTreeListEntry* pClonedEntry = Clone( pSrcEntry, nCloneCount ); + nEntryCount += nCloneCount; + + SvTreeListEntries& rDst = pTargetParent->m_Children; + + pClonedEntry->pParent = pTargetParent; // move parent + + if (nListPos < rDst.size()) + { + SvTreeListEntries::iterator itPos = rDst.begin(); // insertion position. + std::advance(itPos, nListPos); + rDst.insert(itPos, std::unique_ptr<SvTreeListEntry>(pClonedEntry)); + } + else + rDst.push_back(std::unique_ptr<SvTreeListEntry>(pClonedEntry)); + + SetListPositions(rDst); // correct list position in target list + + Broadcast( SvListAction::INSERTED_TREE, pClonedEntry ); + sal_uInt32 nRetVal = findEntryPosition(rDst, pClonedEntry); + return nRetVal; +} + +void SvTreeList::Move( SvTreeListEntry* pSrcEntry, SvTreeListEntry* pDstEntry ) +{ + SvTreeListEntry* pParent; + sal_uInt32 nPos; + + if ( !pDstEntry ) + { + pParent = pRootItem.get(); + nPos = 0; + } + else + { + pParent = pDstEntry->pParent; + nPos = pDstEntry->GetChildListPos(); + nPos++; // (On screen:) insert _below_ pDstEntry + } + Move( pSrcEntry, pParent, nPos ); +} + +void SvTreeList::InsertTree(SvTreeListEntry* pSrcEntry, + SvTreeListEntry* pTargetParent,sal_uInt32 nListPos) +{ + DBG_ASSERT(pSrcEntry,"InsertTree:Entry?"); + if ( !pSrcEntry ) + return; + + if ( !pTargetParent ) + pTargetParent = pRootItem.get(); + + // take sorting into account + GetInsertionPos( pSrcEntry, pTargetParent, nListPos ); + + bAbsPositionsValid = false; + + pSrcEntry->pParent = pTargetParent; // move parent + SvTreeListEntries& rDst = pTargetParent->m_Children; + + if (nListPos < rDst.size()) + { + SvTreeListEntries::iterator itPos = rDst.begin(); + std::advance(itPos, nListPos); + rDst.insert(itPos, std::unique_ptr<SvTreeListEntry>(pSrcEntry)); + } + else + rDst.push_back(std::unique_ptr<SvTreeListEntry>(pSrcEntry)); + + SetListPositions(rDst); // correct list position in target list + nEntryCount += GetChildCount( pSrcEntry ); + nEntryCount++; // the parent is new, too + + Broadcast(SvListAction::INSERTED_TREE, pSrcEntry ); +} + +SvTreeListEntry* SvTreeList::CloneEntry( SvTreeListEntry* pSource ) const +{ + if( aCloneLink.IsSet() ) + return aCloneLink.Call( pSource ); + SvTreeListEntry* pEntry = new SvTreeListEntry; + pEntry->Clone(pSource); + return pEntry; +} + +SvTreeListEntry* SvTreeList::Clone( SvTreeListEntry* pEntry, sal_uInt32& nCloneCount ) const +{ + SvTreeListEntry* pClonedEntry = CloneEntry( pEntry ); + nCloneCount = 1; + if (!pEntry->m_Children.empty()) + // Clone the child entries. + CloneChildren(pClonedEntry->m_Children, nCloneCount, pEntry->m_Children, *pClonedEntry); + + return pClonedEntry; +} + +void SvTreeList::CloneChildren( + SvTreeListEntries& rDst, sal_uInt32& rCloneCount, SvTreeListEntries& rSrc, SvTreeListEntry& rNewParent) const +{ + SvTreeListEntries aClone; + for (auto const& elem : rSrc) + { + SvTreeListEntry& rEntry = *elem; + std::unique_ptr<SvTreeListEntry> pNewEntry(CloneEntry(&rEntry)); + ++rCloneCount; + pNewEntry->pParent = &rNewParent; + if (!rEntry.m_Children.empty()) + // Clone entries recursively. + CloneChildren(pNewEntry->m_Children, rCloneCount, rEntry.m_Children, *pNewEntry); + + aClone.push_back(std::move(pNewEntry)); + } + + rDst.swap(aClone); +} + +sal_uInt32 SvTreeList::GetChildCount( const SvTreeListEntry* pParent ) const +{ + if ( !pParent ) + return GetEntryCount(); + + if (pParent->m_Children.empty()) + return 0; + + sal_uInt32 nCount = 0; + sal_uInt16 nRefDepth = GetDepth( pParent ); + sal_uInt16 nActDepth = nRefDepth; + do + { + pParent = Next(const_cast<SvTreeListEntry*>(pParent), &nActDepth); + nCount++; + } while( pParent && nRefDepth < nActDepth ); + nCount--; + return nCount; +} + +sal_uInt32 SvTreeList::GetVisibleChildCount(const SvListView* pView, SvTreeListEntry* pParent) const +{ + DBG_ASSERT(pView,"GetVisChildCount:No View"); + if ( !pParent ) + pParent = pRootItem.get(); + + if (!pParent || !pView->IsExpanded(pParent) || pParent->m_Children.empty()) + return 0; + + sal_uInt32 nCount = 0; + sal_uInt16 nRefDepth = GetDepth( pParent ); + sal_uInt16 nActDepth = nRefDepth; + do + { + pParent = NextVisible( pView, pParent, &nActDepth ); + nCount++; + } while( pParent && nRefDepth < nActDepth ); + nCount--; + return nCount; +} + +sal_uInt32 SvTreeList::GetChildSelectionCount(const SvListView* pView,SvTreeListEntry* pParent) const +{ + DBG_ASSERT(pView,"GetChildSelCount:No View"); + if ( !pParent ) + pParent = pRootItem.get(); + + if (!pParent || pParent->m_Children.empty()) + return 0; + + sal_uInt32 nCount = 0; + sal_uInt16 nRefDepth = GetDepth( pParent ); + sal_uInt16 nActDepth = nRefDepth; + do + { + pParent = Next( pParent, &nActDepth ); + if( pParent && pView->IsSelected( pParent ) && nRefDepth < nActDepth) + nCount++; + } while( pParent && nRefDepth < nActDepth ); +// nCount--; + return nCount; +} + +SvTreeListEntry* SvTreeList::First() const +{ + if ( nEntryCount ) + return pRootItem->m_Children[0].get(); + else + return nullptr; +} + +SvTreeListEntry* SvTreeList::Next( SvTreeListEntry* pActEntry, sal_uInt16* pDepth ) const +{ + DBG_ASSERT( pActEntry && pActEntry->pParent, "SvTreeList::Next: invalid entry/parent!" ); + if ( !pActEntry || !pActEntry->pParent ) + return nullptr; + + sal_uInt16 nDepth = 0; + bool bWithDepth = false; + if ( pDepth ) + { + nDepth = *pDepth; + bWithDepth = true; + } + + // Get the list where the current entry belongs to (from its parent). + SvTreeListEntries* pActualList = &pActEntry->pParent->m_Children; + sal_uInt32 nActualPos = pActEntry->GetChildListPos(); + + if (!pActEntry->m_Children.empty()) + { + // The current entry has children. Get its first child entry. + nDepth++; + pActEntry = pActEntry->m_Children[0].get(); + if ( bWithDepth ) + *pDepth = nDepth; + return pActEntry; + } + + if (pActualList->size() > (nActualPos+1)) + { + // Get the next sibling of the current entry. + pActEntry = (*pActualList)[nActualPos+1].get(); + if ( bWithDepth ) + *pDepth = nDepth; + return pActEntry; + } + + // Move up level(s) until we find the level where the next sibling exists. + SvTreeListEntry* pParent = pActEntry->pParent; + nDepth--; + while( pParent != pRootItem.get() && pParent != nullptr ) + { + DBG_ASSERT(pParent!=nullptr,"TreeData corrupt!"); + pActualList = &pParent->pParent->m_Children; + nActualPos = pParent->GetChildListPos(); + if (pActualList->size() > (nActualPos+1)) + { + pActEntry = (*pActualList)[nActualPos+1].get(); + if ( bWithDepth ) + *pDepth = nDepth; + return pActEntry; + } + pParent = pParent->pParent; + nDepth--; + } + return nullptr; +} + +SvTreeListEntry* SvTreeList::Prev( SvTreeListEntry* pActEntry ) const +{ + DBG_ASSERT(pActEntry!=nullptr,"Entry?"); + + SvTreeListEntries* pActualList = &pActEntry->pParent->m_Children; + sal_uInt32 nActualPos = pActEntry->GetChildListPos(); + + if ( nActualPos > 0 ) + { + pActEntry = (*pActualList)[nActualPos-1].get(); + while (!pActEntry->m_Children.empty()) + { + pActualList = &pActEntry->m_Children; + pActEntry = pActualList->back().get(); + } + return pActEntry; + } + if ( pActEntry->pParent == pRootItem.get() ) + return nullptr; + + pActEntry = pActEntry->pParent; + + if ( pActEntry ) + { + return pActEntry; + } + return nullptr; +} + +SvTreeListEntry* SvTreeList::Last() const +{ + SvTreeListEntries* pActList = &pRootItem->m_Children; + SvTreeListEntry* pEntry = nullptr; + while (!pActList->empty()) + { + pEntry = pActList->back().get(); + pActList = &pEntry->m_Children; + } + return pEntry; +} + +sal_uInt32 SvTreeList::GetVisiblePos( const SvListView* pView, SvTreeListEntry const * pEntry ) const +{ + DBG_ASSERT(pView&&pEntry,"View/Entry?"); + + if (!pView->m_pImpl->m_bVisPositionsValid) + { + // to make GetVisibleCount refresh the positions + const_cast<SvListView*>(pView)->m_pImpl->m_nVisibleCount = 0; + GetVisibleCount( const_cast<SvListView*>(pView) ); + } + const SvViewDataEntry* pViewData = pView->GetViewData( pEntry ); + return pViewData->nVisPos; +} + +sal_uInt32 SvTreeList::GetVisibleCount( SvListView* pView ) const +{ + assert(pView && "GetVisCount:No View"); + if( !pView->HasViewData() ) + return 0; + if (pView->m_pImpl->m_nVisibleCount) + return pView->m_pImpl->m_nVisibleCount; + + sal_uInt32 nPos = 0; + SvTreeListEntry* pEntry = First(); // first entry is always visible + while ( pEntry ) + { + SvViewDataEntry* pViewData = pView->GetViewData( pEntry ); + pViewData->nVisPos = nPos; + nPos++; + pEntry = NextVisible( pView, pEntry ); + } +#ifdef DBG_UTIL + if( nPos > 10000000 ) + { + OSL_FAIL("nVisibleCount bad"); + } +#endif + pView->m_pImpl->m_nVisibleCount = nPos; + pView->m_pImpl->m_bVisPositionsValid = true; + return nPos; +} + + +// For performance reasons, this function assumes that the passed entry is +// already visible. +SvTreeListEntry* SvTreeList::NextVisible(const SvListView* pView,SvTreeListEntry* pActEntry,sal_uInt16* pActDepth) const +{ + DBG_ASSERT(pView,"NextVisible:No View"); + if ( !pActEntry ) + return nullptr; + + sal_uInt16 nDepth = 0; + bool bWithDepth = false; + if ( pActDepth ) + { + nDepth = *pActDepth; + bWithDepth = true; + } + + SvTreeListEntries* pActualList = &pActEntry->pParent->m_Children; + sal_uInt32 nActualPos = pActEntry->GetChildListPos(); + + if ( pView->IsExpanded(pActEntry) ) + { + OSL_ENSURE(!pActEntry->m_Children.empty(), "Pass entry is supposed to have child entries."); + + nDepth++; + pActEntry = pActEntry->m_Children[0].get(); + if ( bWithDepth ) + *pActDepth = nDepth; + return pActEntry; + } + + nActualPos++; + if ( pActualList->size() > nActualPos ) + { + pActEntry = (*pActualList)[nActualPos].get(); + if ( bWithDepth ) + *pActDepth = nDepth; + return pActEntry; + } + + SvTreeListEntry* pParent = pActEntry->pParent; + nDepth--; + while( pParent != pRootItem.get() ) + { + pActualList = &pParent->pParent->m_Children; + nActualPos = pParent->GetChildListPos(); + nActualPos++; + if ( pActualList->size() > nActualPos ) + { + pActEntry = (*pActualList)[nActualPos].get(); + if ( bWithDepth ) + *pActDepth = nDepth; + return pActEntry; + } + pParent = pParent->pParent; + nDepth--; + } + return nullptr; +} + + +// For performance reasons, this function assumes that the passed entry is +// already visible. + +SvTreeListEntry* SvTreeList::PrevVisible(const SvListView* pView, SvTreeListEntry* pActEntry) const +{ + DBG_ASSERT(pView&&pActEntry,"PrevVis:View/Entry?"); + + SvTreeListEntries* pActualList = &pActEntry->pParent->m_Children; + sal_uInt32 nActualPos = pActEntry->GetChildListPos(); + + if ( nActualPos > 0 ) + { + pActEntry = (*pActualList)[nActualPos-1].get(); + while( pView->IsExpanded(pActEntry) ) + { + pActualList = &pActEntry->m_Children; + pActEntry = pActualList->back().get(); + } + return pActEntry; + } + + if ( pActEntry->pParent == pRootItem.get() ) + return nullptr; + + pActEntry = pActEntry->pParent; + if ( pActEntry ) + { + return pActEntry; + } + return nullptr; +} + +SvTreeListEntry* SvTreeList::LastVisible( const SvListView* pView) const +{ + DBG_ASSERT(pView,"LastVis:No View"); + SvTreeListEntry* pEntry = Last(); + while( pEntry && !IsEntryVisible( pView, pEntry ) ) + pEntry = PrevVisible( pView, pEntry ); + return pEntry; +} + +SvTreeListEntry* SvTreeList::NextVisible(const SvListView* pView,SvTreeListEntry* pEntry,sal_uInt16& nDelta) const +{ + DBG_ASSERT(pView&&pEntry&&IsEntryVisible(pView,pEntry),"NextVis:Wrong Prms/!Vis"); + + sal_uInt32 nVisPos = GetVisiblePos( pView, pEntry ); + // nDelta entries existent? + // example: 0,1,2,3,4,5,6,7,8,9 nVisPos=5 nDelta=7 + // nNewDelta = 10-nVisPos-1 == 4 + if (nVisPos+nDelta >= pView->m_pImpl->m_nVisibleCount) + { + nDelta = static_cast<sal_uInt16>(pView->m_pImpl->m_nVisibleCount-nVisPos); + nDelta--; + } + sal_uInt16 nDeltaTmp = nDelta; + while( nDeltaTmp ) + { + pEntry = NextVisible( pView, pEntry ); + nDeltaTmp--; + DBG_ASSERT(pEntry,"Entry?"); + } + return pEntry; +} + +SvTreeListEntry* SvTreeList::PrevVisible( const SvListView* pView, SvTreeListEntry* pEntry, sal_uInt16& nDelta ) const +{ + DBG_ASSERT(pView&&pEntry&&IsEntryVisible(pView,pEntry),"PrevVis:Parms/!Vis"); + + sal_uInt32 nVisPos = GetVisiblePos( pView, pEntry ); + // nDelta entries existent? + // example: 0,1,2,3,4,5,6,7,8,9 nVisPos=8 nDelta=20 + // nNewDelta = nNewVisPos + if ( nDelta > nVisPos ) + nDelta = static_cast<sal_uInt16>(nVisPos); + sal_uInt16 nDeltaTmp = nDelta; + while( nDeltaTmp ) + { + pEntry = PrevVisible( pView, pEntry ); + nDeltaTmp--; + DBG_ASSERT(pEntry,"Entry?"); + } + return pEntry; +} + +SvTreeListEntry* SvTreeList::FirstSelected( const SvListView* pView) const +{ + DBG_ASSERT(pView,"FirstSel:No View"); + if( !pView ) + return nullptr; + SvTreeListEntry* pActSelEntry = First(); + while( pActSelEntry && !pView->IsSelected(pActSelEntry) ) + pActSelEntry = NextVisible( pView, pActSelEntry ); + return pActSelEntry; +} + + +SvTreeListEntry* SvTreeList::FirstChild( SvTreeListEntry* pParent ) const +{ + if ( !pParent ) + pParent = pRootItem.get(); + SvTreeListEntry* pResult; + if (!pParent->m_Children.empty()) + pResult = pParent->m_Children[0].get(); + else + pResult = nullptr; + return pResult; +} + +SvTreeListEntry* SvTreeList::NextSelected( const SvListView* pView, SvTreeListEntry* pEntry ) const +{ + DBG_ASSERT(pView&&pEntry,"NextSel:View/Entry?"); + pEntry = Next( pEntry ); + while( pEntry && !pView->IsSelected(pEntry) ) + pEntry = Next( pEntry ); + return pEntry; +} + +sal_uInt32 SvTreeList::Insert( SvTreeListEntry* pEntry,SvTreeListEntry* pParent,sal_uInt32 nPos ) +{ + DBG_ASSERT( pEntry,"Entry?"); + + if ( !pParent ) + pParent = pRootItem.get(); + + SvTreeListEntries& rList = pParent->m_Children; + + // take sorting into account + GetInsertionPos( pEntry, pParent, nPos ); + + bAbsPositionsValid = false; + pEntry->pParent = pParent; + + if (nPos < rList.size()) + { + SvTreeListEntries::iterator itPos = rList.begin(); + std::advance(itPos, nPos); + rList.insert(itPos, std::unique_ptr<SvTreeListEntry>(pEntry)); + } + else + rList.push_back(std::unique_ptr<SvTreeListEntry>(pEntry)); + + nEntryCount++; + if (nPos != TREELIST_APPEND && (nPos != (rList.size()-1))) + SetListPositions(rList); + else + pEntry->nListPos = rList.size()-1; + + Broadcast( SvListAction::INSERTED, pEntry ); + return nPos; // pEntry->nListPos; +} + +sal_uInt32 SvTreeList::GetAbsPos( const SvTreeListEntry* pEntry) const +{ + if ( !bAbsPositionsValid ) + const_cast<SvTreeList*>(this)->SetAbsolutePositions(); + return pEntry->nAbsPos; +} + +sal_uInt32 SvTreeList::GetRelPos( const SvTreeListEntry* pChild ) +{ + return pChild->GetChildListPos(); +} + +void SvTreeList::SetAbsolutePositions() +{ + sal_uInt32 nPos = 0; + SvTreeListEntry* pEntry = First(); + while ( pEntry ) + { + pEntry->nAbsPos = nPos; + nPos++; + pEntry = Next( pEntry ); + } + bAbsPositionsValid = true; +} + +void SvListView::ExpandListEntry( SvTreeListEntry* pEntry ) +{ + DBG_ASSERT(pEntry,"Expand:View/Entry?"); + if ( IsExpanded(pEntry) ) + return; + + DBG_ASSERT(!pEntry->m_Children.empty(), "SvTreeList::Expand: We expected to have child entries."); + + SvViewDataEntry* pViewData = GetViewData(pEntry); + pViewData->SetExpanded(true); + SvTreeListEntry* pParent = pEntry->pParent; + // if parent is visible, invalidate status data + if ( IsExpanded( pParent ) ) + { + m_pImpl->m_bVisPositionsValid = false; + m_pImpl->m_nVisibleCount = 0; + } +} + +void SvListView::CollapseListEntry( SvTreeListEntry* pEntry ) +{ + DBG_ASSERT(pEntry,"Collapse:View/Entry?"); + if ( !IsExpanded(pEntry) ) + return; + + DBG_ASSERT(!pEntry->m_Children.empty(), "SvTreeList::Collapse: We expected to have child entries."); + + SvViewDataEntry* pViewData = GetViewData( pEntry ); + pViewData->SetExpanded(false); + + SvTreeListEntry* pParent = pEntry->pParent; + if ( IsExpanded(pParent) ) + { + m_pImpl->m_nVisibleCount = 0; + m_pImpl->m_bVisPositionsValid = false; + } +} + +bool SvListView::SelectListEntry( SvTreeListEntry* pEntry, bool bSelect ) +{ + DBG_ASSERT(pEntry,"Select:View/Entry?"); + SvViewDataEntry* pViewData = GetViewData( pEntry ); + if ( bSelect ) + { + if ( pViewData->IsSelected() || !pViewData->IsSelectable() ) + return false; + else + { + pViewData->SetSelected(true); + m_pImpl->m_nSelectionCount++; + } + } + else + { + if ( !pViewData->IsSelected() ) + return false; + else + { + pViewData->SetSelected(false); + m_pImpl->m_nSelectionCount--; + } + } + return true; +} + +bool SvTreeList::Remove( const SvTreeListEntry* pEntry ) +{ + DBG_ASSERT(pEntry,"Cannot remove root, use clear"); + + if( !pEntry->pParent ) + { + OSL_FAIL("Removing entry not in model!"); + // Under certain circumstances (which?), the explorer deletes entries + // from the view that it hasn't inserted into the view. We don't want + // to crash, so we catch this case here. + return false; + } + + Broadcast(SvListAction::REMOVING, const_cast<SvTreeListEntry*>(pEntry)); + sal_uInt32 nRemoved = 1 + GetChildCount(pEntry); + bAbsPositionsValid = false; + + SvTreeListEntry* pParent = pEntry->pParent; + SvTreeListEntries& rList = pParent->m_Children; + bool bLastEntry = false; + + // Since we need the live instance of SvTreeListEntry for broadcasting, + // we first need to pop it from the container, broadcast it, then delete + // the instance manually at the end. + + std::unique_ptr<SvTreeListEntry> pEntryDeleter; + if ( pEntry->HasChildListPos() ) + { + size_t nListPos = pEntry->GetChildListPos(); + bLastEntry = (nListPos == (rList.size()-1)); + SvTreeListEntries::iterator it = rList.begin(); + std::advance(it, nListPos); + pEntryDeleter = std::move(*it); + rList.erase(it); + } + else + { + SvTreeListEntries::iterator it = + std::find_if(rList.begin(), rList.end(), FindByPointer(pEntry)); + if (it != rList.end()) + { + pEntryDeleter = std::move(*it); + rList.erase(it); + } + } + + if (!rList.empty() && !bLastEntry) + SetListPositions(rList); + + nEntryCount -= nRemoved; + Broadcast(SvListAction::REMOVED, const_cast<SvTreeListEntry*>(pEntry)); + + return true; +} + +SvTreeListEntry* SvTreeList::GetEntryAtAbsPos( sal_uInt32 nAbsPos ) const +{ + SvTreeListEntry* pEntry = First(); + while ( nAbsPos && pEntry ) + { + pEntry = Next( pEntry ); + nAbsPos--; + } + return pEntry; +} + +SvTreeListEntry* SvTreeList::GetEntryAtVisPos( const SvListView* pView, sal_uInt32 nVisPos ) const +{ + DBG_ASSERT(pView,"GetEntryAtVisPos:No View"); + SvTreeListEntry* pEntry = First(); + while ( nVisPos && pEntry ) + { + pEntry = NextVisible( pView, pEntry ); + nVisPos--; + } + return pEntry; +} + +void SvTreeList::SetListPositions( SvTreeListEntries& rEntries ) +{ + if (rEntries.empty()) + return; + + SvTreeListEntry& rFirst = *rEntries.front(); + if (rFirst.pParent) + rFirst.pParent->InvalidateChildrensListPositions(); +} + +void SvTreeList::EnableInvalidate( bool bEnable ) +{ + mbEnableInvalidate = bEnable; +} + +void SvTreeList::InvalidateEntry( SvTreeListEntry* pEntry ) +{ + if (!mbEnableInvalidate) + return; + + Broadcast( SvListAction::INVALIDATE_ENTRY, pEntry ); +} + +SvTreeListEntry* SvTreeList::GetRootLevelParent( SvTreeListEntry* pEntry ) const +{ + DBG_ASSERT(pEntry,"GetRootLevelParent:No Entry"); + SvTreeListEntry* pCurParent = nullptr; + if ( pEntry ) + { + pCurParent = pEntry->pParent; + if ( pCurParent == pRootItem.get() ) + return pEntry; // is its own parent + while( pCurParent && pCurParent->pParent != pRootItem.get() ) + pCurParent = pCurParent->pParent; + } + return pCurParent; +} + +SvListView::SvListView() + : m_pImpl(new Impl(*this)) +{ + pModel.reset(new SvTreeList(*this)); + m_pImpl->InitTable(); +} + +void SvListView::dispose() +{ + pModel.reset(); +} + +SvListView::~SvListView() +{ + m_pImpl->m_DataTable.clear(); +} + +sal_uInt32 SvListView::GetSelectionCount() const +{ return m_pImpl->m_nSelectionCount; } + +bool SvListView::HasViewData() const +{ return m_pImpl->m_DataTable.size() > 1; } // There's always a ROOT + + +void SvListView::Impl::InitTable() +{ + DBG_ASSERT(m_rThis.pModel,"InitTable:No Model"); + DBG_ASSERT(!m_nSelectionCount && !m_nVisibleCount && !m_bVisPositionsValid, + "InitTable: Not cleared!"); + + if (!m_DataTable.empty()) + { + DBG_ASSERT(m_DataTable.size() == 1, "InitTable: TableCount != 1"); + // Delete the view data allocated to the Clear in the root. + // Attention: The model belonging to the root entry (and thus the entry + // itself) might already be deleted. + m_DataTable.clear(); + } + + SvTreeListEntry* pEntry; + + // insert root entry + pEntry = m_rThis.pModel->pRootItem.get(); + std::unique_ptr<SvViewDataEntry> pViewData(new SvViewDataEntry); + pViewData->SetExpanded(true); + m_DataTable.insert(std::make_pair(pEntry, std::move(pViewData))); + // now all the other entries + pEntry = m_rThis.pModel->First(); + while( pEntry ) + { + pViewData = std::make_unique<SvViewDataEntry>(); + m_rThis.InitViewData( pViewData.get(), pEntry ); + m_DataTable.insert(std::make_pair(pEntry, std::move(pViewData))); + pEntry = m_rThis.pModel->Next( pEntry ); + } +} + +void SvListView::Clear() +{ + m_pImpl->m_DataTable.clear(); + m_pImpl->m_nSelectionCount = 0; + m_pImpl->m_nVisibleCount = 0; + m_pImpl->m_bVisPositionsValid = false; + if( pModel ) + { + // insert root entry + SvTreeListEntry* pEntry = pModel->pRootItem.get(); + std::unique_ptr<SvViewDataEntry> pViewData(new SvViewDataEntry); + pViewData->SetExpanded(true); + m_pImpl->m_DataTable.insert(std::make_pair(pEntry, std::move(pViewData))); + } +} + +void SvListView::ModelHasCleared() +{ +} + +void SvListView::ModelHasInserted( SvTreeListEntry* ) +{ +} + +void SvListView::ModelHasInsertedTree( SvTreeListEntry* ) +{ +} + +void SvListView::ModelIsMoving( SvTreeListEntry* /* pSource */ ) +{ +} + + +void SvListView::ModelHasMoved( SvTreeListEntry* ) +{ +} + +void SvListView::ModelIsRemoving( SvTreeListEntry* ) +{ +} + +void SvListView::ModelHasRemoved( SvTreeListEntry* ) +{ + //WARNING WARNING WARNING + //The supplied pointer should have been deleted + //before this call. Be careful not to use it!!! +} + +void SvListView::ModelHasEntryInvalidated( SvTreeListEntry*) +{ +} + +void SvListView::Impl::ActionMoving( SvTreeListEntry* pEntry ) +{ + SvTreeListEntry* pParent = pEntry->pParent; + DBG_ASSERT(pParent,"Model not consistent"); + if (pParent != m_rThis.pModel->pRootItem.get() && pParent->m_Children.size() == 1) + { + SvViewDataEntry* pViewData = m_DataTable.find( pParent )->second.get(); + pViewData->SetExpanded(false); + } + // preliminary + m_nVisibleCount = 0; + m_bVisPositionsValid = false; +} + +void SvListView::Impl::ActionMoved() +{ + m_nVisibleCount = 0; + m_bVisPositionsValid = false; +} + +void SvListView::Impl::ActionInserted( SvTreeListEntry* pEntry ) +{ + DBG_ASSERT(pEntry,"Insert:No Entry"); + std::unique_ptr<SvViewDataEntry> pData(new SvViewDataEntry()); + m_rThis.InitViewData( pData.get(), pEntry ); + std::pair<SvDataTable::iterator, bool> aSuccess = + m_DataTable.insert(std::make_pair(pEntry, std::move(pData))); + DBG_ASSERT(aSuccess.second,"Entry already in View"); + if (m_nVisibleCount && m_rThis.pModel->IsEntryVisible(&m_rThis, pEntry)) + { + m_nVisibleCount = 0; + m_bVisPositionsValid = false; + } +} + +void SvListView::Impl::ActionInsertedTree( SvTreeListEntry* pEntry ) +{ + if (m_rThis.pModel->IsEntryVisible(&m_rThis, pEntry)) + { + m_nVisibleCount = 0; + m_bVisPositionsValid = false; + } + // iterate over entry and its children + SvTreeListEntry* pCurEntry = pEntry; + sal_uInt16 nRefDepth = m_rThis.pModel->GetDepth( pCurEntry ); + while( pCurEntry ) + { + DBG_ASSERT(m_DataTable.find(pCurEntry) != m_DataTable.end(),"Entry already in Table"); + std::unique_ptr<SvViewDataEntry> pViewData(new SvViewDataEntry()); + m_rThis.InitViewData( pViewData.get(), pEntry ); + m_DataTable.insert(std::make_pair(pCurEntry, std::move(pViewData))); + pCurEntry = m_rThis.pModel->Next( pCurEntry ); + if ( pCurEntry && m_rThis.pModel->GetDepth(pCurEntry) <= nRefDepth) + pCurEntry = nullptr; + } +} + +void SvListView::Impl::RemoveViewData( SvTreeListEntry* pParent ) +{ + for (auto const& it : pParent->m_Children) + { + SvTreeListEntry& rEntry = *it; + m_DataTable.erase(&rEntry); + if (rEntry.HasChildren()) + RemoveViewData(&rEntry); + } +} + + +void SvListView::Impl::ActionRemoving( SvTreeListEntry* pEntry ) +{ + DBG_ASSERT(pEntry,"Remove:No Entry"); + + SvViewDataEntry* pViewData = m_DataTable.find( pEntry )->second.get(); + sal_uInt32 nSelRemoved = 0; + if ( pViewData->IsSelected() ) + nSelRemoved = 1 + m_rThis.pModel->GetChildSelectionCount(&m_rThis, pEntry); + m_nSelectionCount -= nSelRemoved; + sal_uInt32 nVisibleRemoved = 0; + if (m_rThis.pModel->IsEntryVisible(&m_rThis, pEntry)) + nVisibleRemoved = 1 + m_rThis.pModel->GetVisibleChildCount(&m_rThis, pEntry); + if( m_nVisibleCount ) + { +#ifdef DBG_UTIL + if (m_nVisibleCount < nVisibleRemoved) + { + OSL_FAIL("nVisibleRemoved bad"); + } +#endif + m_nVisibleCount -= nVisibleRemoved; + } + m_bVisPositionsValid = false; + + m_DataTable.erase(pEntry); + RemoveViewData( pEntry ); + + SvTreeListEntry* pCurEntry = pEntry->pParent; + if (pCurEntry && pCurEntry != m_rThis.pModel->pRootItem.get() && pCurEntry->m_Children.size() == 1) + { + pViewData = m_DataTable.find(pCurEntry)->second.get(); + pViewData->SetExpanded(false); + } +} + +void SvListView::Impl::ActionClear() +{ + m_rThis.Clear(); +} + +void SvListView::ModelNotification( SvListAction nActionId, SvTreeListEntry* pEntry1, + SvTreeListEntry* /*pEntry2*/, sal_uInt32 /*nPos*/ ) +{ + + switch( nActionId ) + { + case SvListAction::INSERTED: + m_pImpl->ActionInserted( pEntry1 ); + ModelHasInserted( pEntry1 ); + break; + case SvListAction::INSERTED_TREE: + m_pImpl->ActionInsertedTree( pEntry1 ); + ModelHasInsertedTree( pEntry1 ); + break; + case SvListAction::REMOVING: + ModelIsRemoving( pEntry1 ); + m_pImpl->ActionRemoving( pEntry1 ); + break; + case SvListAction::REMOVED: + ModelHasRemoved( pEntry1 ); + break; + case SvListAction::MOVING: + ModelIsMoving( pEntry1 ); + m_pImpl->ActionMoving( pEntry1 ); + break; + case SvListAction::MOVED: + m_pImpl->ActionMoved(); + ModelHasMoved( pEntry1 ); + break; + case SvListAction::CLEARING: + m_pImpl->ActionClear(); + ModelHasCleared(); // sic! for compatibility reasons! + break; + case SvListAction::CLEARED: + break; + case SvListAction::INVALIDATE_ENTRY: + // no action for the base class + ModelHasEntryInvalidated( pEntry1 ); + break; + case SvListAction::RESORTED: + m_pImpl->m_bVisPositionsValid = false; + break; + case SvListAction::RESORTING: + break; + default: + OSL_FAIL("unknown ActionId"); + } +} + +void SvListView::InitViewData( SvViewDataEntry*, SvTreeListEntry* ) +{ +} + +bool SvListView::IsExpanded( SvTreeListEntry* pEntry ) const +{ + DBG_ASSERT(pEntry,"IsExpanded:No Entry"); + SvDataTable::const_iterator itr = m_pImpl->m_DataTable.find(pEntry); + DBG_ASSERT(itr != m_pImpl->m_DataTable.end(),"Entry not in Table"); + if (itr == m_pImpl->m_DataTable.end()) + return false; + return itr->second->IsExpanded(); +} + +bool SvListView::IsAllExpanded( SvTreeListEntry* pEntry ) const +{ + DBG_ASSERT(pEntry,"IsAllExpanded:No Entry"); + if (!IsExpanded(pEntry)) + return false; + const SvTreeListEntries& rChildren = pEntry->GetChildEntries(); + for (auto& rChild : rChildren) + { + if (rChild->HasChildren() || rChild->HasChildrenOnDemand()) + { + if (!IsAllExpanded(rChild.get())) + return false; + } + } + return true; +} + +bool SvListView::IsSelected(const SvTreeListEntry* pEntry) const +{ + DBG_ASSERT(pEntry,"IsExpanded:No Entry"); + SvDataTable::const_iterator itr = m_pImpl->m_DataTable.find(const_cast<SvTreeListEntry*>(pEntry)); + if (itr == m_pImpl->m_DataTable.end()) + return false; + return itr->second->IsSelected(); +} + +void SvListView::SetEntryFocus( SvTreeListEntry* pEntry, bool bFocus ) +{ + DBG_ASSERT(pEntry,"SetEntryFocus:No Entry"); + SvDataTable::iterator itr = m_pImpl->m_DataTable.find(pEntry); + DBG_ASSERT(itr != m_pImpl->m_DataTable.end(),"Entry not in Table"); + itr->second->SetFocus(bFocus); +} + +const SvViewDataEntry* SvListView::GetViewData( const SvTreeListEntry* pEntry ) const +{ + SvDataTable::const_iterator itr = + m_pImpl->m_DataTable.find(const_cast<SvTreeListEntry*>(pEntry)); + if (itr == m_pImpl->m_DataTable.end()) + return nullptr; + return itr->second.get(); +} + +SvViewDataEntry* SvListView::GetViewData( SvTreeListEntry* pEntry ) +{ + SvDataTable::iterator itr = m_pImpl->m_DataTable.find( pEntry ); + DBG_ASSERT(itr != m_pImpl->m_DataTable.end(),"Entry not in model or wrong view"); + return itr->second.get(); +} + +sal_Int32 SvTreeList::Compare(const SvTreeListEntry* pLeft, const SvTreeListEntry* pRight) const +{ + if( aCompareLink.IsSet()) + { + SvSortData aSortData; + aSortData.pLeft = pLeft; + aSortData.pRight = pRight; + return aCompareLink.Call( aSortData ); + } + return 0; +} + +void SvTreeList::Resort() +{ + Broadcast( SvListAction::RESORTING ); + bAbsPositionsValid = false; + ResortChildren( pRootItem.get() ); + Broadcast( SvListAction::RESORTED ); +} + +namespace { + +class SortComparator +{ + SvTreeList& mrList; +public: + + explicit SortComparator( SvTreeList& rList ) : mrList(rList) {} + + bool operator() (std::unique_ptr<SvTreeListEntry> const& rpLeft, + std::unique_ptr<SvTreeListEntry> const& rpRight) const + { + int nCompare = mrList.Compare(rpLeft.get(), rpRight.get()); + if (nCompare != 0 && mrList.GetSortMode() == SvSortMode::Descending) + { + if( nCompare < 0 ) + nCompare = 1; + else + nCompare = -1; + } + return nCompare < 0; + } +}; + +} + +void SvTreeList::ResortChildren( SvTreeListEntry* pParent ) +{ + DBG_ASSERT(pParent,"Parent not set"); + + if (pParent->m_Children.empty()) + return; + + SortComparator aComp(*this); + std::sort(pParent->m_Children.begin(), pParent->m_Children.end(), aComp); + + // Recursively sort child entries. + for (auto const& it : pParent->m_Children) + { + SvTreeListEntry& r = *it; + ResortChildren(&r); + } + + SetListPositions(pParent->m_Children); // correct list position in target list +} + +void SvTreeList::GetInsertionPos( SvTreeListEntry const * pEntry, SvTreeListEntry* pParent, + sal_uInt32& rPos ) +{ + DBG_ASSERT(pEntry,"No Entry"); + + if( eSortMode == SvSortMode::None ) + return; + + rPos = TREELIST_ENTRY_NOTFOUND; + const SvTreeListEntries& rChildList = GetChildList(pParent); + + if (rChildList.empty()) + return; + + tools::Long i = 0; + tools::Long j = rChildList.size()-1; + tools::Long k; + sal_Int32 nCompare = 1; + + do + { + k = (i+j)/2; + const SvTreeListEntry* pTempEntry = rChildList[k].get(); + nCompare = Compare( pEntry, pTempEntry ); + if (nCompare != 0 && eSortMode == SvSortMode::Descending) + { + if( nCompare < 0 ) + nCompare = 1; + else + nCompare = -1; + } + if( nCompare > 0 ) + i = k + 1; + else + j = k - 1; + } while( (nCompare != 0) && (i <= j) ); + + if( nCompare != 0 ) + { + if (i > static_cast<tools::Long>(rChildList.size()-1)) // not found, end of list + rPos = TREELIST_ENTRY_NOTFOUND; + else + rPos = i; // not found, middle of list + } + else + rPos = k; +} + +SvTreeListEntry* SvTreeList::GetEntry( SvTreeListEntry* pParent, sal_uInt32 nPos ) const +{ if ( !pParent ) + pParent = pRootItem.get(); + SvTreeListEntry* pRet = nullptr; + if (nPos < pParent->m_Children.size()) + pRet = pParent->m_Children[nPos].get(); + return pRet; +} + +SvTreeListEntry* SvTreeList::GetEntry( sal_uInt32 nRootPos ) const +{ + SvTreeListEntry* pRet = nullptr; + if (nEntryCount && nRootPos < pRootItem->m_Children.size()) + pRet = pRootItem->m_Children[nRootPos].get(); + return pRet; +} + +const SvTreeListEntries& SvTreeList::GetChildList( SvTreeListEntry* pParent ) const +{ + if ( !pParent ) + pParent = pRootItem.get(); + return pParent->m_Children; +} + +SvTreeListEntries& SvTreeList::GetChildList( SvTreeListEntry* pParent ) +{ + if ( !pParent ) + pParent = pRootItem.get(); + return pParent->m_Children; +} + +const SvTreeListEntry* SvTreeList::GetParent( const SvTreeListEntry* pEntry ) const +{ + if (!pEntry) + return nullptr; + const SvTreeListEntry* pParent = pEntry->pParent; + if (pParent == pRootItem.get()) + pParent = nullptr; + return pParent; +} + +SvTreeListEntry* SvTreeList::GetParent( SvTreeListEntry* pEntry ) +{ + if (!pEntry) + return nullptr; + SvTreeListEntry* pParent = pEntry->pParent; + if (pParent == pRootItem.get()) + pParent = nullptr; + return pParent; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/treelistbox.cxx b/vcl/source/treelist/treelistbox.cxx new file mode 100644 index 000000000..7260cb69f --- /dev/null +++ b/vcl/source/treelist/treelistbox.cxx @@ -0,0 +1,3612 @@ +/* -*- 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 . + */ + +/* + TODO: + - delete anchor in SelectionEngine when selecting manually + - SelectAll( false ) => only repaint the deselected entries +*/ + +#include <vcl/toolkit/treelistbox.hxx> +#include <vcl/accessiblefactory.hxx> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <vcl/svapp.hxx> +#include <vcl/builder.hxx> +#include <vcl/toolkit/edit.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/decoview.hxx> +#include <vcl/uitest/uiobject.hxx> +#include <sot/formats.hxx> +#include <unotools/accessiblestatesethelper.hxx> +#include <comphelper/string.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> + +#include <vcl/toolkit/svlbitm.hxx> +#include <vcl/toolkit/treelistentry.hxx> +#include <vcl/toolkit/viewdataentry.hxx> +#include <accel.hxx> +#include <svimpbox.hxx> + +#include <set> +#include <string.h> +#include <vector> + +using namespace css::accessibility; + +// Drag&Drop +static VclPtr<SvTreeListBox> g_pDDSource; +static VclPtr<SvTreeListBox> g_pDDTarget; + +#define SVLBOX_ACC_RETURN 1 +#define SVLBOX_ACC_ESCAPE 2 + +class SvInplaceEdit2 +{ + Link<SvInplaceEdit2&,void> aCallBackHdl; + Accelerator aAccReturn; + Accelerator aAccEscape; + Idle aIdle { "svtools::SvInplaceEdit2 aIdle" }; + VclPtr<Edit> pEdit; + bool bCanceled; + bool bAlreadyInCallBack; + + void CallCallBackHdl_Impl(); + DECL_LINK( Timeout_Impl, Timer *, void ); + DECL_LINK( ReturnHdl_Impl, Accelerator&, void ); + DECL_LINK( EscapeHdl_Impl, Accelerator&, void ); + +public: + SvInplaceEdit2( vcl::Window* pParent, const Point& rPos, const Size& rSize, + const OUString& rData, const Link<SvInplaceEdit2&,void>& rNotifyEditEnd, + const Selection& ); + ~SvInplaceEdit2(); + bool KeyInput( const KeyEvent& rKEvt ); + void LoseFocus(); + bool EditingCanceled() const { return bCanceled; } + OUString GetText() const; + OUString const & GetSavedValue() const; + void StopEditing( bool bCancel ); + void Hide(); +}; + +// *************************************************************** + +namespace { + +class MyEdit_Impl : public Edit +{ + SvInplaceEdit2* pOwner; +public: + MyEdit_Impl( vcl::Window* pParent, SvInplaceEdit2* pOwner ); + virtual ~MyEdit_Impl() override { disposeOnce(); } + virtual void dispose() override { pOwner = nullptr; Edit::dispose(); } + virtual void KeyInput( const KeyEvent& rKEvt ) override; + virtual void LoseFocus() override; +}; + +} + +MyEdit_Impl::MyEdit_Impl( vcl::Window* pParent, SvInplaceEdit2* _pOwner ) : + + Edit( pParent, WB_LEFT ), + + pOwner( _pOwner ) + +{ +} + +void MyEdit_Impl::KeyInput( const KeyEvent& rKEvt ) +{ + if( !pOwner->KeyInput( rKEvt )) + Edit::KeyInput( rKEvt ); +} + +void MyEdit_Impl::LoseFocus() +{ + if (pOwner) + pOwner->LoseFocus(); +} + +SvInplaceEdit2::SvInplaceEdit2 +( + vcl::Window* pParent, const Point& rPos, + const Size& rSize, + const OUString& rData, + const Link<SvInplaceEdit2&,void>& rNotifyEditEnd, + const Selection& rSelection +) : + + aCallBackHdl ( rNotifyEditEnd ), + bCanceled ( false ), + bAlreadyInCallBack ( false ) + +{ + + pEdit = VclPtr<MyEdit_Impl>::Create( pParent, this ); + + vcl::Font aFont( pParent->GetFont() ); + aFont.SetTransparent( false ); + Color aColor( pParent->GetBackground().GetColor() ); + aFont.SetFillColor(aColor ); + pEdit->SetFont( aFont ); + pEdit->SetBackground( pParent->GetBackground() ); + pEdit->SetPosPixel( rPos ); + pEdit->SetSizePixel( rSize ); + pEdit->SetText( rData ); + pEdit->SetSelection( rSelection ); + pEdit->SaveValue(); + + aAccReturn.InsertItem( SVLBOX_ACC_RETURN, vcl::KeyCode(KEY_RETURN) ); + aAccEscape.InsertItem( SVLBOX_ACC_ESCAPE, vcl::KeyCode(KEY_ESCAPE) ); + + aAccReturn.SetActivateHdl( LINK( this, SvInplaceEdit2, ReturnHdl_Impl) ); + aAccEscape.SetActivateHdl( LINK( this, SvInplaceEdit2, EscapeHdl_Impl) ); + Application::InsertAccel( &aAccReturn ); + Application::InsertAccel( &aAccEscape ); + + pEdit->Show(); + pEdit->GrabFocus(); +} + +SvInplaceEdit2::~SvInplaceEdit2() +{ + if( !bAlreadyInCallBack ) + { + Application::RemoveAccel( &aAccReturn ); + Application::RemoveAccel( &aAccEscape ); + } + pEdit.disposeAndClear(); +} + +OUString const & SvInplaceEdit2::GetSavedValue() const +{ + return pEdit->GetSavedValue(); +} + +void SvInplaceEdit2::Hide() +{ + pEdit->Hide(); +} + + +IMPL_LINK_NOARG(SvInplaceEdit2, ReturnHdl_Impl, Accelerator&, void) +{ + bCanceled = false; + CallCallBackHdl_Impl(); +} + +IMPL_LINK_NOARG(SvInplaceEdit2, EscapeHdl_Impl, Accelerator&, void) +{ + bCanceled = true; + CallCallBackHdl_Impl(); +} + +bool SvInplaceEdit2::KeyInput( const KeyEvent& rKEvt ) +{ + vcl::KeyCode aCode = rKEvt.GetKeyCode(); + sal_uInt16 nCode = aCode.GetCode(); + + switch ( nCode ) + { + case KEY_ESCAPE: + bCanceled = true; + CallCallBackHdl_Impl(); + return true; + + case KEY_RETURN: + bCanceled = false; + CallCallBackHdl_Impl(); + return true; + } + return false; +} + +void SvInplaceEdit2::StopEditing( bool bCancel ) +{ + if ( !bAlreadyInCallBack ) + { + bCanceled = bCancel; + CallCallBackHdl_Impl(); + } +} + +void SvInplaceEdit2::LoseFocus() +{ + if ( !bAlreadyInCallBack + && ((!Application::GetFocusWindow()) || !pEdit->IsChild( Application::GetFocusWindow()) ) + ) + { + bCanceled = false; + aIdle.SetPriority(TaskPriority::REPAINT); + aIdle.SetInvokeHandler(LINK(this,SvInplaceEdit2,Timeout_Impl)); + aIdle.Start(); + } +} + +IMPL_LINK_NOARG(SvInplaceEdit2, Timeout_Impl, Timer *, void) +{ + CallCallBackHdl_Impl(); +} + +void SvInplaceEdit2::CallCallBackHdl_Impl() +{ + aIdle.Stop(); + if ( !bAlreadyInCallBack ) + { + bAlreadyInCallBack = true; + Application::RemoveAccel( &aAccReturn ); + Application::RemoveAccel( &aAccEscape ); + pEdit->Hide(); + aCallBackHdl.Call( *this ); + } +} + +OUString SvInplaceEdit2::GetText() const +{ + return pEdit->GetText(); +} + +// *************************************************************** +// class SvLBoxTab +// *************************************************************** + + +SvLBoxTab::SvLBoxTab() +{ + nPos = 0; + nFlags = SvLBoxTabFlags::NONE; +} + +SvLBoxTab::SvLBoxTab( tools::Long nPosition, SvLBoxTabFlags nTabFlags ) +{ + nPos = nPosition; + nFlags = nTabFlags; +} + +SvLBoxTab::SvLBoxTab( const SvLBoxTab& rTab ) +{ + nPos = rTab.nPos; + nFlags = rTab.nFlags; +} + +tools::Long SvLBoxTab::CalcOffset( tools::Long nItemWidth, tools::Long nTabWidth ) +{ + tools::Long nOffset = 0; + if ( nFlags & SvLBoxTabFlags::ADJUST_RIGHT ) + { + nOffset = nTabWidth - nItemWidth; + if( nOffset < 0 ) + nOffset = 0; + } + else if ( nFlags & SvLBoxTabFlags::ADJUST_CENTER ) + { + if( nFlags & SvLBoxTabFlags::FORCE ) + { + // correct implementation of centering + nOffset = ( nTabWidth - nItemWidth ) / 2; + if( nOffset < 0 ) + nOffset = 0; + } + else + { + // historically grown, wrong calculation of tabs which is needed by + // Abo-Tabbox, Tools/Options/Customize etc. + nItemWidth++; + nOffset = -( nItemWidth / 2 ); + } + } + return nOffset; +} + +// *************************************************************** +// class SvLBoxItem +// *************************************************************** + + +SvLBoxItem::SvLBoxItem() + : mbDisabled(false) +{ +} + +SvLBoxItem::~SvLBoxItem() +{ +} + +int SvLBoxItem::GetWidth(const SvTreeListBox* pView, const SvTreeListEntry* pEntry) const +{ + const SvViewDataItem* pViewData = pView->GetViewDataItem( pEntry, this ); + int nWidth = pViewData->mnWidth; + if (nWidth == -1) + { + nWidth = CalcWidth(pView); + const_cast<SvViewDataItem*>(pViewData)->mnWidth = nWidth; + } + return nWidth; +} + +int SvLBoxItem::GetHeight(const SvTreeListBox* pView, const SvTreeListEntry* pEntry) const +{ + const SvViewDataItem* pViewData = pView->GetViewDataItem( pEntry, this ); + return pViewData->mnHeight; +} + +int SvLBoxItem::GetWidth(const SvTreeListBox* pView, const SvViewDataEntry* pData, sal_uInt16 nItemPos) const +{ + const SvViewDataItem& rIData = pData->GetItem(nItemPos); + int nWidth = rIData.mnWidth; + if (nWidth == -1) + { + nWidth = CalcWidth(pView); + const_cast<SvViewDataItem&>(rIData).mnWidth = nWidth; + } + return nWidth; +} + +int SvLBoxItem::GetHeight(const SvViewDataEntry* pData, sal_uInt16 nItemPos) +{ + const SvViewDataItem& rIData = pData->GetItem(nItemPos); + return rIData.mnHeight; +} + +int SvLBoxItem::CalcWidth(const SvTreeListBox* /*pView*/) const +{ + return 0; +} + +struct SvTreeListBoxImpl +{ + bool m_bDoingQuickSelection:1; + + vcl::QuickSelectionEngine m_aQuickSelectionEngine; + + explicit SvTreeListBoxImpl(SvTreeListBox& _rBox) : + m_bDoingQuickSelection(false), + m_aQuickSelectionEngine(_rBox) {} +}; + +SvTreeListBox::SvTreeListBox(vcl::Window* pParent, WinBits nWinStyle) : + Control(pParent, nWinStyle | WB_CLIPCHILDREN), + DropTargetHelper(this), + DragSourceHelper(this), + mpImpl(new SvTreeListBoxImpl(*this)), + mbContextBmpExpanded(false), + mbAlternatingRowColors(false), + mbUpdateAlternatingRows(false), + mbQuickSearch(false), + mbActivateOnSingleClick(false), + mbHoverSelection(false), + mbSelectingByHover(false), + mnClicksToToggle(0), //at default clicking on a row won't toggle its default checkbox + eSelMode(SelectionMode::NONE), + nMinWidthInChars(0), + mnDragAction(DND_ACTION_COPYMOVE | DND_ACTION_LINK), + mbCenterAndClipText(false) +{ + nImpFlags = SvTreeListBoxFlags::NONE; + pTargetEntry = nullptr; + nDragDropMode = DragDropMode::NONE; + pModel->SetCloneLink( LINK(this, SvTreeListBox, CloneHdl_Impl )); + pHdlEntry = nullptr; + eSelMode = SelectionMode::Single; + nDragDropMode = DragDropMode::NONE; + SetType(WindowType::TREELISTBOX); + + InitTreeView(); + pImpl->SetModel( pModel.get() ); + + SetSublistOpenWithLeftRight(); +} + +void SvTreeListBox::Clear() +{ + if (pModel) + pModel->Clear(); // Model calls SvTreeListBox::ModelHasCleared() +} + +IMPL_LINK( SvTreeListBox, CloneHdl_Impl, SvTreeListEntry*, pEntry, SvTreeListEntry* ) +{ + return CloneEntry(pEntry); +} + +sal_uInt32 SvTreeListBox::Insert( SvTreeListEntry* pEntry, SvTreeListEntry* pParent, sal_uInt32 nPos ) +{ + sal_uInt32 nInsPos = pModel->Insert( pEntry, pParent, nPos ); + pEntry->SetBackColor( GetBackground().GetColor() ); + SetAlternatingRowColors( mbAlternatingRowColors ); + return nInsPos; +} + +sal_uInt32 SvTreeListBox::Insert( SvTreeListEntry* pEntry,sal_uInt32 nRootPos ) +{ + sal_uInt32 nInsPos = pModel->Insert( pEntry, nRootPos ); + pEntry->SetBackColor( GetBackground().GetColor() ); + SetAlternatingRowColors( mbAlternatingRowColors ); + return nInsPos; +} + +bool SvTreeListBox::ExpandingHdl() +{ + return !aExpandingHdl.IsSet() || aExpandingHdl.Call( this ); +} + +void SvTreeListBox::ExpandedHdl() +{ + aExpandedHdl.Call( this ); +} + +void SvTreeListBox::SelectHdl() +{ + aSelectHdl.Call( this ); +} + +void SvTreeListBox::DeselectHdl() +{ + aDeselectHdl.Call( this ); +} + +bool SvTreeListBox::DoubleClickHdl() +{ + return !aDoubleClickHdl.IsSet() || aDoubleClickHdl.Call(this); +} + +bool SvTreeListBox::CheckDragAndDropMode( SvTreeListBox const * pSource, sal_Int8 nAction ) +{ + if ( pSource != this ) + return false; // no drop + + if ( !(nDragDropMode & DragDropMode::CTRL_MOVE) ) + return false; // D&D locked within list + + if( DND_ACTION_MOVE == nAction ) + { + if ( !(nDragDropMode & DragDropMode::CTRL_MOVE) ) + return false; // no local move + } + else + return false; // no local copy + + return true; +} + + +/* + NotifyMoving/Copying + ==================== + + default behavior: + + 1. target doesn't have children + - entry becomes sibling of target. entry comes after target + (->Window: below the target) + 2. target is an expanded parent + - entry inserted at the beginning of the target childlist + 3. target is a collapsed parent + - entry is inserted at the end of the target childlist +*/ +TriState SvTreeListBox::NotifyMoving( + SvTreeListEntry* pTarget, // D&D dropping position in GetModel() + const SvTreeListEntry* pEntry, // entry that we want to move, from + // GetSourceListBox()->GetModel() + SvTreeListEntry*& rpNewParent, // new target parent + sal_uInt32& rNewChildPos) // position in childlist of target parent +{ + DBG_ASSERT(pEntry,"NotifyMoving:SourceEntry?"); + if( !pTarget ) + { + rpNewParent = nullptr; + rNewChildPos = 0; + return TRISTATE_TRUE; + } + if ( !pTarget->HasChildren() && !pTarget->HasChildrenOnDemand() ) + { + // case 1 + rpNewParent = GetParent( pTarget ); + rNewChildPos = SvTreeList::GetRelPos( pTarget ) + 1; + rNewChildPos += nCurEntrySelPos; + nCurEntrySelPos++; + } + else + { + // cases 2 & 3 + rpNewParent = pTarget; + if( IsExpanded(pTarget)) + rNewChildPos = 0; + else + rNewChildPos = TREELIST_APPEND; + } + return TRISTATE_TRUE; +} + +TriState SvTreeListBox::NotifyCopying( + SvTreeListEntry* pTarget, // D&D dropping position in GetModel() + const SvTreeListEntry* pEntry, // entry that we want to move, from + // GetSourceListBox()->GetModel() + SvTreeListEntry*& rpNewParent, // new target parent + sal_uInt32& rNewChildPos) // position in childlist of target parent +{ + return NotifyMoving(pTarget,pEntry,rpNewParent,rNewChildPos); +} + +SvTreeListEntry* SvTreeListBox::FirstChild( SvTreeListEntry* pParent ) const +{ + return pModel->FirstChild(pParent); +} + +// return: all entries copied +bool SvTreeListBox::CopySelection( SvTreeListBox* pSource, SvTreeListEntry* pTarget ) +{ + nCurEntrySelPos = 0; // selection counter for NotifyMoving/Copying + bool bSuccess = true; + std::vector<SvTreeListEntry*> aList; + bool bClone = ( pSource->GetModel() != GetModel() ); + Link<SvTreeListEntry*,SvTreeListEntry*> aCloneLink( pModel->GetCloneLink() ); + pModel->SetCloneLink( LINK(this, SvTreeListBox, CloneHdl_Impl )); + + // cache selection to simplify iterating over the selection when doing a D&D + // exchange within the same listbox + SvTreeListEntry* pSourceEntry = pSource->FirstSelected(); + while ( pSourceEntry ) + { + // children are copied automatically + pSource->SelectChildren( pSourceEntry, false ); + aList.push_back( pSourceEntry ); + pSourceEntry = pSource->NextSelected( pSourceEntry ); + } + + for (auto const& elem : aList) + { + pSourceEntry = elem; + SvTreeListEntry* pNewParent = nullptr; + sal_uInt32 nInsertionPos = TREELIST_APPEND; + TriState nOk = NotifyCopying(pTarget,pSourceEntry,pNewParent,nInsertionPos); + if ( nOk ) + { + if ( bClone ) + { + sal_uInt32 nCloneCount = 0; + pSourceEntry = pModel->Clone(pSourceEntry, nCloneCount); + pModel->InsertTree(pSourceEntry, pNewParent, nInsertionPos); + } + else + { + sal_uInt32 nListPos = pModel->Copy(pSourceEntry, pNewParent, nInsertionPos); + pSourceEntry = GetEntry( pNewParent, nListPos ); + } + } + else + bSuccess = false; + + if (nOk == TRISTATE_INDET) // HACK: make visible moved entry + MakeVisible( pSourceEntry ); + } + pModel->SetCloneLink( aCloneLink ); + return bSuccess; +} + +// return: all entries were moved +bool SvTreeListBox::MoveSelectionCopyFallbackPossible( SvTreeListBox* pSource, SvTreeListEntry* pTarget, bool bAllowCopyFallback ) +{ + nCurEntrySelPos = 0; // selection counter for NotifyMoving/Copying + bool bSuccess = true; + std::vector<SvTreeListEntry*> aList; + bool bClone = ( pSource->GetModel() != GetModel() ); + Link<SvTreeListEntry*,SvTreeListEntry*> aCloneLink( pModel->GetCloneLink() ); + if ( bClone ) + pModel->SetCloneLink( LINK(this, SvTreeListBox, CloneHdl_Impl )); + + SvTreeListEntry* pSourceEntry = pSource->FirstSelected(); + while ( pSourceEntry ) + { + // children are automatically moved + pSource->SelectChildren( pSourceEntry, false ); + aList.push_back( pSourceEntry ); + pSourceEntry = pSource->NextSelected( pSourceEntry ); + } + + for (auto const& elem : aList) + { + pSourceEntry = elem; + SvTreeListEntry* pNewParent = nullptr; + sal_uInt32 nInsertionPos = TREELIST_APPEND; + TriState nOk = NotifyMoving(pTarget,pSourceEntry,pNewParent,nInsertionPos); + TriState nCopyOk = nOk; + if ( !nOk && bAllowCopyFallback ) + { + nInsertionPos = TREELIST_APPEND; + nCopyOk = NotifyCopying(pTarget,pSourceEntry,pNewParent,nInsertionPos); + } + + if ( nOk || nCopyOk ) + { + if ( bClone ) + { + sal_uInt32 nCloneCount = 0; + pSourceEntry = pModel->Clone(pSourceEntry, nCloneCount); + pModel->InsertTree(pSourceEntry, pNewParent, nInsertionPos); + } + else + { + if ( nOk ) + pModel->Move(pSourceEntry, pNewParent, nInsertionPos); + else + pModel->Copy(pSourceEntry, pNewParent, nInsertionPos); + } + } + else + bSuccess = false; + + if (nOk == TRISTATE_INDET) // HACK: make moved entry visible + MakeVisible( pSourceEntry ); + } + pModel->SetCloneLink( aCloneLink ); + return bSuccess; +} + +void SvTreeListBox::RemoveSelection() +{ + std::vector<const SvTreeListEntry*> aList; + // cache selection, as the implementation deselects everything on the first + // remove + SvTreeListEntry* pEntry = FirstSelected(); + while ( pEntry ) + { + aList.push_back( pEntry ); + if ( pEntry->HasChildren() ) + // remove deletes all children automatically + SelectChildren(pEntry, false); + pEntry = NextSelected( pEntry ); + } + + for (auto const& elem : aList) + pModel->Remove(elem); +} + +void SvTreeListBox::RemoveEntry(SvTreeListEntry const * pEntry) +{ + pModel->Remove(pEntry); +} + +void SvTreeListBox::RecalcViewData() +{ + SvTreeListEntry* pEntry = First(); + while( pEntry ) + { + sal_uInt16 nCount = pEntry->ItemCount(); + sal_uInt16 nCurPos = 0; + while ( nCurPos < nCount ) + { + SvLBoxItem& rItem = pEntry->GetItem( nCurPos ); + rItem.InitViewData( this, pEntry ); + nCurPos++; + } + pEntry = Next( pEntry ); + } +} + +void SvTreeListBox::ImplShowTargetEmphasis( SvTreeListEntry* pEntry, bool bShow) +{ + if ( bShow && (nImpFlags & SvTreeListBoxFlags::TARGEMPH_VIS) ) + return; + if ( !bShow && !(nImpFlags & SvTreeListBoxFlags::TARGEMPH_VIS) ) + return; + pImpl->PaintDDCursor( pEntry, bShow); + if( bShow ) + nImpFlags |= SvTreeListBoxFlags::TARGEMPH_VIS; + else + nImpFlags &= ~SvTreeListBoxFlags::TARGEMPH_VIS; +} + +void SvTreeListBox::OnCurrentEntryChanged() +{ + if ( !mpImpl->m_bDoingQuickSelection ) + mpImpl->m_aQuickSelectionEngine.Reset(); +} + +SvTreeListEntry* SvTreeListBox::GetEntry( SvTreeListEntry* pParent, sal_uInt32 nPos ) const +{ + return pModel->GetEntry(pParent, nPos); +} + +SvTreeListEntry* SvTreeListBox::GetEntry( sal_uInt32 nRootPos ) const +{ + return pModel->GetEntry(nRootPos); +} + +SvTreeListEntry* SvTreeListBox::GetEntryFromPath( const ::std::deque< sal_Int32 >& _rPath ) const +{ + + SvTreeListEntry* pEntry = nullptr; + SvTreeListEntry* pParent = nullptr; + for (auto const& elem : _rPath) + { + pEntry = GetEntry( pParent, elem ); + if ( !pEntry ) + break; + pParent = pEntry; + } + + return pEntry; +} + +void SvTreeListBox::FillEntryPath( SvTreeListEntry* pEntry, ::std::deque< sal_Int32 >& _rPath ) const +{ + + if ( !pEntry ) + return; + + SvTreeListEntry* pParentEntry = GetParent( pEntry ); + while ( true ) + { + sal_uInt32 i, nCount = GetLevelChildCount( pParentEntry ); + for ( i = 0; i < nCount; ++i ) + { + SvTreeListEntry* pTemp = GetEntry( pParentEntry, i ); + DBG_ASSERT( pEntry, "invalid entry" ); + if ( pEntry == pTemp ) + { + _rPath.push_front( static_cast<sal_Int32>(i) ); + break; + } + } + + if ( pParentEntry ) + { + pEntry = pParentEntry; + pParentEntry = GetParent( pParentEntry ); + } + else + break; + } +} + +SvTreeListEntry* SvTreeListBox::GetParent( SvTreeListEntry* pEntry ) const +{ + return pModel->GetParent(pEntry); +} + +sal_uInt32 SvTreeListBox::GetChildCount( SvTreeListEntry const * pParent ) const +{ + return pModel->GetChildCount(pParent); +} + +sal_uInt32 SvTreeListBox::GetLevelChildCount( SvTreeListEntry* _pParent ) const +{ + + //if _pParent is 0, then pEntry is the first child of the root. + SvTreeListEntry* pEntry = FirstChild( _pParent ); + + if( !pEntry )//there is only root, root don't have children + return 0; + + if( !_pParent )//root and children of root + return pEntry->pParent->m_Children.size(); + + return _pParent->m_Children.size(); +} + +SvViewDataEntry* SvTreeListBox::GetViewDataEntry( SvTreeListEntry const * pEntry ) const +{ + return const_cast<SvViewDataEntry*>(SvListView::GetViewData(pEntry)); +} + +SvViewDataItem* SvTreeListBox::GetViewDataItem(SvTreeListEntry const * pEntry, SvLBoxItem const * pItem) +{ + return const_cast<SvViewDataItem*>(static_cast<const SvTreeListBox*>(this)->GetViewDataItem(pEntry, pItem)); +} + +const SvViewDataItem* SvTreeListBox::GetViewDataItem(const SvTreeListEntry* pEntry, const SvLBoxItem* pItem) const +{ + const SvViewDataEntry* pEntryData = SvListView::GetViewData(pEntry); + assert(pEntryData && "Entry not in View"); + sal_uInt16 nItemPos = pEntry->GetPos(pItem); + return &pEntryData->GetItem(nItemPos); +} + +void SvTreeListBox::InitViewData( SvViewDataEntry* pData, SvTreeListEntry* pEntry ) +{ + SvTreeListEntry* pInhEntry = pEntry; + SvViewDataEntry* pEntryData = pData; + + pEntryData->Init(pInhEntry->ItemCount()); + sal_uInt16 nCount = pInhEntry->ItemCount(); + sal_uInt16 nCurPos = 0; + while( nCurPos < nCount ) + { + SvLBoxItem& rItem = pInhEntry->GetItem( nCurPos ); + SvViewDataItem& rItemData = pEntryData->GetItem(nCurPos); + rItem.InitViewData( this, pInhEntry, &rItemData ); + nCurPos++; + } +} + +void SvTreeListBox::EnableSelectionAsDropTarget( bool bEnable ) +{ + sal_uInt16 nRefDepth; + SvTreeListEntry* pTemp; + + SvTreeListEntry* pSelEntry = FirstSelected(); + while( pSelEntry ) + { + if ( !bEnable ) + { + pSelEntry->nEntryFlags |= SvTLEntryFlags::DISABLE_DROP; + nRefDepth = pModel->GetDepth( pSelEntry ); + pTemp = Next( pSelEntry ); + while( pTemp && pModel->GetDepth( pTemp ) > nRefDepth ) + { + pTemp->nEntryFlags |= SvTLEntryFlags::DISABLE_DROP; + pTemp = Next( pTemp ); + } + } + else + { + pSelEntry->nEntryFlags &= ~SvTLEntryFlags::DISABLE_DROP; + nRefDepth = pModel->GetDepth( pSelEntry ); + pTemp = Next( pSelEntry ); + while( pTemp && pModel->GetDepth( pTemp ) > nRefDepth ) + { + pTemp->nEntryFlags &= ~SvTLEntryFlags::DISABLE_DROP; + pTemp = Next( pTemp ); + } + } + pSelEntry = NextSelected( pSelEntry ); + } +} + +// ****************************************************************** +// InplaceEditing +// ****************************************************************** + +void SvTreeListBox::EditText( const OUString& rStr, const tools::Rectangle& rRect, + const Selection& rSel ) +{ + pEdCtrl.reset(); + nImpFlags |= SvTreeListBoxFlags::IN_EDT; + nImpFlags &= ~SvTreeListBoxFlags::EDTEND_CALLED; + HideFocus(); + pEdCtrl.reset( new SvInplaceEdit2( + this, rRect.TopLeft(), rRect.GetSize(), rStr, + LINK( this, SvTreeListBox, TextEditEndedHdl_Impl ), + rSel ) ); +} + +IMPL_LINK_NOARG(SvTreeListBox, TextEditEndedHdl_Impl, SvInplaceEdit2&, void) +{ + if ( nImpFlags & SvTreeListBoxFlags::EDTEND_CALLED ) // avoid nesting + return; + nImpFlags |= SvTreeListBoxFlags::EDTEND_CALLED; + OUString aStr; + if ( !pEdCtrl->EditingCanceled() ) + aStr = pEdCtrl->GetText(); + else + aStr = pEdCtrl->GetSavedValue(); + EditedText( aStr ); + // Hide may only be called after the new text was put into the entry, so + // that we don't call the selection handler in the GetFocus of the listbox + // with the old entry text. + pEdCtrl->Hide(); + nImpFlags &= ~SvTreeListBoxFlags::IN_EDT; + GrabFocus(); +} + +void SvTreeListBox::CancelTextEditing() +{ + if ( pEdCtrl ) + pEdCtrl->StopEditing( true ); + nImpFlags &= ~SvTreeListBoxFlags::IN_EDT; +} + +void SvTreeListBox::EndEditing( bool bCancel ) +{ + if( pEdCtrl ) + pEdCtrl->StopEditing( bCancel ); + nImpFlags &= ~SvTreeListBoxFlags::IN_EDT; +} + +vcl::StringEntryIdentifier SvTreeListBox::CurrentEntry( OUString& _out_entryText ) const +{ + // always accept the current entry if there is one + SvTreeListEntry* pEntry( GetCurEntry() ); + if (pEntry) + { + _out_entryText = GetEntryText(pEntry); + return pEntry; + } + + pEntry = FirstSelected(); + if ( !pEntry ) + pEntry = First(); + + if ( pEntry ) + _out_entryText = GetEntryText( pEntry ); + + return pEntry; +} + +vcl::StringEntryIdentifier SvTreeListBox::NextEntry(vcl::StringEntryIdentifier _pCurrentSearchEntry, OUString& _out_entryText) const +{ + SvTreeListEntry* pEntry = const_cast< SvTreeListEntry* >( static_cast< const SvTreeListEntry* >( _pCurrentSearchEntry ) ); + + if ( ( ( GetChildCount( pEntry ) > 0 ) + || ( pEntry->HasChildrenOnDemand() ) + ) + && !IsExpanded( pEntry ) + ) + { + SvTreeListEntry* pNextSiblingEntry = pEntry->NextSibling(); + if ( !pNextSiblingEntry ) + pEntry = Next( pEntry ); + else + pEntry = pNextSiblingEntry; + } + else + { + pEntry = Next( pEntry ); + } + + if ( !pEntry ) + pEntry = First(); + + if ( pEntry ) + _out_entryText = GetEntryText( pEntry ); + + return pEntry; +} + +void SvTreeListBox::SelectEntry(vcl::StringEntryIdentifier _pEntry) +{ + SvTreeListEntry* pEntry = const_cast< SvTreeListEntry* >( static_cast< const SvTreeListEntry* >( _pEntry ) ); + DBG_ASSERT( pEntry, "SvTreeListBox::SelectSearchEntry: invalid entry!" ); + if ( !pEntry ) + return; + + SelectAll( false ); + SetCurEntry( pEntry ); + Select( pEntry ); +} + +bool SvTreeListBox::HandleKeyInput( const KeyEvent& _rKEvt ) +{ + if ( _rKEvt.GetKeyCode().IsMod1() ) + return false; + + if (mbQuickSearch) + { + mpImpl->m_bDoingQuickSelection = true; + const bool bHandled = mpImpl->m_aQuickSelectionEngine.HandleKeyEvent( _rKEvt ); + mpImpl->m_bDoingQuickSelection = false; + if ( bHandled ) + return true; + } + + return false; +} + + +//JP 28.3.2001: new Drag & Drop API +sal_Int8 SvTreeListBox::AcceptDrop( const AcceptDropEvent& rEvt ) +{ + sal_Int8 nRet = DND_ACTION_NONE; + + if (rEvt.mbLeaving || !CheckDragAndDropMode(g_pDDSource, rEvt.mnAction)) + { + ImplShowTargetEmphasis( pTargetEntry, false ); + } + else if( nDragDropMode == DragDropMode::NONE ) + { + SAL_WARN( "svtools.contnr", "SvTreeListBox::QueryDrop(): no target" ); + } + else + { + SvTreeListEntry* pEntry = GetDropTarget( rEvt.maPosPixel ); + if( !IsDropFormatSupported( SotClipboardFormatId::TREELISTBOX ) ) + { + SAL_WARN( "svtools.contnr", "SvTreeListBox::QueryDrop(): no format" ); + } + else + { + DBG_ASSERT(g_pDDSource, "SvTreeListBox::QueryDrop(): SourceBox == 0"); + if (!( pEntry && g_pDDSource->GetModel() == GetModel() + && DND_ACTION_MOVE == rEvt.mnAction + && (pEntry->nEntryFlags & SvTLEntryFlags::DISABLE_DROP))) + { + nRet = rEvt.mnAction; + } + } + + // **** draw emphasis **** + if( DND_ACTION_NONE == nRet ) + ImplShowTargetEmphasis( pTargetEntry, false ); + else if( pEntry != pTargetEntry || !(nImpFlags & SvTreeListBoxFlags::TARGEMPH_VIS) ) + { + ImplShowTargetEmphasis( pTargetEntry, false ); + pTargetEntry = pEntry; + ImplShowTargetEmphasis( pTargetEntry, true ); + } + } + return nRet; +} + +sal_Int8 SvTreeListBox::ExecuteDrop( const ExecuteDropEvent& rEvt, SvTreeListBox* pSourceView ) +{ + assert(pSourceView); + pSourceView->EnableSelectionAsDropTarget(); + + ImplShowTargetEmphasis( pTargetEntry, false ); + g_pDDTarget = this; + + TransferableDataHelper aData( rEvt.maDropEvent.Transferable ); + + sal_Int8 nRet; + if( aData.HasFormat( SotClipboardFormatId::TREELISTBOX )) + nRet = rEvt.mnAction; + else + nRet = DND_ACTION_NONE; + + if( DND_ACTION_NONE != nRet ) + { + nRet = DND_ACTION_NONE; + + SvTreeListEntry* pTarget = pTargetEntry; // may be 0! + + if( DND_ACTION_COPY == rEvt.mnAction ) + { + if (CopySelection(g_pDDSource, pTarget)) + nRet = rEvt.mnAction; + } + else if( DND_ACTION_MOVE == rEvt.mnAction ) + { + if (MoveSelectionCopyFallbackPossible( g_pDDSource, pTarget, false )) + nRet = rEvt.mnAction; + } + else if( DND_ACTION_COPYMOVE == rEvt.mnAction ) + { + if (MoveSelectionCopyFallbackPossible(g_pDDSource, pTarget, true)) + nRet = rEvt.mnAction; + } + } + return nRet; +} + +sal_Int8 SvTreeListBox::ExecuteDrop( const ExecuteDropEvent& rEvt ) +{ + return ExecuteDrop( rEvt, g_pDDSource ); +} + +/** + * This sets the global variables used to determine the + * in-process drag source. + */ +void SvTreeListBox::SetupDragOrigin() +{ + g_pDDSource = this; + g_pDDTarget = nullptr; +} + +void SvTreeListBox::StartDrag( sal_Int8, const Point& rPosPixel ) +{ + if(!isDisposed()) + { + // tdf#143114 do not start drag when a Button/Checkbox is in + // drag-before-ButtonUp mode (CaptureMouse() active) + if(pImpl->IsCaptureOnButtonActive()) + return; + } + + nOldDragMode = GetDragDropMode(); + if ( nOldDragMode == DragDropMode::NONE ) + return; + + ReleaseMouse(); + + SvTreeListEntry* pEntry = GetEntry( rPosPixel ); // GetDropTarget( rPos ); + if( !pEntry ) + { + DragFinished( DND_ACTION_NONE ); + return; + } + + rtl::Reference<TransferDataContainer> xContainer = m_xTransferHelper; + + if (!xContainer) + { + xContainer.set(new TransferDataContainer); + // apparently some (unused) content is needed + xContainer->CopyAnyData( SotClipboardFormatId::TREELISTBOX, + "unused", SAL_N_ELEMENTS("unused") ); + } + + nDragDropMode = NotifyStartDrag(); + if( nDragDropMode == DragDropMode::NONE || 0 == GetSelectionCount() ) + { + nDragDropMode = nOldDragMode; + DragFinished( DND_ACTION_NONE ); + return; + } + + SetupDragOrigin(); + + bool bOldUpdateMode = Control::IsUpdateMode(); + Control::SetUpdateMode( true ); + PaintImmediately(); + Control::SetUpdateMode( bOldUpdateMode ); + + // Disallow using the selection and its children as drop targets. + // Important: If the selection of the SourceListBox is changed in the + // DropHandler, the entries have to be allowed as drop targets again: + // (GetSourceListBox()->EnableSelectionAsDropTarget( true, true );) + EnableSelectionAsDropTarget( false ); + + xContainer->StartDrag(this, mnDragAction, GetDragFinishedHdl()); +} + +void SvTreeListBox::SetDragHelper(const rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants) +{ + m_xTransferHelper = rHelper; + mnDragAction = eDNDConstants; +} + +void SvTreeListBox::DragFinished( sal_Int8 +#ifndef UNX +nAction +#endif +) +{ + EnableSelectionAsDropTarget(); + +#ifndef UNX + if ( (nAction == DND_ACTION_MOVE) + && ( (g_pDDTarget && (g_pDDTarget->GetModel() != GetModel())) + || !g_pDDTarget)) + { + RemoveSelection(); + } +#endif + + UnsetDropTarget(); + g_pDDSource = nullptr; + g_pDDTarget = nullptr; + nDragDropMode = nOldDragMode; +} + +void SvTreeListBox::UnsetDropTarget() +{ + if (pTargetEntry) + { + ImplShowTargetEmphasis(pTargetEntry, false); + pTargetEntry = nullptr; + } +} + +DragDropMode SvTreeListBox::NotifyStartDrag() +{ + return DragDropMode(0xffff); +} + +// Handler and methods for Drag - finished handler. +// The with get GetDragFinishedHdl() get link can set on the +// TransferDataContainer. This link is a callback for the DragFinished +// call. AddBox method is called from the GetDragFinishedHdl() and the +// remove is called in link callback and in the destructor. So it can't +// called to a deleted object. + +namespace +{ + // void* to avoid loplugin:vclwidgets, we don't need ownership here + std::set<const void*> gSortLBoxes; +} + +void SvTreeListBox::AddBoxToDDList_Impl( const SvTreeListBox& rB ) +{ + gSortLBoxes.insert( &rB ); +} + +void SvTreeListBox::RemoveBoxFromDDList_Impl( const SvTreeListBox& rB ) +{ + gSortLBoxes.erase( &rB ); +} + +IMPL_LINK( SvTreeListBox, DragFinishHdl_Impl, sal_Int8, nAction, void ) +{ + auto &rSortLBoxes = gSortLBoxes; + auto it = rSortLBoxes.find(this); + if( it != rSortLBoxes.end() ) + { + DragFinished( nAction ); + rSortLBoxes.erase( it ); + } +} + +Link<sal_Int8,void> SvTreeListBox::GetDragFinishedHdl() const +{ + AddBoxToDDList_Impl( *this ); + return LINK( const_cast<SvTreeListBox*>(this), SvTreeListBox, DragFinishHdl_Impl ); +} + +/* + Bugs/TODO + + - calculate rectangle when editing in-place (bug with some fonts) + - SetSpaceBetweenEntries: offset is not taken into account in SetEntryHeight +*/ + +#define SV_LBOX_DEFAULT_INDENT_PIXEL 20 + +void SvTreeListBox::InitTreeView() +{ + pCheckButtonData = nullptr; + pEdEntry = nullptr; + pEdItem = nullptr; + nEntryHeight = 0; + pEdCtrl = nullptr; + nFirstSelTab = 0; + nLastSelTab = 0; + nFocusWidth = -1; + mnCheckboxItemWidth = 0; + + nTreeFlags = SvTreeFlags::RECALCTABS; + nIndent = SV_LBOX_DEFAULT_INDENT_PIXEL; + nEntryHeightOffs = SV_ENTRYHEIGHTOFFS_PIXEL; + pImpl.reset( new SvImpLBox( this, GetModel(), GetStyle() ) ); + + mbContextBmpExpanded = true; + nContextBmpWidthMax = 0; + + SetFont( GetFont() ); + AdjustEntryHeightAndRecalc(); + + SetSpaceBetweenEntries( 0 ); + GetOutDev()->SetLineColor(); + InitSettings(); + ImplInitStyle(); + SetTabs(); +} + +OUString SvTreeListBox::SearchEntryTextWithHeadTitle( SvTreeListEntry* pEntry ) +{ + assert(pEntry); + OUStringBuffer sRet; + + sal_uInt16 nCount = pEntry->ItemCount(); + sal_uInt16 nCur = 0; + while( nCur < nCount ) + { + SvLBoxItem& rItem = pEntry->GetItem( nCur ); + if ( (rItem.GetType() == SvLBoxItemType::String) && + !static_cast<SvLBoxString&>( rItem ).GetText().isEmpty() ) + { + sRet.append(static_cast<SvLBoxString&>( rItem ).GetText() + ","); + } + nCur++; + } + + if (!sRet.isEmpty()) + sRet.remove(sRet.getLength() - 1, 1); + return sRet.makeStringAndClear(); +} + +SvTreeListBox::~SvTreeListBox() +{ + disposeOnce(); +} + +void SvTreeListBox::dispose() +{ + if (IsMouseCaptured()) + ReleaseMouse(); + + if( pImpl ) + { + pImpl->CallEventListeners( VclEventId::ObjectDying ); + pImpl.reset(); + } + if( mpImpl ) + { + ClearTabList(); + + pEdCtrl.reset(); + + SvListView::dispose(); + + SvTreeListBox::RemoveBoxFromDDList_Impl( *this ); + + if (this == g_pDDSource) + g_pDDSource = nullptr; + if (this == g_pDDTarget) + g_pDDTarget = nullptr; + mpImpl.reset(); + } + + DropTargetHelper::dispose(); + DragSourceHelper::dispose(); + Control::dispose(); +} + +void SvTreeListBox::SetNoAutoCurEntry( bool b ) +{ + pImpl->SetNoAutoCurEntry( b ); +} + +void SvTreeListBox::SetSublistOpenWithLeftRight() +{ + pImpl->m_bSubLstOpLR = true; +} + +void SvTreeListBox::Resize() +{ + if( IsEditingActive() ) + EndEditing( true ); + + Control::Resize(); + + pImpl->Resize(); + nFocusWidth = -1; + pImpl->ShowCursor( false ); + pImpl->ShowCursor( true ); +} + +/* Cases: + + A) entries have bitmaps + 0. no buttons + 1. node buttons (can optionally also be on root items) + 2. node buttons (can optionally also be on root items) + CheckButton + 3. CheckButton + B) entries don't have bitmaps (=>via WindowBits because of D&D!) + 0. no buttons + 1. node buttons (can optionally also be on root items) + 2. node buttons (can optionally also be on root items) + CheckButton + 3. CheckButton +*/ + +#define NO_BUTTONS 0 +#define NODE_BUTTONS 1 +#define NODE_AND_CHECK_BUTTONS 2 +#define CHECK_BUTTONS 3 + +#define TABFLAGS_TEXT (SvLBoxTabFlags::DYNAMIC | \ + SvLBoxTabFlags::ADJUST_LEFT | \ + SvLBoxTabFlags::EDITABLE | \ + SvLBoxTabFlags::SHOW_SELECTION) + +#define TABFLAGS_CONTEXTBMP (SvLBoxTabFlags::DYNAMIC | SvLBoxTabFlags::ADJUST_CENTER) + +#define TABFLAGS_CHECKBTN (SvLBoxTabFlags::DYNAMIC | \ + SvLBoxTabFlags::ADJUST_CENTER) + +#define TAB_STARTPOS 2 + +// take care of GetTextOffset when doing changes +void SvTreeListBox::SetTabs() +{ + if( IsEditingActive() ) + EndEditing( true ); + nTreeFlags &= ~SvTreeFlags::RECALCTABS; + nFocusWidth = -1; + const WinBits nStyle( GetStyle() ); + bool bHasButtons = (nStyle & WB_HASBUTTONS)!=0; + bool bHasButtonsAtRoot = (nStyle & (WB_HASLINESATROOT | + WB_HASBUTTONSATROOT))!=0; + tools::Long nStartPos = TAB_STARTPOS; + tools::Long nNodeWidthPixel = GetExpandedNodeBmp().GetSizePixel().Width(); + + // pCheckButtonData->Width() knows nothing about the native checkbox width, + // so we have mnCheckboxItemWidth which becomes valid when something is added. + tools::Long nCheckWidth = 0; + if( nTreeFlags & SvTreeFlags::CHKBTN ) + nCheckWidth = mnCheckboxItemWidth; + tools::Long nCheckWidthDIV2 = nCheckWidth / 2; + + tools::Long nContextWidth = nContextBmpWidthMax; + tools::Long nContextWidthDIV2 = nContextWidth / 2; + + ClearTabList(); + + int nCase = NO_BUTTONS; + if( !(nTreeFlags & SvTreeFlags::CHKBTN) ) + { + if( bHasButtons ) + nCase = NODE_BUTTONS; + } + else + { + if( bHasButtons ) + nCase = NODE_AND_CHECK_BUTTONS; + else + nCase = CHECK_BUTTONS; + } + + switch( nCase ) + { + case NO_BUTTONS : + nStartPos += nContextWidthDIV2; // because of centering + AddTab( nStartPos, TABFLAGS_CONTEXTBMP ); + nStartPos += nContextWidthDIV2; // right edge of context bitmap + // only set a distance if there are bitmaps + if( nContextBmpWidthMax ) + nStartPos += 5; // distance context bitmap to text + AddTab( nStartPos, TABFLAGS_TEXT ); + break; + + case NODE_BUTTONS : + if( bHasButtonsAtRoot ) + nStartPos += ( nIndent + (nNodeWidthPixel/2) ); + else + nStartPos += nContextWidthDIV2; + AddTab( nStartPos, TABFLAGS_CONTEXTBMP ); + nStartPos += nContextWidthDIV2; // right edge of context bitmap + // only set a distance if there are bitmaps + if( nContextBmpWidthMax ) + nStartPos += 5; // distance context bitmap to text + AddTab( nStartPos, TABFLAGS_TEXT ); + break; + + case NODE_AND_CHECK_BUTTONS : + if( bHasButtonsAtRoot ) + nStartPos += ( nIndent + nNodeWidthPixel ); + else + nStartPos += nCheckWidthDIV2; + AddTab( nStartPos, TABFLAGS_CHECKBTN ); + nStartPos += nCheckWidthDIV2; // right edge of CheckButton + nStartPos += 3; // distance CheckButton to context bitmap + nStartPos += nContextWidthDIV2; // center of context bitmap + AddTab( nStartPos, TABFLAGS_CONTEXTBMP ); + nStartPos += nContextWidthDIV2; // right edge of context bitmap + // only set a distance if there are bitmaps + if( nContextBmpWidthMax ) + nStartPos += 5; // distance context bitmap to text + AddTab( nStartPos, TABFLAGS_TEXT ); + break; + + case CHECK_BUTTONS : + nStartPos += nCheckWidthDIV2; + AddTab( nStartPos, TABFLAGS_CHECKBTN ); + nStartPos += nCheckWidthDIV2; // right edge of CheckButton + nStartPos += 3; // distance CheckButton to context bitmap + nStartPos += nContextWidthDIV2; // center of context bitmap + AddTab( nStartPos, TABFLAGS_CONTEXTBMP ); + nStartPos += nContextWidthDIV2; // right edge of context bitmap + // only set a distance if there are bitmaps + if( nContextBmpWidthMax ) + nStartPos += 5; // distance context bitmap to text + AddTab( nStartPos, TABFLAGS_TEXT ); + break; + } + pImpl->NotifyTabsChanged(); +} + +void SvTreeListBox::InitEntry(SvTreeListEntry* pEntry, + const OUString& aStr, const Image& aCollEntryBmp, const Image& aExpEntryBmp) +{ + if( nTreeFlags & SvTreeFlags::CHKBTN ) + { + pEntry->AddItem(std::make_unique<SvLBoxButton>(pCheckButtonData)); + } + + pEntry->AddItem(std::make_unique<SvLBoxContextBmp>( aCollEntryBmp,aExpEntryBmp, mbContextBmpExpanded)); + + pEntry->AddItem(std::make_unique<SvLBoxString>(aStr)); +} + +OUString SvTreeListBox::GetEntryText(SvTreeListEntry* pEntry) const +{ + assert(pEntry); + SvLBoxString* pItem = static_cast<SvLBoxString*>(pEntry->GetFirstItem(SvLBoxItemType::String)); + if (pItem) // There may be entries without text items, e.g. in IconView + return pItem->GetText(); + return {}; +} + +const Image& SvTreeListBox::GetExpandedEntryBmp(const SvTreeListEntry* pEntry) +{ + assert(pEntry); + const SvLBoxContextBmp* pItem = static_cast<const SvLBoxContextBmp*>(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp)); + assert(pItem); + return pItem->GetBitmap2( ); +} + +const Image& SvTreeListBox::GetCollapsedEntryBmp( const SvTreeListEntry* pEntry ) +{ + assert(pEntry); + const SvLBoxContextBmp* pItem = static_cast<const SvLBoxContextBmp*>(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp)); + assert(pItem); + return pItem->GetBitmap1( ); +} + +IMPL_LINK( SvTreeListBox, CheckButtonClick, SvLBoxButtonData *, pData, void ) +{ + pHdlEntry = pData->GetActEntry(); + CheckButtonHdl(); +} + +SvTreeListEntry* SvTreeListBox::InsertEntry( + const OUString& rText, + SvTreeListEntry* pParent, + bool bChildrenOnDemand, sal_uInt32 nPos, + void* pUser +) +{ + nTreeFlags |= SvTreeFlags::MANINS; + + const Image& rDefExpBmp = pImpl->GetDefaultEntryExpBmp( ); + const Image& rDefColBmp = pImpl->GetDefaultEntryColBmp( ); + + aCurInsertedExpBmp = rDefExpBmp; + aCurInsertedColBmp = rDefColBmp; + + SvTreeListEntry* pEntry = new SvTreeListEntry; + pEntry->SetUserData( pUser ); + InitEntry( pEntry, rText, rDefColBmp, rDefExpBmp ); + pEntry->EnableChildrenOnDemand( bChildrenOnDemand ); + + if( !pParent ) + Insert( pEntry, nPos ); + else + Insert( pEntry, pParent, nPos ); + + aPrevInsertedExpBmp = rDefExpBmp; + aPrevInsertedColBmp = rDefColBmp; + + nTreeFlags &= ~SvTreeFlags::MANINS; + + return pEntry; +} + +void SvTreeListBox::SetEntryText(SvTreeListEntry* pEntry, const OUString& rStr) +{ + SvLBoxString* pItem = static_cast<SvLBoxString*>(pEntry->GetFirstItem(SvLBoxItemType::String)); + assert(pItem); + pItem->SetText(rStr); + pItem->InitViewData( this, pEntry ); + GetModel()->InvalidateEntry( pEntry ); +} + +void SvTreeListBox::SetExpandedEntryBmp( SvTreeListEntry* pEntry, const Image& aBmp ) +{ + SvLBoxContextBmp* pItem = static_cast<SvLBoxContextBmp*>(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp)); + + assert(pItem); + pItem->SetBitmap2( aBmp ); + + ModelHasEntryInvalidated(pEntry); + CalcEntryHeight( pEntry ); + Size aSize = aBmp.GetSizePixel(); + short nWidth = pImpl->UpdateContextBmpWidthVector( pEntry, static_cast<short>(aSize.Width()) ); + if( nWidth > nContextBmpWidthMax ) + { + nContextBmpWidthMax = nWidth; + SetTabs(); + } +} + +void SvTreeListBox::SetCollapsedEntryBmp(SvTreeListEntry* pEntry,const Image& aBmp ) +{ + SvLBoxContextBmp* pItem = static_cast<SvLBoxContextBmp*>(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp)); + + assert(pItem); + pItem->SetBitmap1( aBmp ); + + ModelHasEntryInvalidated(pEntry); + CalcEntryHeight( pEntry ); + Size aSize = aBmp.GetSizePixel(); + short nWidth = pImpl->UpdateContextBmpWidthVector( pEntry, static_cast<short>(aSize.Width()) ); + if( nWidth > nContextBmpWidthMax ) + { + nContextBmpWidthMax = nWidth; + SetTabs(); + } +} + +void SvTreeListBox::CheckBoxInserted(SvTreeListEntry* pEntry) +{ + SvLBoxButton* pItem = static_cast<SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button)); + if( pItem ) + { + auto nWidth = pItem->GetWidth(this, pEntry); + if( mnCheckboxItemWidth < nWidth ) + { + mnCheckboxItemWidth = nWidth; + nTreeFlags |= SvTreeFlags::RECALCTABS; + } + } +} + +void SvTreeListBox::ImpEntryInserted( SvTreeListEntry* pEntry ) +{ + + SvTreeListEntry* pParent = pModel->GetParent( pEntry ); + if( pParent ) + { + SvTLEntryFlags nFlags = pParent->GetFlags(); + nFlags &= ~SvTLEntryFlags::NO_NODEBMP; + pParent->SetFlags( nFlags ); + } + + if(!((nTreeFlags & SvTreeFlags::MANINS) && + (aPrevInsertedExpBmp == aCurInsertedExpBmp) && + (aPrevInsertedColBmp == aCurInsertedColBmp) )) + { + Size aSize = GetCollapsedEntryBmp( pEntry ).GetSizePixel(); + if( aSize.Width() > nContextBmpWidthMax ) + { + nContextBmpWidthMax = static_cast<short>(aSize.Width()); + nTreeFlags |= SvTreeFlags::RECALCTABS; + } + aSize = GetExpandedEntryBmp( pEntry ).GetSizePixel(); + if( aSize.Width() > nContextBmpWidthMax ) + { + nContextBmpWidthMax = static_cast<short>(aSize.Width()); + nTreeFlags |= SvTreeFlags::RECALCTABS; + } + } + CalcEntryHeight( pEntry ); + + if( !(nTreeFlags & SvTreeFlags::CHKBTN) ) + return; + + CheckBoxInserted(pEntry); +} + +void SvTreeListBox::SetCheckButtonState( SvTreeListEntry* pEntry, SvButtonState eState) +{ + if( !(nTreeFlags & SvTreeFlags::CHKBTN) ) + return; + + SvLBoxButton* pItem = static_cast<SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button)); + if(!pItem) + return ; + switch( eState ) + { + case SvButtonState::Checked: + pItem->SetStateChecked(); + break; + + case SvButtonState::Unchecked: + pItem->SetStateUnchecked(); + break; + + case SvButtonState::Tristate: + pItem->SetStateTristate(); + break; + } + InvalidateEntry( pEntry ); +} + +SvButtonState SvTreeListBox::GetCheckButtonState( SvTreeListEntry* pEntry ) const +{ + SvButtonState eState = SvButtonState::Unchecked; + if( pEntry && ( nTreeFlags & SvTreeFlags::CHKBTN ) ) + { + SvLBoxButton* pItem = static_cast<SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button)); + if(!pItem) + return SvButtonState::Tristate; + SvItemStateFlags nButtonFlags = pItem->GetButtonFlags(); + eState = SvLBoxButtonData::ConvertToButtonState( nButtonFlags ); + } + return eState; +} + +void SvTreeListBox::CheckButtonHdl() +{ + if ( pCheckButtonData ) + pImpl->CallEventListeners( VclEventId::CheckboxToggle, static_cast<void*>(pCheckButtonData->GetActEntry()) ); +} + + +// TODO: Currently all data is cloned so that they conform to the default tree +// view format. Actually, the model should be used as a reference here. This +// leads to us _not_ calling SvTreeListEntry::Clone, but only its base class +// SvTreeListEntry. + + +SvTreeListEntry* SvTreeListBox::CloneEntry( SvTreeListEntry* pSource ) +{ + OUString aStr; + Image aCollEntryBmp; + Image aExpEntryBmp; + + SvLBoxString* pStringItem = static_cast<SvLBoxString*>(pSource->GetFirstItem(SvLBoxItemType::String)); + if( pStringItem ) + aStr = pStringItem->GetText(); + SvLBoxContextBmp* pBmpItem = static_cast<SvLBoxContextBmp*>(pSource->GetFirstItem(SvLBoxItemType::ContextBmp)); + if( pBmpItem ) + { + aCollEntryBmp = pBmpItem->GetBitmap1( ); + aExpEntryBmp = pBmpItem->GetBitmap2( ); + } + SvTreeListEntry* pClone = new SvTreeListEntry; + InitEntry( pClone, aStr, aCollEntryBmp, aExpEntryBmp ); + pClone->SvTreeListEntry::Clone( pSource ); + pClone->EnableChildrenOnDemand( pSource->HasChildrenOnDemand() ); + pClone->SetUserData( pSource->GetUserData() ); + + return pClone; +} + +const Image& SvTreeListBox::GetDefaultExpandedEntryBmp( ) const +{ + return pImpl->GetDefaultEntryExpBmp( ); +} + +const Image& SvTreeListBox::GetDefaultCollapsedEntryBmp( ) const +{ + return pImpl->GetDefaultEntryColBmp( ); +} + +void SvTreeListBox::SetDefaultExpandedEntryBmp( const Image& aBmp ) +{ + Size aSize = aBmp.GetSizePixel(); + if( aSize.Width() > nContextBmpWidthMax ) + nContextBmpWidthMax = static_cast<short>(aSize.Width()); + SetTabs(); + + pImpl->SetDefaultEntryExpBmp( aBmp ); +} + +void SvTreeListBox::SetDefaultCollapsedEntryBmp( const Image& aBmp ) +{ + Size aSize = aBmp.GetSizePixel(); + if( aSize.Width() > nContextBmpWidthMax ) + nContextBmpWidthMax = static_cast<short>(aSize.Width()); + SetTabs(); + + pImpl->SetDefaultEntryColBmp( aBmp ); +} + +void SvTreeListBox::EnableCheckButton( SvLBoxButtonData* pData ) +{ + if( !pData ) + nTreeFlags &= ~SvTreeFlags::CHKBTN; + else + { + SetCheckButtonData( pData ); + nTreeFlags |= SvTreeFlags::CHKBTN; + pData->SetLink( LINK(this, SvTreeListBox, CheckButtonClick)); + } + + SetTabs(); + if( IsUpdateMode() ) + Invalidate(); +} + +void SvTreeListBox::SetCheckButtonData( SvLBoxButtonData* pData ) +{ + if ( pData ) + pCheckButtonData = pData; +} + +const Image& SvTreeListBox::GetDefaultExpandedNodeImage( ) +{ + return SvImpLBox::GetDefaultExpandedNodeImage( ); +} + +const Image& SvTreeListBox::GetDefaultCollapsedNodeImage( ) +{ + return SvImpLBox::GetDefaultCollapsedNodeImage( ); +} + +void SvTreeListBox::SetNodeDefaultImages() +{ + SetExpandedNodeBmp(GetDefaultExpandedNodeImage()); + SetCollapsedNodeBmp(GetDefaultCollapsedNodeImage()); + SetTabs(); +} + +bool SvTreeListBox::EditingEntry( SvTreeListEntry* ) +{ + return true; +} + +bool SvTreeListBox::EditedEntry( SvTreeListEntry* /*pEntry*/,const OUString& /*rNewText*/) +{ + return true; +} + +void SvTreeListBox::EnableInplaceEditing( bool bOn ) +{ + if (bOn) + nImpFlags |= SvTreeListBoxFlags::EDT_ENABLED; + else + nImpFlags &= ~SvTreeListBoxFlags::EDT_ENABLED; +} + +void SvTreeListBox::KeyInput( const KeyEvent& rKEvt ) +{ + // under OS/2, we get key up/down even while editing + if( IsEditingActive() ) + return; + + if( !pImpl->KeyInput( rKEvt ) ) + { + bool bHandled = HandleKeyInput( rKEvt ); + if ( !bHandled ) + Control::KeyInput( rKEvt ); + } +} + +void SvTreeListBox::RequestingChildren( SvTreeListEntry* pParent ) +{ + if( !pParent->HasChildren() ) + InsertEntry( "<dummy>", pParent ); +} + +void SvTreeListBox::GetFocus() +{ + //If there is no item in the tree, draw focus. + if( !First()) + { + Invalidate(); + } + pImpl->GetFocus(); + Control::GetFocus(); + + SvTreeListEntry* pEntry = FirstSelected(); + if ( !pEntry ) + { + pEntry = pImpl->GetCurEntry(); + } + if (pImpl->m_pCursor) + { + if (pEntry != pImpl->m_pCursor) + pEntry = pImpl->m_pCursor; + } + if ( pEntry ) + pImpl->CallEventListeners( VclEventId::ListboxTreeFocus, pEntry ); + +} + +void SvTreeListBox::LoseFocus() +{ + // If there is no item in the tree, delete visual focus. + if ( !First() ) + Invalidate(); + if ( pImpl ) + pImpl->LoseFocus(); + Control::LoseFocus(); +} + +void SvTreeListBox::ModelHasCleared() +{ + pImpl->m_pCursor = nullptr; // else we crash in GetFocus when editing in-place + pTargetEntry = nullptr; + pEdCtrl.reset(); + pImpl->Clear(); + nFocusWidth = -1; + + nContextBmpWidthMax = 0; + SetDefaultExpandedEntryBmp( GetDefaultExpandedEntryBmp() ); + SetDefaultCollapsedEntryBmp( GetDefaultCollapsedEntryBmp() ); + + if( !(nTreeFlags & SvTreeFlags::FIXEDHEIGHT )) + nEntryHeight = 0; + AdjustEntryHeight(); + AdjustEntryHeight( GetDefaultExpandedEntryBmp() ); + AdjustEntryHeight( GetDefaultCollapsedEntryBmp() ); + + SvListView::ModelHasCleared(); +} + +bool SvTreeListBox::PosOverBody(const Point& rPos) const +{ + if (rPos.X() < 0 || rPos.Y() < 0) + return false; + Size aSize(GetSizePixel()); + if (rPos.X() > aSize.Width() || rPos.Y() > aSize.Height()) + return false; + if (pImpl->m_aVerSBar->IsVisible()) + { + tools::Rectangle aRect(pImpl->m_aVerSBar->GetPosPixel(), pImpl->m_aVerSBar->GetSizePixel()); + if (aRect.Contains(rPos)) + return false; + } + if (pImpl->m_aHorSBar->IsVisible()) + { + tools::Rectangle aRect(pImpl->m_aHorSBar->GetPosPixel(), pImpl->m_aHorSBar->GetSizePixel()); + if (aRect.Contains(rPos)) + return false; + } + return true; +} + +void SvTreeListBox::ScrollOutputArea( short nDeltaEntries ) +{ + if( !nDeltaEntries || !pImpl->m_aVerSBar->IsVisible() ) + return; + + tools::Long nThumb = pImpl->m_aVerSBar->GetThumbPos(); + tools::Long nMax = pImpl->m_aVerSBar->GetRange().Max(); + + if( nDeltaEntries < 0 ) + { + // move window up + nDeltaEntries *= -1; + tools::Long nVis = pImpl->m_aVerSBar->GetVisibleSize(); + tools::Long nTemp = nThumb + nVis; + if( nDeltaEntries > (nMax - nTemp) ) + nDeltaEntries = static_cast<short>(nMax - nTemp); + pImpl->PageDown( static_cast<sal_uInt16>(nDeltaEntries) ); + } + else + { + if( nDeltaEntries > nThumb ) + nDeltaEntries = static_cast<short>(nThumb); + pImpl->PageUp( static_cast<sal_uInt16>(nDeltaEntries) ); + } + pImpl->SyncVerThumb(); +} + +void SvTreeListBox::ScrollToAbsPos( tools::Long nPos ) +{ + pImpl->ScrollToAbsPos( nPos ); +} + +void SvTreeListBox::SetSelectionMode( SelectionMode eSelectMode ) +{ + eSelMode = eSelectMode; + pImpl->SetSelectionMode( eSelectMode ); +} + +void SvTreeListBox::SetDragDropMode( DragDropMode nDDMode ) +{ + nDragDropMode = nDDMode; + pImpl->SetDragDropMode( nDDMode ); +} + +void SvTreeListBox::CalcEntryHeight( SvTreeListEntry const * pEntry ) +{ + short nHeightMax=0; + sal_uInt16 nCount = pEntry->ItemCount(); + sal_uInt16 nCur = 0; + SvViewDataEntry* pViewData = GetViewDataEntry( pEntry ); + while( nCur < nCount ) + { + auto nHeight = SvLBoxItem::GetHeight(pViewData, nCur); + if( nHeight > nHeightMax ) + nHeightMax = nHeight; + nCur++; + } + + if( nHeightMax > nEntryHeight ) + { + nEntryHeight = nHeightMax; + Control::SetFont( GetFont() ); + pImpl->SetEntryHeight(); + } +} + +void SvTreeListBox::SetEntryHeight( short nHeight ) +{ + if( nHeight > nEntryHeight ) + { + nEntryHeight = nHeight; + if( nEntryHeight ) + nTreeFlags |= SvTreeFlags::FIXEDHEIGHT; + else + nTreeFlags &= ~SvTreeFlags::FIXEDHEIGHT; + Control::SetFont( GetFont() ); + pImpl->SetEntryHeight(); + } +} + +void SvTreeListBox::SetEntryWidth( short nWidth ) +{ + nEntryWidth = nWidth; +} + +void SvTreeListBox::AdjustEntryHeight( const Image& rBmp ) +{ + const Size aSize( rBmp.GetSizePixel() ); + if( aSize.Height() > nEntryHeight ) + { + nEntryHeight = static_cast<short>(aSize.Height()) + nEntryHeightOffs; + pImpl->SetEntryHeight(); + } +} + +void SvTreeListBox::AdjustEntryHeight() +{ + tools::Long nHeight = GetTextHeight(); + if( nHeight > nEntryHeight ) + { + nEntryHeight = static_cast<short>(nHeight) + nEntryHeightOffs; + pImpl->SetEntryHeight(); + } +} + +bool SvTreeListBox::Expand( SvTreeListEntry* pParent ) +{ + pHdlEntry = pParent; + bool bExpanded = false; + SvTLEntryFlags nFlags; + + if( pParent->HasChildrenOnDemand() ) + RequestingChildren( pParent ); + bool bExpandAllowed = pParent->HasChildren() && ExpandingHdl(); + // double check if the expander callback ended up removing all children + if (pParent->HasChildren()) + { + if (bExpandAllowed) + { + bExpanded = true; + ExpandListEntry( pParent ); + pImpl->EntryExpanded( pParent ); + pHdlEntry = pParent; + ExpandedHdl(); + SetAlternatingRowColors( mbAlternatingRowColors ); + } + nFlags = pParent->GetFlags(); + nFlags &= ~SvTLEntryFlags::NO_NODEBMP; + nFlags |= SvTLEntryFlags::HAD_CHILDREN; + pParent->SetFlags( nFlags ); + } + else + { + nFlags = pParent->GetFlags(); + nFlags |= SvTLEntryFlags::NO_NODEBMP; + pParent->SetFlags( nFlags ); + GetModel()->InvalidateEntry( pParent ); // repaint + } + + // #i92103# + if ( bExpanded ) + { + pImpl->CallEventListeners( VclEventId::ItemExpanded, pParent ); + } + + return bExpanded; +} + +bool SvTreeListBox::Collapse( SvTreeListEntry* pParent ) +{ + pHdlEntry = pParent; + bool bCollapsed = false; + + if( ExpandingHdl() ) + { + bCollapsed = true; + pImpl->CollapsingEntry( pParent ); + CollapseListEntry( pParent ); + pImpl->EntryCollapsed( pParent ); + pHdlEntry = pParent; + ExpandedHdl(); + SetAlternatingRowColors( mbAlternatingRowColors ); + } + + // #i92103# + if ( bCollapsed ) + { + pImpl->CallEventListeners( VclEventId::ItemCollapsed, pParent ); + } + + return bCollapsed; +} + +bool SvTreeListBox::Select( SvTreeListEntry* pEntry, bool bSelect ) +{ + DBG_ASSERT(pEntry,"Select: Null-Ptr"); + bool bRetVal = SelectListEntry( pEntry, bSelect ); + DBG_ASSERT(IsSelected(pEntry)==bSelect,"Select failed"); + if( bRetVal ) + { + pImpl->EntrySelected( pEntry, bSelect ); + pHdlEntry = pEntry; + if( bSelect ) + { + SelectHdl(); + CallEventListeners( VclEventId::ListboxTreeSelect, pEntry); + } + else + DeselectHdl(); + } + return bRetVal; +} + +sal_uInt32 SvTreeListBox::SelectChildren( SvTreeListEntry* pParent, bool bSelect ) +{ + pImpl->DestroyAnchor(); + sal_uInt32 nRet = 0; + if( !pParent->HasChildren() ) + return 0; + sal_uInt16 nRefDepth = pModel->GetDepth( pParent ); + SvTreeListEntry* pChild = FirstChild( pParent ); + do { + nRet++; + Select( pChild, bSelect ); + pChild = Next( pChild ); + } while( pChild && pModel->GetDepth( pChild ) > nRefDepth ); + return nRet; +} + +void SvTreeListBox::SelectAll( bool bSelect ) +{ + pImpl->SelAllDestrAnch( + bSelect, + true, // delete anchor, + true ); // even when using SelectionMode::Single, deselect the cursor +} + +void SvTreeListBox::ModelHasInsertedTree( SvTreeListEntry* pEntry ) +{ + sal_uInt16 nRefDepth = pModel->GetDepth( pEntry ); + SvTreeListEntry* pTmp = pEntry; + do + { + ImpEntryInserted( pTmp ); + pTmp = Next( pTmp ); + } while( pTmp && nRefDepth < pModel->GetDepth( pTmp ) ); + pImpl->TreeInserted( pEntry ); +} + +void SvTreeListBox::ModelHasInserted( SvTreeListEntry* pEntry ) +{ + ImpEntryInserted( pEntry ); + pImpl->EntryInserted( pEntry ); +} + +void SvTreeListBox::ModelIsMoving(SvTreeListEntry* pSource ) +{ + pImpl->MovingEntry( pSource ); +} + +void SvTreeListBox::ModelHasMoved( SvTreeListEntry* pSource ) +{ + pImpl->EntryMoved( pSource ); +} + +void SvTreeListBox::ModelIsRemoving( SvTreeListEntry* pEntry ) +{ + if(pEdEntry == pEntry) + pEdEntry = nullptr; + + pImpl->RemovingEntry( pEntry ); +} + +void SvTreeListBox::ModelHasRemoved( SvTreeListEntry* pEntry ) +{ + if (pEntry == pHdlEntry) + pHdlEntry = nullptr; + + if (pEntry == pTargetEntry) + pTargetEntry = nullptr; + + pImpl->EntryRemoved(); +} + +void SvTreeListBox::SetCollapsedNodeBmp( const Image& rBmp) +{ + AdjustEntryHeight( rBmp ); + pImpl->SetCollapsedNodeBmp( rBmp ); +} + +void SvTreeListBox::SetExpandedNodeBmp( const Image& rBmp ) +{ + AdjustEntryHeight( rBmp ); + pImpl->SetExpandedNodeBmp( rBmp ); +} + + +void SvTreeListBox::SetFont( const vcl::Font& rFont ) +{ + vcl::Font aTempFont( rFont ); + vcl::Font aOrigFont( GetFont() ); + aTempFont.SetTransparent( true ); + if (aTempFont == aOrigFont) + return; + Control::SetFont( aTempFont ); + + aTempFont.SetColor(aOrigFont.GetColor()); + aTempFont.SetFillColor(aOrigFont.GetFillColor()); + aTempFont.SetTransparent(aOrigFont.IsTransparent()); + + if (aTempFont == aOrigFont) + return; + + AdjustEntryHeightAndRecalc(); +} + +void SvTreeListBox::AdjustEntryHeightAndRecalc() +{ + AdjustEntryHeight(); + // always invalidate, else things go wrong in SetEntryHeight + RecalcViewData(); +} + +void SvTreeListBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + Control::Paint(rRenderContext, rRect); + if (nTreeFlags & SvTreeFlags::RECALCTABS) + SetTabs(); + pImpl->Paint(rRenderContext, rRect); + + //Add visual focus draw + if (First()) + return; + + if (HasFocus()) + { + tools::Long nHeight = rRenderContext.GetTextHeight(); + tools::Rectangle aRect(Point(0, 0), Size(GetSizePixel().Width(), nHeight)); + ShowFocus(aRect); + } + else + { + HideFocus(); + } +} + +void SvTreeListBox::MouseButtonDown( const MouseEvent& rMEvt ) +{ + // tdf#143114 remember the *correct* starting entry + pImpl->m_pCursorOld = (rMEvt.IsLeft() && (nTreeFlags & SvTreeFlags::CHKBTN) && mnClicksToToggle > 0) + ? GetEntry(rMEvt.GetPosPixel()) + : nullptr; + + pImpl->MouseButtonDown( rMEvt ); +} + +void SvTreeListBox::MouseButtonUp( const MouseEvent& rMEvt ) +{ + // tdf#116675 clicking on an entry should toggle its checkbox + // tdf#143114 use the already created starting entry and if it exists + if (nullptr != pImpl->m_pCursorOld) + { + const Point aPnt = rMEvt.GetPosPixel(); + SvTreeListEntry* pEntry = GetEntry(aPnt); + + // compare if MouseButtonUp *is* on the same entry, regardless of scrolling + // or other things + if (pEntry && pEntry->m_Items.size() > 0 && 1 == mnClicksToToggle && pEntry == pImpl->m_pCursorOld) + { + SvLBoxItem* pItem = GetItem(pEntry, aPnt.X()); + // if the checkbox button was clicked, that will be toggled later, do not toggle here + // anyway users probably don't want to toggle the checkbox by clickink on another button + if (!pItem || pItem->GetType() != SvLBoxItemType::Button) + { + SvLBoxButton* pItemCheckBox + = static_cast<SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button)); + if (pItemCheckBox && GetItemPos(pEntry, 0).first < aPnt.X() - GetMapMode().GetOrigin().X()) + { + pItemCheckBox->ClickHdl(pEntry); + InvalidateEntry(pEntry); + } + } + } + } + + pImpl->MouseButtonUp( rMEvt ); +} + +void SvTreeListBox::MouseMove( const MouseEvent& rMEvt ) +{ + pImpl->MouseMove( rMEvt ); +} + +void SvTreeListBox::SetUpdateMode( bool bUpdate ) +{ + pImpl->SetUpdateMode( bUpdate ); + mbUpdateAlternatingRows = bUpdate; + SetAlternatingRowColors( mbAlternatingRowColors ); +} + +void SvTreeListBox::SetSpaceBetweenEntries( short nOffsLogic ) +{ + if( nOffsLogic != nEntryHeightOffs ) + { + nEntryHeight = nEntryHeight - nEntryHeightOffs; + nEntryHeightOffs = nOffsLogic; + nEntryHeight = nEntryHeight + nOffsLogic; + AdjustEntryHeightAndRecalc(); + pImpl->SetEntryHeight(); + } +} + +void SvTreeListBox::SetCurEntry( SvTreeListEntry* pEntry ) +{ + pImpl->SetCurEntry( pEntry ); +} + +Image const & SvTreeListBox::GetExpandedNodeBmp( ) const +{ + return pImpl->GetExpandedNodeBmp( ); +} + +Point SvTreeListBox::GetEntryPosition(const SvTreeListEntry* pEntry) const +{ + return pImpl->GetEntryPosition( pEntry ); +} + +void SvTreeListBox::MakeVisible( SvTreeListEntry* pEntry ) +{ + pImpl->MakeVisible(pEntry); +} + +void SvTreeListBox::MakeVisible( SvTreeListEntry* pEntry, bool bMoveToTop ) +{ + pImpl->MakeVisible( pEntry, bMoveToTop ); +} + +void SvTreeListBox::ModelHasEntryInvalidated( SvTreeListEntry* pEntry ) +{ + + // reinitialize the separate items of the entries + sal_uInt16 nCount = pEntry->ItemCount(); + for( sal_uInt16 nIdx = 0; nIdx < nCount; nIdx++ ) + { + SvLBoxItem& rItem = pEntry->GetItem( nIdx ); + rItem.InitViewData( this, pEntry ); + } + + // repaint + pImpl->InvalidateEntry( pEntry ); +} + +void SvTreeListBox::EditItemText(SvTreeListEntry* pEntry, SvLBoxString* pItem, const Selection& rSelection) +{ + assert(pEntry && pItem); + if( IsSelected( pEntry )) + { + pImpl->ShowCursor( false ); + SelectListEntry( pEntry, false ); + pImpl->InvalidateEntry(pEntry); + SelectListEntry( pEntry, true ); + pImpl->ShowCursor( true ); + } + pEdEntry = pEntry; + pEdItem = pItem; + SvLBoxTab* pTab = GetTab( pEntry, pItem ); + DBG_ASSERT(pTab,"EditItemText:Tab not found"); + + auto nItemHeight( pItem->GetHeight(this, pEntry) ); + Point aPos = GetEntryPosition( pEntry ); + aPos.AdjustY(( nEntryHeight - nItemHeight ) / 2 ); + aPos.setX( GetTabPos( pEntry, pTab ) ); + tools::Long nOutputWidth = pImpl->GetOutputSize().Width(); + Size aSize( nOutputWidth - aPos.X(), nItemHeight ); + sal_uInt16 nPos = std::find_if( aTabs.begin(), aTabs.end(), + [pTab](const std::unique_ptr<SvLBoxTab>& p) { return p.get() == pTab; }) + - aTabs.begin(); + if( nPos+1 < static_cast<sal_uInt16>(aTabs.size()) ) + { + SvLBoxTab* pRightTab = aTabs[ nPos + 1 ].get(); + tools::Long nRight = GetTabPos( pEntry, pRightTab ); + if( nRight <= nOutputWidth ) + aSize.setWidth( nRight - aPos.X() ); + } + Point aOrigin( GetMapMode().GetOrigin() ); + aPos += aOrigin; // convert to win coordinates + aSize.AdjustWidth( -(aOrigin.X()) ); + tools::Rectangle aRect( aPos, aSize ); + EditText( pItem->GetText(), aRect, rSelection ); +} + +void SvTreeListBox::EditEntry( SvTreeListEntry* pEntry ) +{ + pImpl->m_aEditClickPos = Point( -1, -1 ); + ImplEditEntry( pEntry ); +} + +void SvTreeListBox::ImplEditEntry( SvTreeListEntry* pEntry ) +{ + if( IsEditingActive() ) + EndEditing(); + if( !pEntry ) + pEntry = GetCurEntry(); + if( !pEntry ) + return; + + tools::Long nClickX = pImpl->m_aEditClickPos.X(); + bool bIsMouseTriggered = nClickX >= 0; + + SvLBoxString* pItem = nullptr; + sal_uInt16 nCount = pEntry->ItemCount(); + tools::Long nTabPos, nNextTabPos = 0; + for( sal_uInt16 i = 0 ; i < nCount ; i++ ) + { + SvLBoxItem& rTmpItem = pEntry->GetItem( i ); + if (rTmpItem.GetType() != SvLBoxItemType::String) + continue; + + SvLBoxTab* pTab = GetTab( pEntry, &rTmpItem ); + nNextTabPos = -1; + if( i < nCount - 1 ) + { + SvLBoxItem& rNextItem = pEntry->GetItem( i + 1 ); + SvLBoxTab* pNextTab = GetTab( pEntry, &rNextItem ); + nNextTabPos = pNextTab->GetPos(); + } + + if( pTab && pTab->IsEditable() ) + { + nTabPos = pTab->GetPos(); + if( !bIsMouseTriggered || (nClickX > nTabPos && (nNextTabPos == -1 || nClickX < nNextTabPos ) ) ) + { + pItem = static_cast<SvLBoxString*>( &rTmpItem ); + break; + } + } + } + + if( pItem && EditingEntry( pEntry ) ) + { + Selection aSel( SELECTION_MIN, SELECTION_MAX ); + SelectAll( false ); + MakeVisible( pEntry ); + EditItemText( pEntry, pItem, aSel ); + } +} + +void SvTreeListBox::EditedText( const OUString& rStr ) + +{ + if(pEdEntry) // we have to check if this entry is null that means that it is removed while editing + { + if( EditedEntry( pEdEntry, rStr ) ) + { + static_cast<SvLBoxString*>(pEdItem)->SetText( rStr ); + pModel->InvalidateEntry( pEdEntry ); + } + if( GetSelectionCount() == 0 ) + Select( pEdEntry ); + if( GetSelectionMode() == SelectionMode::Multiple && !GetCurEntry() ) + SetCurEntry( pEdEntry ); + } +} + +SvTreeListEntry* SvTreeListBox::GetDropTarget( const Point& rPos ) +{ + // scroll + if( rPos.Y() < 12 ) + { + ImplShowTargetEmphasis(pTargetEntry, false); + ScrollOutputArea( +1 ); + } + else + { + Size aSize( pImpl->GetOutputSize() ); + if( rPos.Y() > aSize.Height() - 12 ) + { + ImplShowTargetEmphasis(pTargetEntry, false); + ScrollOutputArea( -1 ); + } + } + + SvTreeListEntry* pTarget = pImpl->GetEntry( rPos ); + // when dropping in a vacant space, use the last entry + if( !pTarget ) + return LastVisible(); + else if( (GetDragDropMode() & DragDropMode::ENABLE_TOP) && + pTarget == First() && rPos.Y() < 6 ) + return nullptr; + + return pTarget; +} + + +SvTreeListEntry* SvTreeListBox::GetEntry( const Point& rPos, bool bHit ) const +{ + SvTreeListEntry* pEntry = pImpl->GetEntry( rPos ); + if( pEntry && bHit ) + { + tools::Long nLine = pImpl->GetEntryLine( pEntry ); + if( !(pImpl->EntryReallyHit( pEntry, rPos, nLine)) ) + return nullptr; + } + return pEntry; +} + +SvTreeListEntry* SvTreeListBox::GetCurEntry() const +{ + return pImpl ? pImpl->GetCurEntry() : nullptr; +} + +void SvTreeListBox::ImplInitStyle() +{ + const WinBits nWindowStyle = GetStyle(); + + nTreeFlags |= SvTreeFlags::RECALCTABS; + if (nWindowStyle & WB_SORT) + { + GetModel()->SetSortMode(SvSortMode::Ascending); + GetModel()->SetCompareHdl(LINK(this, SvTreeListBox, DefaultCompare)); + } + else + { + GetModel()->SetSortMode(SvSortMode::None); + GetModel()->SetCompareHdl(Link<const SvSortData&,sal_Int32>()); + } + pImpl->SetStyle(nWindowStyle); + pImpl->Resize(); + Invalidate(); +} + +void SvTreeListBox::InvalidateEntry(SvTreeListEntry* pEntry) +{ + DBG_ASSERT(pEntry,"InvalidateEntry:No Entry"); + if (pEntry) + { + GetModel()->InvalidateEntry(pEntry); + } +} + +void SvTreeListBox::PaintEntry1(SvTreeListEntry& rEntry, tools::Long nLine, vcl::RenderContext& rRenderContext) +{ + tools::Rectangle aRect; // multi purpose + + bool bHorSBar = pImpl->HasHorScrollBar(); + + pImpl->UpdateContextBmpWidthMax(&rEntry); + + if (nTreeFlags & SvTreeFlags::RECALCTABS) + SetTabs(); + + short nTempEntryHeight = GetEntryHeight(); + tools::Long nWidth = pImpl->GetOutputSize().Width(); + + // Did we turn on the scrollbar within PreparePaints? If yes, we have to set + // the ClipRegion anew. + if (!bHorSBar && pImpl->HasHorScrollBar()) + rRenderContext.SetClipRegion(vcl::Region(pImpl->GetClipRegionRect())); + + Point aEntryPos(rRenderContext.GetMapMode().GetOrigin()); + aEntryPos.setX( aEntryPos.X() * -1 ); // conversion document coordinates + tools::Long nMaxRight = nWidth + aEntryPos.X() - 1; + + Color aBackupTextColor(rRenderContext.GetTextColor()); + vcl::Font aBackupFont(rRenderContext.GetFont()); + Color aBackupColor = rRenderContext.GetFillColor(); + + bool bCurFontIsSel = false; + // if a ClipRegion was set from outside, we don't have to reset it + const WinBits nWindowStyle = GetStyle(); + const bool bHideSelection = (nWindowStyle & WB_HIDESELECTION) !=0 && !HasFocus(); + const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings(); + + vcl::Font aHighlightFont(rRenderContext.GetFont()); + const Color aHighlightTextColor(rSettings.GetHighlightTextColor()); + aHighlightFont.SetColor(aHighlightTextColor); + + Size aRectSize(0, nTempEntryHeight); + + SvViewDataEntry* pViewDataEntry = GetViewDataEntry( &rEntry ); + const bool bSeparator(rEntry.GetFlags() & SvTLEntryFlags::IS_SEPARATOR); + + const size_t nTabCount = aTabs.size(); + const size_t nItemCount = rEntry.ItemCount(); + size_t nCurTab = 0; + size_t nCurItem = 0; + + while (nCurTab < nTabCount && nCurItem < nItemCount) + { + SvLBoxTab* pTab = aTabs[nCurTab].get(); + const size_t nNextTab = nCurTab + 1; + SvLBoxTab* pNextTab = nNextTab < nTabCount ? aTabs[nNextTab].get() : nullptr; + SvLBoxItem& rItem = rEntry.GetItem(nCurItem); + + SvLBoxTabFlags nFlags = pTab->nFlags; + Size aSize(rItem.GetWidth(this, pViewDataEntry, nCurItem), + SvLBoxItem::GetHeight(pViewDataEntry, nCurItem)); + tools::Long nTabPos = GetTabPos(&rEntry, pTab); + + tools::Long nNextTabPos; + if (pNextTab) + nNextTabPos = GetTabPos(&rEntry, pNextTab); + else + { + nNextTabPos = nMaxRight; + if (nTabPos > nMaxRight) + nNextTabPos += 50; + } + + tools::Long nX; + if( pTab->nFlags & SvLBoxTabFlags::ADJUST_RIGHT ) + // avoid cutting the right edge off the tab separation + nX = nTabPos + pTab->CalcOffset(aSize.Width(), (nNextTabPos - SV_TAB_BORDER - 1) - nTabPos); + else + nX = nTabPos + pTab->CalcOffset(aSize.Width(), nNextTabPos - nTabPos); + + aEntryPos.setX( nX ); + aEntryPos.setY( nLine ); + + // set background pattern/color + + Wallpaper aWallpaper = rRenderContext.GetBackground(); + + bool bSelTab = bool(nFlags & SvLBoxTabFlags::SHOW_SELECTION); + + if (pViewDataEntry->IsHighlighted() && bSelTab) + { + Color aNewWallColor = rSettings.GetHighlightColor(); + // if the face color is bright then the deactivate color is also bright + // -> so you can't see any deactivate selection + if (bHideSelection && !rSettings.GetFaceColor().IsBright() + && aWallpaper.GetColor().IsBright() != rSettings.GetDeactiveColor().IsBright()) + { + aNewWallColor = rSettings.GetDeactiveColor(); + } + // set font color to highlight + if (!bCurFontIsSel) + { + rRenderContext.SetTextColor(aHighlightTextColor); + rRenderContext.SetFont(aHighlightFont); + bCurFontIsSel = true; + } + aWallpaper.SetColor(aNewWallColor); + } + else // no selection + { + if (bCurFontIsSel || rEntry.GetTextColor()) + { + bCurFontIsSel = false; + if (const auto & xCustomTextColor = rEntry.GetTextColor()) + rRenderContext.SetTextColor(*xCustomTextColor); + else + rRenderContext.SetTextColor(aBackupTextColor); + rRenderContext.SetFont(aBackupFont); + } + else + { + aWallpaper.SetColor(rEntry.GetBackColor()); + } + } + + // draw background + if (!(nTreeFlags & SvTreeFlags::USESEL)) + { + // only draw the area that is used by the item + aRectSize.setWidth( aSize.Width() ); + aRect.SetPos(aEntryPos); + aRect.SetSize(aRectSize); + } + else + { + // draw from the current to the next tab + if (nCurTab != 0) + aRect.SetLeft( nTabPos ); + else + // if we're in the 0th tab, always draw from column 0 -- + // else we get problems with centered tabs + aRect.SetLeft( 0 ); + aRect.SetTop( nLine ); + aRect.SetBottom( nLine + nTempEntryHeight - 1 ); + if (pNextTab) + { + tools::Long nRight; + nRight = GetTabPos(&rEntry, pNextTab) - 1; + if (nRight > nMaxRight) + nRight = nMaxRight; + aRect.SetRight( nRight ); + } + else + { + aRect.SetRight( nMaxRight ); + } + } + // A custom selection that starts at a tab position > 0, do not fill + // the background of the 0th item, else e.g. we might not be able to + // realize tab listboxes with lines. + if (!(nCurTab == 0 && (nTreeFlags & SvTreeFlags::USESEL) && nFirstSelTab)) + { + Color aBackgroundColor = aWallpaper.GetColor(); + if (aBackgroundColor != COL_TRANSPARENT) + { + rRenderContext.SetFillColor(aBackgroundColor); + // this case may occur for smaller horizontal resizes + if (aRect.Left() < aRect.Right()) + rRenderContext.DrawRect(aRect); + } + } + // draw item + // center vertically + aEntryPos.AdjustY((nTempEntryHeight - aSize.Height()) / 2 ); + + rItem.Paint(aEntryPos, *this, rRenderContext, pViewDataEntry, rEntry); + + // division line between tabs (but not if this is a separator line) + if (!bSeparator && pNextTab && rItem.GetType() == SvLBoxItemType::String && + // not at the right edge of the window! + aRect.Right() < nMaxRight) + { + aRect.SetLeft( aRect.Right() - SV_TAB_BORDER ); + rRenderContext.DrawRect(aRect); + } + + rRenderContext.SetFillColor(aBackupColor); + + nCurItem++; + nCurTab++; + } + + if (pViewDataEntry->IsDragTarget()) + { + rRenderContext.Push(); + rRenderContext.SetLineColor(rSettings.GetDeactiveColor()); + rRenderContext.SetFillColor(rSettings.GetDeactiveColor()); + + const bool bAsTree = GetStyle() & (WB_HASLINES | WB_HASLINESATROOT); + if (bAsTree) + { + rRenderContext.DrawRect(tools::Rectangle(Point(0, nLine + nTempEntryHeight - 2), Size(nWidth, 2))); + rRenderContext.DrawRect(tools::Rectangle(Point(0, nLine), Size(nWidth, 2))); + } + else + { + rRenderContext.DrawRect(tools::Rectangle(Point(0, nLine), Size(nWidth, 2))); + } + + rRenderContext.Pop(); + } + + if (bCurFontIsSel || rEntry.GetTextColor()) + { + rRenderContext.SetTextColor(aBackupTextColor); + rRenderContext.SetFont(aBackupFont); + } + + sal_uInt16 nFirstDynTabPos(0); + SvLBoxTab* pFirstDynamicTab = GetFirstDynamicTab(nFirstDynTabPos); + tools::Long nDynTabPos = GetTabPos(&rEntry, pFirstDynamicTab); + nDynTabPos += pImpl->m_nNodeBmpTabDistance; + nDynTabPos += pImpl->m_nNodeBmpWidth / 2; + nDynTabPos += 4; // 4 pixels of buffer, so the node bitmap is not too close + // to the next tab + + if( !((!(rEntry.GetFlags() & SvTLEntryFlags::NO_NODEBMP)) && + (nWindowStyle & WB_HASBUTTONS) && pFirstDynamicTab && + (rEntry.HasChildren() || rEntry.HasChildrenOnDemand()))) + return; + + // find first tab and check if the node bitmap extends into it + sal_uInt16 nNextTab = nFirstDynTabPos; + SvLBoxTab* pNextTab; + do + { + nNextTab++; + pNextTab = nNextTab < nTabCount ? aTabs[nNextTab].get() : nullptr; + } while (pNextTab && pNextTab->IsDynamic()); + + if (pNextTab && (GetTabPos( &rEntry, pNextTab ) <= nDynTabPos)) + return; + + if (!((nWindowStyle & WB_HASBUTTONSATROOT) || pModel->GetDepth(&rEntry) > 0)) + return; + + Point aPos(GetTabPos(&rEntry, pFirstDynamicTab), nLine); + aPos.AdjustX(pImpl->m_nNodeBmpTabDistance ); + + const Image* pImg = nullptr; + + const bool bExpanded = IsExpanded(&rEntry); + if (bExpanded) + pImg = &pImpl->GetExpandedNodeBmp(); + else + pImg = &pImpl->GetCollapsedNodeBmp(); + const bool bDefaultImage = bExpanded ? *pImg == GetDefaultExpandedNodeImage() + : *pImg == GetDefaultCollapsedNodeImage(); + aPos.AdjustY((nTempEntryHeight - pImg->GetSizePixel().Height()) / 2 ); + + if (!bDefaultImage) + { + // If it's a custom image then draw what was explicitly set to use + DrawImageFlags nStyle = DrawImageFlags::NONE; + if (!IsEnabled()) + nStyle |= DrawImageFlags::Disable; + rRenderContext.DrawImage(aPos, *pImg, nStyle); + } + else + { + bool bNativeOK = false; + // native + if (rRenderContext.IsNativeControlSupported(ControlType::ListNode, ControlPart::Entire)) + { + ImplControlValue aControlValue; + tools::Rectangle aCtrlRegion(aPos, pImg->GetSizePixel()); + ControlState nState = ControlState::NONE; + + if (IsEnabled()) + nState |= ControlState::ENABLED; + + if (bExpanded) + aControlValue.setTristateVal(ButtonValue::On); //expanded node + else + { + if ((!rEntry.HasChildren()) && rEntry.HasChildrenOnDemand() && + (!(rEntry.GetFlags() & SvTLEntryFlags::HAD_CHILDREN))) + { + aControlValue.setTristateVal( ButtonValue::DontKnow ); //don't know + } + else + { + aControlValue.setTristateVal( ButtonValue::Off ); //collapsed node + } + } + + bNativeOK = rRenderContext.DrawNativeControl(ControlType::ListNode, ControlPart::Entire, aCtrlRegion, nState, aControlValue, OUString()); + } + if (!bNativeOK) + { + DecorationView aDecoView(&rRenderContext); + DrawSymbolFlags nSymbolStyle = DrawSymbolFlags::NONE; + if (!IsEnabled()) + nSymbolStyle |= DrawSymbolFlags::Disable; + + Color aCol = aBackupTextColor; + if (pViewDataEntry->IsHighlighted()) + aCol = aHighlightTextColor; + + SymbolType eSymbol = bExpanded ? SymbolType::SPIN_DOWN : SymbolType::SPIN_RIGHT; + aDecoView.DrawSymbol(tools::Rectangle(aPos, pImg->GetSizePixel()), eSymbol, aCol, nSymbolStyle); + } + } +} + +void SvTreeListBox::DrawCustomEntry(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, const SvTreeListEntry& rEntry) +{ + aCustomRenderHdl.Call(std::tuple<vcl::RenderContext&, const tools::Rectangle&, const SvTreeListEntry&>(rRenderContext, rRect, rEntry)); +} + +Size SvTreeListBox::MeasureCustomEntry(vcl::RenderContext& rRenderContext, const SvTreeListEntry& rEntry) const +{ + return aCustomMeasureHdl.Call(std::pair<vcl::RenderContext&, const SvTreeListEntry&>(rRenderContext, rEntry)); +} + +tools::Rectangle SvTreeListBox::GetFocusRect(const SvTreeListEntry* pEntry, tools::Long nLine ) +{ + pImpl->UpdateContextBmpWidthMax( pEntry ); + + Size aSize; + tools::Rectangle aRect; + aRect.SetTop( nLine ); + aSize.setHeight( GetEntryHeight() ); + + tools::Long nRealWidth = pImpl->GetOutputSize().Width(); + nRealWidth -= GetMapMode().GetOrigin().X(); + + sal_uInt16 nCurTab; + SvLBoxTab* pTab = GetFirstTab( SvLBoxTabFlags::SHOW_SELECTION, nCurTab ); + tools::Long nTabPos = 0; + if( pTab ) + nTabPos = GetTabPos( pEntry, pTab ); + tools::Long nNextTabPos; + if( pTab && nCurTab < aTabs.size() - 1 ) + { + SvLBoxTab* pNextTab = aTabs[ nCurTab + 1 ].get(); + nNextTabPos = GetTabPos( pEntry, pNextTab ); + } + else + { + nNextTabPos = nRealWidth; + if( nTabPos > nRealWidth ) + nNextTabPos += 50; + } + + bool bUserSelection = bool( nTreeFlags & SvTreeFlags::USESEL ); + if( !bUserSelection ) + { + if( pTab && nCurTab < pEntry->ItemCount() ) + { + const SvLBoxItem& rItem = pEntry->GetItem( nCurTab ); + aSize.setWidth(rItem.GetWidth(this, pEntry)); + if( !aSize.Width() ) + aSize.setWidth( 15 ); + tools::Long nX = nTabPos; //GetTabPos( pEntry, pTab ); + // alignment + nX += pTab->CalcOffset( aSize.Width(), nNextTabPos - nTabPos ); + aRect.SetLeft( nX ); + // make sure that first and last letter aren't cut off slightly + aRect.SetSize( aSize ); + if( aRect.Left() > 0 ) + aRect.AdjustLeft( -1 ); + aRect.AdjustRight( 1 ); + } + } + else + { + // if SelTab != 0, we have to calculate also + if( nFocusWidth == -1 || nFirstSelTab ) + { + SvLBoxTab* pLastTab = nullptr; // default to select whole width + + sal_uInt16 nLastTab; + GetLastTab(SvLBoxTabFlags::SHOW_SELECTION,nLastTab); + nLastTab++; + if( nLastTab < aTabs.size() ) // is there another one? + pLastTab = aTabs[ nLastTab ].get(); + + aSize.setWidth( pLastTab ? pLastTab->GetPos() : 0x0fffffff ); + nFocusWidth = static_cast<short>(aSize.Width()); + if( pTab ) + nFocusWidth = nFocusWidth - static_cast<short>(nTabPos); //pTab->GetPos(); + } + else + { + aSize.setWidth( nFocusWidth ); + if( pTab ) + { + if( nCurTab ) + aSize.AdjustWidth(nTabPos ); + else + aSize.AdjustWidth(pTab->GetPos() ); // Tab0 always from the leftmost position + } + } + // if selection starts with 0th tab, draw from column 0 on + if( nCurTab != 0 ) + { + aRect.SetLeft( nTabPos ); + aSize.AdjustWidth( -nTabPos ); + } + aRect.SetSize( aSize ); + } + // adjust right edge because of clipping + if( aRect.Right() >= nRealWidth ) + { + aRect.SetRight( nRealWidth-1 ); + nFocusWidth = static_cast<short>(aRect.GetWidth()); + } + return aRect; +} + +sal_IntPtr SvTreeListBox::GetTabPos(const SvTreeListEntry* pEntry, const SvLBoxTab* pTab) const +{ + assert(pTab); + sal_IntPtr nPos = pTab->GetPos(); + if( pTab->IsDynamic() ) + { + sal_uInt16 nDepth = pModel->GetDepth( pEntry ); + nDepth = nDepth * static_cast<sal_uInt16>(nIndent); + nPos += static_cast<sal_IntPtr>(nDepth); + } + return nPos + (pEntry->GetExtraIndent() * nIndent); +} + +SvLBoxItem* SvTreeListBox::GetItem_Impl( SvTreeListEntry* pEntry, tools::Long nX, + SvLBoxTab** ppTab ) +{ + SvLBoxItem* pItemClicked = nullptr; + sal_uInt16 nTabCount = aTabs.size(); + sal_uInt16 nItemCount = pEntry->ItemCount(); + SvLBoxTab* pTab = aTabs.front().get(); + SvLBoxItem* pItem = &pEntry->GetItem(0); + sal_uInt16 nNextItem = 1; + nX -= GetMapMode().GetOrigin().X(); + tools::Long nRealWidth = pImpl->GetOutputSize().Width(); + nRealWidth -= GetMapMode().GetOrigin().X(); + + while( true ) + { + SvLBoxTab* pNextTab=nNextItem<nTabCount ? aTabs[nNextItem].get() : nullptr; + tools::Long nStart = GetTabPos( pEntry, pTab ); + + tools::Long nNextTabPos; + if( pNextTab ) + nNextTabPos = GetTabPos( pEntry, pNextTab ); + else + { + nNextTabPos = nRealWidth; + if( nStart > nRealWidth ) + nNextTabPos += 50; + } + + auto nItemWidth(pItem->GetWidth(this, pEntry)); + nStart += pTab->CalcOffset(nItemWidth, nNextTabPos - nStart); + auto nLen = nItemWidth; + if( pNextTab ) + { + tools::Long nTabWidth = GetTabPos( pEntry, pNextTab ) - nStart; + if( nTabWidth < nLen ) + nLen = nTabWidth; + } + + if( nX >= nStart && nX < (nStart+nLen ) ) + { + pItemClicked = pItem; + if( ppTab ) + { + *ppTab = pTab; + break; + } + } + if( nNextItem >= nItemCount || nNextItem >= nTabCount) + break; + pTab = aTabs[ nNextItem ].get(); + pItem = &pEntry->GetItem( nNextItem ); + nNextItem++; + } + return pItemClicked; +} + +std::pair<tools::Long, tools::Long> SvTreeListBox::GetItemPos(SvTreeListEntry* pEntry, sal_uInt16 nTabIdx) +{ + sal_uInt16 nTabCount = aTabs.size(); + sal_uInt16 nItemCount = pEntry->ItemCount(); + if (nTabIdx >= nItemCount || nTabIdx >= nTabCount) + return std::make_pair(-1, -1); + + SvLBoxTab* pTab = aTabs.front().get(); + SvLBoxItem* pItem = &pEntry->GetItem(nTabIdx); + sal_uInt16 nNextItem = nTabIdx + 1; + + tools::Long nRealWidth = pImpl->GetOutputSize().Width(); + nRealWidth -= GetMapMode().GetOrigin().X(); + + SvLBoxTab* pNextTab = nNextItem < nTabCount ? aTabs[nNextItem].get() : nullptr; + tools::Long nStart = GetTabPos(pEntry, pTab); + + tools::Long nNextTabPos; + if (pNextTab) + nNextTabPos = GetTabPos(pEntry, pNextTab); + else + { + nNextTabPos = nRealWidth; + if (nStart > nRealWidth) + nNextTabPos += 50; + } + + auto nItemWidth(pItem->GetWidth(this, pEntry)); + nStart += pTab->CalcOffset(nItemWidth, nNextTabPos - nStart); + auto nLen = nItemWidth; + if (pNextTab) + { + tools::Long nTabWidth = GetTabPos(pEntry, pNextTab) - nStart; + if (nTabWidth < nLen) + nLen = nTabWidth; + } + return std::make_pair(nStart, nLen); +} + +tools::Long SvTreeListBox::getPreferredDimensions(std::vector<tools::Long> &rWidths) const +{ + tools::Long nHeight = 0; + rWidths.clear(); + SvTreeListEntry* pEntry = First(); + while (pEntry) + { + sal_uInt16 nCount = pEntry->ItemCount(); + sal_uInt16 nCurPos = 0; + if (nCount > rWidths.size()) + rWidths.resize(nCount); + while (nCurPos < nCount) + { + SvLBoxItem& rItem = pEntry->GetItem( nCurPos ); + auto nWidth = rItem.GetWidth(this, pEntry); + if (nWidth) + { + nWidth += SV_TAB_BORDER * 2; + if (nWidth > rWidths[nCurPos]) + rWidths[nCurPos] = nWidth; + } + ++nCurPos; + } + pEntry = Next( pEntry ); + nHeight += GetEntryHeight(); + } + return nHeight; +} + +Size SvTreeListBox::GetOptimalSize() const +{ + std::vector<tools::Long> aWidths; + Size aRet(0, getPreferredDimensions(aWidths)); + for (tools::Long aWidth : aWidths) + aRet.AdjustWidth(aWidth ); + + sal_Int32 nLeftBorder(0), nTopBorder(0), nRightBorder(0), nBottomBorder(0); + GetBorder(nLeftBorder, nTopBorder, nRightBorder, nBottomBorder); + aRet.AdjustWidth(nLeftBorder + nRightBorder); + aRet.AdjustHeight(nTopBorder + nBottomBorder); + + tools::Long nMinWidth = nMinWidthInChars * approximate_char_width(); + aRet.setWidth( std::max(aRet.Width(), nMinWidth) ); + + if (GetStyle() & WB_VSCROLL) + aRet.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize()); + + return aRet; +} + +void SvTreeListBox::SetAlternatingRowColors( bool bEnable ) +{ + if( !mbUpdateAlternatingRows ) + { + mbAlternatingRowColors = bEnable; + return; + } + + if( bEnable ) + { + SvTreeListEntry* pEntry = pModel->First(); + for(size_t i = 0; pEntry; ++i) + { + pEntry->SetBackColor( i % 2 == 0 ? GetBackground().GetColor() : GetSettings().GetStyleSettings().GetAlternatingRowColor()); + SvTreeListEntry *pNextEntry = nullptr; + if( IsExpanded( pEntry ) ) + pNextEntry = pModel->FirstChild( pEntry ); + else + pNextEntry = pEntry->NextSibling(); + + if( !pNextEntry ) + pEntry = pModel->Next( pEntry ); + else + pEntry = pNextEntry; + } + } + else if( mbAlternatingRowColors ) + for(SvTreeListEntry* pEntry = pModel->First(); pEntry; pEntry = pModel->Next(pEntry)) + pEntry->SetBackColor( GetBackground().GetColor() ); + + mbAlternatingRowColors = bEnable; + pImpl->UpdateAll(true); +} + +void SvTreeListBox::SetForceMakeVisible( bool bEnable ) +{ + pImpl->SetForceMakeVisible(bEnable); +} + +SvLBoxItem* SvTreeListBox::GetItem(SvTreeListEntry* pEntry,tools::Long nX,SvLBoxTab** ppTab) +{ + return GetItem_Impl( pEntry, nX, ppTab ); +} + +SvLBoxItem* SvTreeListBox::GetItem(SvTreeListEntry* pEntry,tools::Long nX ) +{ + SvLBoxTab* pDummyTab; + return GetItem_Impl( pEntry, nX, &pDummyTab ); +} + +void SvTreeListBox::AddTab(tools::Long nTabPos, SvLBoxTabFlags nFlags ) +{ + nFocusWidth = -1; + SvLBoxTab* pTab = new SvLBoxTab( nTabPos, nFlags ); + aTabs.emplace_back( pTab ); + if( nTreeFlags & SvTreeFlags::USESEL ) + { + sal_uInt16 nPos = aTabs.size() - 1; + if( nPos >= nFirstSelTab && nPos <= nLastSelTab ) + pTab->nFlags |= SvLBoxTabFlags::SHOW_SELECTION; + else + // string items usually have to be selected -- turn this off + // explicitly + pTab->nFlags &= ~SvLBoxTabFlags::SHOW_SELECTION; + } +} + + +SvLBoxTab* SvTreeListBox::GetFirstDynamicTab( sal_uInt16& rPos ) const +{ + sal_uInt16 nCurTab = 0; + sal_uInt16 nTabCount = aTabs.size(); + while( nCurTab < nTabCount ) + { + SvLBoxTab* pTab = aTabs[nCurTab].get(); + if( pTab->nFlags & SvLBoxTabFlags::DYNAMIC ) + { + rPos = nCurTab; + return pTab; + } + nCurTab++; + } + return nullptr; +} + +SvLBoxTab* SvTreeListBox::GetFirstDynamicTab() const +{ + sal_uInt16 nDummy; + return GetFirstDynamicTab( nDummy ); +} + +SvLBoxTab* SvTreeListBox::GetTab( SvTreeListEntry const * pEntry, SvLBoxItem const * pItem) const +{ + sal_uInt16 nPos = pEntry->GetPos( pItem ); + return aTabs[ nPos ].get(); +} + +void SvTreeListBox::ClearTabList() +{ + aTabs.clear(); +} + + +Size SvTreeListBox::GetOutputSizePixel() const +{ + Size aSize = pImpl->GetOutputSize(); + return aSize; +} + +void SvTreeListBox::NotifyScrolled() +{ + aScrolledHdl.Call( this ); +} + +void SvTreeListBox::Invalidate( InvalidateFlags nInvalidateFlags ) +{ + if (!pImpl) + return; + if( nFocusWidth == -1 ) + // to make sure that the control doesn't show the wrong focus rectangle + // after painting + pImpl->RecalcFocusRect(); + Control::Invalidate( nInvalidateFlags ); + pImpl->Invalidate(); +} + +void SvTreeListBox::Invalidate( const tools::Rectangle& rRect, InvalidateFlags nInvalidateFlags ) +{ + if( nFocusWidth == -1 ) + // to make sure that the control doesn't show the wrong focus rectangle + // after painting + pImpl->RecalcFocusRect(); + Control::Invalidate( rRect, nInvalidateFlags ); +} + + +void SvTreeListBox::SetHighlightRange( sal_uInt16 nStart, sal_uInt16 nEnd) +{ + + nTreeFlags |= SvTreeFlags::USESEL; + if( nStart > nEnd ) + std::swap(nStart, nEnd); + // select all tabs that lie within the area + nTreeFlags |= SvTreeFlags::RECALCTABS; + nFirstSelTab = nStart; + nLastSelTab = nEnd; + pImpl->RecalcFocusRect(); +} + +void SvTreeListBox::Command(const CommandEvent& rCEvt) +{ + if (!aPopupMenuHdl.Call(rCEvt)) + pImpl->Command(rCEvt); + //pass at least alt press/release to parent impl + if (rCEvt.GetCommand() == CommandEventId::ModKeyChange) + Control::Command(rCEvt); +} + +SvLBoxTab* SvTreeListBox::GetFirstTab( SvLBoxTabFlags nFlagMask, sal_uInt16& rPos ) +{ + sal_uInt16 nTabCount = aTabs.size(); + for( sal_uInt16 nPos = 0; nPos < nTabCount; nPos++ ) + { + SvLBoxTab* pTab = aTabs[ nPos ].get(); + if( pTab->nFlags & nFlagMask ) + { + rPos = nPos; + return pTab; + } + } + rPos = 0xffff; + return nullptr; +} + +void SvTreeListBox::GetLastTab( SvLBoxTabFlags nFlagMask, sal_uInt16& rTabPos ) +{ + sal_uInt16 nPos = static_cast<sal_uInt16>(aTabs.size()); + while( nPos ) + { + --nPos; + SvLBoxTab* pTab = aTabs[ nPos ].get(); + if( pTab->nFlags & nFlagMask ) + { + rTabPos = nPos; + return; + } + } + rTabPos = 0xffff; +} + +void SvTreeListBox::RequestHelp( const HelpEvent& rHEvt ) +{ + if (aTooltipHdl.IsSet() && aTooltipHdl.Call(rHEvt)) + return; + + if( !pImpl->RequestHelp( rHEvt ) ) + Control::RequestHelp( rHEvt ); +} + +sal_Int32 SvTreeListBox::DefaultCompare(const SvLBoxString* pLeftText, const SvLBoxString* pRightText) +{ + OUString aLeft = pLeftText ? pLeftText->GetText() : OUString(); + OUString aRight = pRightText ? pRightText->GetText() : OUString(); + pImpl->UpdateStringSorter(); + return pImpl->m_pStringSorter->compare(aLeft, aRight); +} + +IMPL_LINK( SvTreeListBox, DefaultCompare, const SvSortData&, rData, sal_Int32 ) +{ + const SvTreeListEntry* pLeft = rData.pLeft; + const SvTreeListEntry* pRight = rData.pRight; + const SvLBoxString* pLeftText = static_cast<const SvLBoxString*>(pLeft->GetFirstItem(SvLBoxItemType::String)); + const SvLBoxString* pRightText = static_cast<const SvLBoxString*>(pRight->GetFirstItem(SvLBoxItemType::String)); + return DefaultCompare(pLeftText, pRightText); +} + +void SvTreeListBox::ModelNotification( SvListAction nActionId, SvTreeListEntry* pEntry1, + SvTreeListEntry* pEntry2, sal_uInt32 nPos ) +{ + SolarMutexGuard aSolarGuard; + + if( nActionId == SvListAction::CLEARING ) + CancelTextEditing(); + + SvListView::ModelNotification( nActionId, pEntry1, pEntry2, nPos ); + switch( nActionId ) + { + case SvListAction::INSERTED: + { + SvLBoxContextBmp* pBmpItem = static_cast< SvLBoxContextBmp* >( pEntry1->GetFirstItem( SvLBoxItemType::ContextBmp ) ); + if ( !pBmpItem ) + break; + const Image& rBitmap1( pBmpItem->GetBitmap1() ); + const Image& rBitmap2( pBmpItem->GetBitmap2() ); + short nMaxWidth = short( std::max( rBitmap1.GetSizePixel().Width(), rBitmap2.GetSizePixel().Width() ) ); + nMaxWidth = pImpl->UpdateContextBmpWidthVector( pEntry1, nMaxWidth ); + if( nMaxWidth > nContextBmpWidthMax ) + { + nContextBmpWidthMax = nMaxWidth; + SetTabs(); + } + if (get_width_request() == -1) + queue_resize(); + } + break; + + case SvListAction::RESORTING: + SetUpdateMode( false ); + break; + + case SvListAction::RESORTED: + // after a selection: show first entry and also keep the selection + MakeVisible( pModel->First(), true ); + SetUpdateMode( true ); + break; + + case SvListAction::CLEARED: + if( IsUpdateMode() ) + PaintImmediately(); + break; + + default: break; + } +} + +SvTreeListEntry* SvTreeListBox::GetFirstEntryInView() const +{ + return GetEntry( Point() ); +} + +SvTreeListEntry* SvTreeListBox::GetNextEntryInView(SvTreeListEntry* pEntry ) const +{ + SvTreeListEntry* pNext = NextVisible( pEntry ); + if( pNext ) + { + Point aPos( GetEntryPosition(pNext) ); + const Size& rSize = pImpl->GetOutputSize(); + if( aPos.Y() < 0 || aPos.Y() >= rSize.Height() ) + return nullptr; + } + return pNext; +} + + +void SvTreeListBox::DataChanged( const DataChangedEvent& rDCEvt ) +{ + if( (rDCEvt.GetType()==DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + nEntryHeight = 0; // _together_ with true of 1. par (bFont) of InitSettings() a zero-height + // forces complete recalc of heights! + InitSettings(); + Invalidate(); + } + else + Control::DataChanged( rDCEvt ); +} + +void SvTreeListBox::StateChanged( StateChangedType eType ) +{ + if( eType == StateChangedType::Enable ) + Invalidate( InvalidateFlags::Children ); + + Control::StateChanged( eType ); + + if ( eType == StateChangedType::Style ) + ImplInitStyle(); +} + +void SvTreeListBox::ApplySettings(vcl::RenderContext& rRenderContext) +{ + SetPointFont(rRenderContext, GetPointFont(*GetOutDev())); + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor()); + rRenderContext.SetTextFillColor(); + rRenderContext.SetBackground(rStyleSettings.GetFieldColor()); + + // always try to re-create default-SvLBoxButtonData + if (pCheckButtonData && pCheckButtonData->HasDefaultImages()) + pCheckButtonData->SetDefaultImages(this); +} + +void SvTreeListBox::InitSettings() +{ + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + vcl::Font aFont = rStyleSettings.GetFieldFont(); + SetPointFont(*GetOutDev(), aFont); + AdjustEntryHeightAndRecalc(); + + SetTextColor(rStyleSettings.GetFieldTextColor()); + SetTextFillColor(); + + SetBackground(rStyleSettings.GetFieldColor()); + + // always try to re-create default-SvLBoxButtonData + if( pCheckButtonData && pCheckButtonData->HasDefaultImages() ) + pCheckButtonData->SetDefaultImages(this); +} + +css::uno::Reference< XAccessible > SvTreeListBox::CreateAccessible() +{ + vcl::Window* pParent = GetAccessibleParentWindow(); + DBG_ASSERT( pParent, "SvTreeListBox::CreateAccessible - accessible parent not found" ); + + css::uno::Reference< XAccessible > xAccessible; + if ( pParent ) + { + css::uno::Reference< XAccessible > xAccParent = pParent->GetAccessible(); + if ( xAccParent.is() ) + { + // need to be done here to get the vclxwindow later on in the accessible + css::uno::Reference< css::awt::XWindowPeer > xHoldAlive(GetComponentInterface()); + xAccessible = pImpl->m_aFactoryAccess.getFactory().createAccessibleTreeListBox( *this, xAccParent ); + } + } + return xAccessible; +} + +void SvTreeListBox::FillAccessibleEntryStateSet( SvTreeListEntry* pEntry, ::utl::AccessibleStateSetHelper& rStateSet ) const +{ + assert(pEntry && "SvTreeListBox::FillAccessibleEntryStateSet: invalid entry"); + + if ( pEntry->HasChildrenOnDemand() || pEntry->HasChildren() ) + { + rStateSet.AddState( AccessibleStateType::EXPANDABLE ); + if ( IsExpanded( pEntry ) ) + rStateSet.AddState( sal_Int16(AccessibleStateType::EXPANDED) ); + } + + if ( GetCheckButtonState( pEntry ) == SvButtonState::Checked ) + rStateSet.AddState( AccessibleStateType::CHECKED ); + if ( IsEntryVisible( pEntry ) ) + rStateSet.AddState( AccessibleStateType::VISIBLE ); + if ( IsSelected( pEntry ) ) + rStateSet.AddState( AccessibleStateType::SELECTED ); + if ( IsEnabled() ) + { + rStateSet.AddState( AccessibleStateType::ENABLED ); + rStateSet.AddState( AccessibleStateType::FOCUSABLE ); + rStateSet.AddState( AccessibleStateType::SELECTABLE ); + SvViewDataEntry* pViewDataNewCur = GetViewDataEntry(pEntry); + if (pViewDataNewCur && pViewDataNewCur->HasFocus()) + rStateSet.AddState( AccessibleStateType::FOCUSED ); + } +} + +OUString SvTreeListBox::GetEntryAccessibleDescription(SvTreeListEntry* pEntry) const +{ + assert(pEntry); + + //want to count the real column number in the list box. + sal_uInt16 iRealItemCount = 0; + for (size_t i = 0; i < pEntry->ItemCount(); ++i) + { + const SvLBoxItem& rItem = pEntry->GetItem(i); + if (rItem.GetType() == SvLBoxItemType::String && + !static_cast<const SvLBoxString&>(rItem).GetText().isEmpty()) + { + iRealItemCount++; + } + } + // No idea why <= 1; that was in AccessibleListBoxEntry::getAccessibleDescription + // since the "Integrate branch of IAccessible2" commit + if (iRealItemCount <= 1) + { + return {}; + } + else + { + return SearchEntryTextWithHeadTitle(pEntry); + } +} + +tools::Rectangle SvTreeListBox::GetBoundingRect(const SvTreeListEntry* pEntry) +{ + Point aPos = GetEntryPosition( pEntry ); + tools::Rectangle aRect = GetFocusRect( pEntry, aPos.Y() ); + return aRect; +} + +void SvTreeListBox::CallImplEventListeners(VclEventId nEvent, void* pData) +{ + CallEventListeners(nEvent, pData); +} + +void SvTreeListBox::set_min_width_in_chars(sal_Int32 nChars) +{ + nMinWidthInChars = nChars; + queue_resize(); +} + +bool SvTreeListBox::set_property(const OString &rKey, const OUString &rValue) +{ + if (rKey == "min-width-chars") + { + set_min_width_in_chars(rValue.toInt32()); + } + else if (rKey == "enable-tree-lines") + { + auto nStyle = GetStyle(); + nStyle &= ~(WB_HASLINES | WB_HASLINESATROOT); + if (toBool(rValue)) + nStyle |= (WB_HASLINES | WB_HASLINESATROOT); + SetStyle(nStyle); + } + else if (rKey == "show-expanders") + { + auto nStyle = GetStyle(); + nStyle &= ~(WB_HASBUTTONS | WB_HASBUTTONSATROOT); + if (toBool(rValue)) + nStyle |= (WB_HASBUTTONS | WB_HASBUTTONSATROOT); + SetStyle(nStyle); + } + else if (rKey == "enable-search") + { + SetQuickSearch(toBool(rValue)); + } + else if (rKey == "activate-on-single-click") + { + SetActivateOnSingleClick(toBool(rValue)); + } + else if (rKey == "hover-selection") + { + SetHoverSelection(toBool(rValue)); + } + else if (rKey == "reorderable") + { + if (toBool(rValue)) + SetDragDropMode(DragDropMode::CTRL_MOVE | DragDropMode::ENABLE_TOP); + } + else + return Control::set_property(rKey, rValue); + return true; +} + +void SvTreeListBox::EnableRTL(bool bEnable) +{ + Control::EnableRTL(bEnable); + pImpl->m_aHorSBar->EnableRTL(bEnable); + pImpl->m_aVerSBar->EnableRTL(bEnable); + pImpl->m_aScrBarBox->EnableRTL(bEnable); +} + +FactoryFunction SvTreeListBox::GetUITestFactory() const +{ + return TreeListUIObject::create; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/treelistentry.cxx b/vcl/source/treelist/treelistentry.cxx new file mode 100644 index 000000000..c369b7332 --- /dev/null +++ b/vcl/source/treelist/treelistentry.cxx @@ -0,0 +1,235 @@ +/* -*- 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 <memory> +#include <vcl/toolkit/treelistentry.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <tools/debug.hxx> + +void SvTreeListEntry::ClearChildren() +{ + m_Children.clear(); +} + +void SvTreeListEntry::SetListPositions() +{ + sal_uInt32 nCur = 0; + for (auto const& pEntry : m_Children) + { + SvTreeListEntry& rEntry = *pEntry; + rEntry.nListPos &= 0x80000000; + rEntry.nListPos |= nCur; + ++nCur; + } + + nListPos &= (~0x80000000); // remove the invalid bit. +} + +void SvTreeListEntry::InvalidateChildrensListPositions() +{ + nListPos |= 0x80000000; +} + +SvTreeListEntry::SvTreeListEntry() + : pParent(nullptr) + , nAbsPos(0) + , nListPos(0) + , mnExtraIndent(0) + , pUserData(nullptr) + , nEntryFlags(SvTLEntryFlags::NONE) + , maBackColor(Application::GetSettings().GetStyleSettings().GetWindowColor()) +{ +} + +SvTreeListEntry::~SvTreeListEntry() +{ +#ifdef DBG_UTIL + pParent = nullptr; +#endif + + m_Children.clear(); + m_Items.clear(); +} + +bool SvTreeListEntry::HasChildren() const +{ + return !m_Children.empty(); +} + +bool SvTreeListEntry::HasChildListPos() const +{ + return pParent && !(pParent->nListPos & 0x80000000); +} + +sal_uInt32 SvTreeListEntry::GetChildListPos() const +{ + if( pParent && (pParent->nListPos & 0x80000000) ) + pParent->SetListPositions(); + return ( nListPos & 0x7fffffff ); +} + + +void SvTreeListEntry::Clone(SvTreeListEntry* pSource) +{ + nListPos &= 0x80000000; + nListPos |= ( pSource->nListPos & 0x7fffffff); + nAbsPos = pSource->nAbsPos; + mnExtraIndent = pSource->mnExtraIndent; + + m_Items.clear(); + for (auto const& it : pSource->m_Items) + { + SvLBoxItem* pItem = &(*it); + std::unique_ptr<SvLBoxItem> pNewItem(pItem->Clone(pItem)); + m_Items.push_back(std::move(pNewItem)); + } + + pUserData = pSource->GetUserData(); + nEntryFlags = pSource->nEntryFlags; +} + +size_t SvTreeListEntry::ItemCount() const +{ + return m_Items.size(); +} + +void SvTreeListEntry::AddItem(std::unique_ptr<SvLBoxItem> pItem) +{ + m_Items.push_back(std::move(pItem)); +} + +void SvTreeListEntry::EnableChildrenOnDemand( bool bEnable ) +{ + if ( bEnable ) + nEntryFlags |= SvTLEntryFlags::CHILDREN_ON_DEMAND; + else + nEntryFlags &= ~SvTLEntryFlags::CHILDREN_ON_DEMAND; +} + +void SvTreeListEntry::ReplaceItem(std::unique_ptr<SvLBoxItem> pNewItem, size_t const nPos) +{ + DBG_ASSERT(pNewItem,"ReplaceItem:No Item"); + if (nPos >= m_Items.size()) + { + // Out of bound. Bail out. + pNewItem.reset(); + return; + } + + m_Items.erase(m_Items.begin()+nPos); + m_Items.insert(m_Items.begin()+nPos, std::move(pNewItem)); +} + +const SvLBoxItem& SvTreeListEntry::GetItem( size_t nPos ) const +{ + return *m_Items[nPos]; +} + +SvLBoxItem& SvTreeListEntry::GetItem( size_t nPos ) +{ + return *m_Items[nPos]; +} + +namespace { + +class FindByType +{ + SvLBoxItemType meType; +public: + explicit FindByType(SvLBoxItemType eType) : meType(eType) {} + bool operator() (const std::unique_ptr<SvLBoxItem>& rpItem) const + { + return rpItem->GetType() == meType; + } +}; + +class FindByPointer +{ + const SvLBoxItem* mpItem; +public: + explicit FindByPointer(const SvLBoxItem* p) : mpItem(p) {} + bool operator() (const std::unique_ptr<SvLBoxItem>& rpItem) const + { + return rpItem.get() == mpItem; + } +}; + +} + +const SvLBoxItem* SvTreeListEntry::GetFirstItem(SvLBoxItemType eType) const +{ + ItemsType::const_iterator it = std::find_if(m_Items.begin(), m_Items.end(), FindByType(eType)); + return (it == m_Items.end()) ? nullptr : (*it).get(); +} + +SvLBoxItem* SvTreeListEntry::GetFirstItem(SvLBoxItemType eType) +{ + ItemsType::iterator it = std::find_if(m_Items.begin(), m_Items.end(), FindByType(eType)); + return (it == m_Items.end()) ? nullptr : (*it).get(); +} + +size_t SvTreeListEntry::GetPos( const SvLBoxItem* pItem ) const +{ + ItemsType::const_iterator it = std::find_if(m_Items.begin(), m_Items.end(), FindByPointer(pItem)); + return it == m_Items.end() ? ITEM_NOT_FOUND : std::distance(m_Items.begin(), it); +} + + +void SvTreeListEntry::SetUserData( void* pPtr ) +{ + pUserData = pPtr; +} + +bool SvTreeListEntry::HasChildrenOnDemand() const +{ + return static_cast<bool>(nEntryFlags & SvTLEntryFlags::CHILDREN_ON_DEMAND); +} + +void SvTreeListEntry::SetFlags( SvTLEntryFlags nFlags ) +{ + nEntryFlags = nFlags; +} + +SvTreeListEntry* SvTreeListEntry::NextSibling() const +{ + SvTreeListEntries& rList = pParent->m_Children; + sal_uInt32 nPos = GetChildListPos(); + nPos++; + return (nPos < rList.size()) ? rList[nPos].get() : nullptr; +} + +SvTreeListEntry* SvTreeListEntry::PrevSibling() const +{ + SvTreeListEntries& rList = pParent->m_Children; + sal_uInt32 nPos = GetChildListPos(); + if ( nPos == 0 ) + return nullptr; + nPos--; + return rList[nPos].get(); +} + + +SvTreeListEntry* SvTreeListEntry::LastSibling() const +{ + SvTreeListEntries& rChildren = pParent->m_Children; + return (rChildren.empty()) ? nullptr : rChildren.back().get(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/uiobject.cxx b/vcl/source/treelist/uiobject.cxx new file mode 100644 index 000000000..ccc066804 --- /dev/null +++ b/vcl/source/treelist/uiobject.cxx @@ -0,0 +1,185 @@ +/* -*- 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/. + */ + +#include <memory> +#include <vcl/toolkit/svlbitm.hxx> +#include <vcl/uitest/uiobject.hxx> +#include <vcl/toolkit/treelistbox.hxx> +#include <vcl/toolkit/treelistentry.hxx> + +TreeListUIObject::TreeListUIObject(const VclPtr<SvTreeListBox>& xTreeList): + WindowUIObject(xTreeList), + mxTreeList(xTreeList) +{ +} + +namespace { + +bool isCheckBoxList(const VclPtr<SvTreeListBox>& xTreeList) +{ + return (xTreeList->GetTreeFlags() & SvTreeFlags::CHKBTN) == SvTreeFlags::CHKBTN; +} + +} + +StringMap TreeListUIObject::get_state() +{ + StringMap aMap = WindowUIObject::get_state(); + + aMap["SelectionCount"] = OUString::number(mxTreeList->GetSelectionCount()); + aMap["VisibleCount"] = OUString::number(mxTreeList->GetVisibleCount()); + aMap["Children"] = OUString::number(mxTreeList->GetChildCount(nullptr)); + aMap["LevelChildren"] = OUString::number(mxTreeList->GetLevelChildCount(nullptr)); + aMap["CheckBoxList"] = OUString::boolean(isCheckBoxList(mxTreeList)); + SvTreeListEntry* pEntry = mxTreeList->FirstSelected(); + aMap["SelectEntryText"] = pEntry ? mxTreeList->GetEntryText(pEntry) : OUString(); + + return aMap; +} + +void TreeListUIObject::execute(const OUString& rAction, + const StringMap& rParameters) +{ + if (rAction.isEmpty()) + { + } + else + WindowUIObject::execute(rAction, rParameters); +} + +std::unique_ptr<UIObject> TreeListUIObject::get_child(const OUString& rID) +{ + sal_Int32 nID = rID.toInt32(); + if (nID >= 0) + { + SvTreeListEntry* pEntry = mxTreeList->GetEntry(nullptr, nID); + if (!pEntry) + return nullptr; + + return std::unique_ptr<UIObject>(new TreeListEntryUIObject(mxTreeList, pEntry)); + } + + return nullptr; +} + +std::set<OUString> TreeListUIObject::get_children() const +{ + std::set<OUString> aChildren; + + size_t nChildren = mxTreeList->GetLevelChildCount(nullptr); + for (size_t i = 0; i < nChildren; ++i) + { + aChildren.insert(OUString::number(i)); + } + + return aChildren; +} + +OUString TreeListUIObject::get_name() const +{ + return "TreeListUIObject"; +} + +std::unique_ptr<UIObject> TreeListUIObject::create(vcl::Window* pWindow) +{ + SvTreeListBox* pTreeList = dynamic_cast<SvTreeListBox*>(pWindow); + assert(pTreeList); + return std::unique_ptr<UIObject>(new TreeListUIObject(pTreeList)); +} + +TreeListEntryUIObject::TreeListEntryUIObject(const VclPtr<SvTreeListBox>& xTreeList, SvTreeListEntry* pEntry): + mxTreeList(xTreeList), + mpEntry(pEntry) +{ +} + +StringMap TreeListEntryUIObject::get_state() +{ + StringMap aMap; + + aMap["Text"] = mxTreeList->GetEntryText(mpEntry); + aMap["Children"] = OUString::number(mxTreeList->GetLevelChildCount(mpEntry)); + aMap["VisibleChildCount"] = OUString::number(mxTreeList->GetVisibleChildCount(mpEntry)); + aMap["IsSelected"] = OUString::boolean(mxTreeList->IsSelected(mpEntry)); + + aMap["IsSemiTransparent"] = OUString::boolean(bool(mpEntry->GetFlags() & SvTLEntryFlags::SEMITRANSPARENT)); + + SvLBoxButton* pItem = static_cast<SvLBoxButton*>(mpEntry->GetFirstItem(SvLBoxItemType::Button)); + if (pItem) + aMap["IsChecked"] = OUString::boolean(pItem->IsStateChecked()); + + return aMap; +} + +void TreeListEntryUIObject::execute(const OUString& rAction, const StringMap& /*rParameters*/) +{ + if (rAction == "COLLAPSE") + { + mxTreeList->Collapse(mpEntry); + } + else if (rAction == "EXPAND") + { + mxTreeList->Expand(mpEntry); + } + else if (rAction == "SELECT") + { + mxTreeList->Select(mpEntry); + } + else if (rAction == "DESELECT") + { + mxTreeList->Select(mpEntry, false); + } + else if (rAction == "CLICK") + { + SvLBoxButton* pItem = static_cast<SvLBoxButton*>(mpEntry->GetFirstItem(SvLBoxItemType::Button)); + if (!pItem) + return; + pItem->ClickHdl(mpEntry); + } + else if (rAction == "DOUBLECLICK") + { + mxTreeList->Select(mpEntry); + mxTreeList->DoubleClickHdl(); + } +} + +std::unique_ptr<UIObject> TreeListEntryUIObject::get_child(const OUString& rID) +{ + sal_Int32 nID = rID.toInt32(); + if (nID >= 0) + { + SvTreeListEntry* pEntry = mxTreeList->GetEntry(mpEntry, nID); + if (!pEntry) + return nullptr; + + return std::unique_ptr<UIObject>(new TreeListEntryUIObject(mxTreeList, pEntry)); + } + + return nullptr; +} + +std::set<OUString> TreeListEntryUIObject::get_children() const +{ + std::set<OUString> aChildren; + + size_t nChildren = mxTreeList->GetLevelChildCount(mpEntry); + for (size_t i = 0; i < nChildren; ++i) + { + aChildren.insert(OUString::number(i)); + } + + return aChildren; +} + +OUString TreeListEntryUIObject::get_type() const +{ + return "TreeListEntry"; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/viewdataentry.cxx b/vcl/source/treelist/viewdataentry.cxx new file mode 100644 index 000000000..b14d47931 --- /dev/null +++ b/vcl/source/treelist/viewdataentry.cxx @@ -0,0 +1,87 @@ +/* -*- 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 <vcl/toolkit/viewdataentry.hxx> + +SvViewDataEntry::SvViewDataEntry() : + nVisPos(0), + mbSelected(false), + mbHighlighted(false), + mbExpanded(false), + mbFocused(false), + mbSelectable(true), + mbDragTarget(false) +{ +} + +SvViewDataEntry::SvViewDataEntry( const SvViewDataEntry& rData ) : + nVisPos(rData.nVisPos), + mbSelected(false), + mbHighlighted(false), + mbExpanded(rData.mbExpanded), + mbFocused(false), + mbSelectable(rData.mbSelectable), + mbDragTarget(false) +{ +} + +SvViewDataEntry::~SvViewDataEntry() +{ +#ifdef DBG_UTIL + nVisPos = 0x12345678; +#endif +} + +void SvViewDataEntry::SetFocus( bool bFocus ) +{ + mbFocused = bFocus; +} + +void SvViewDataEntry::SetSelected( bool bSelected ) +{ + mbSelected = bSelected; + mbHighlighted = bSelected; +} + +void SvViewDataEntry::SetExpanded( bool bExpanded ) +{ + mbExpanded = bExpanded; +} + +void SvViewDataEntry::SetSelectable( bool bSelectable ) +{ + mbSelectable = bSelectable; +} + +void SvViewDataEntry::Init(size_t nSize) +{ + maItems.resize(nSize); +} + +const SvViewDataItem& SvViewDataEntry::GetItem(size_t nPos) const +{ + return maItems[nPos]; +} + +SvViewDataItem& SvViewDataEntry::GetItem(size_t nPos) +{ + return maItems[nPos]; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |