summaryrefslogtreecommitdiffstats
path: root/vcl/source/treelist
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/source/treelist')
-rw-r--r--vcl/source/treelist/headbar.cxx1302
-rw-r--r--vcl/source/treelist/iconview.cxx315
-rw-r--r--vcl/source/treelist/iconviewimpl.cxx741
-rw-r--r--vcl/source/treelist/iconviewimpl.hxx91
-rw-r--r--vcl/source/treelist/imap.cxx994
-rw-r--r--vcl/source/treelist/imap2.cxx530
-rw-r--r--vcl/source/treelist/imap3.cxx84
-rw-r--r--vcl/source/treelist/inetimg.cxx136
-rw-r--r--vcl/source/treelist/svimpbox.cxx3142
-rw-r--r--vcl/source/treelist/svlbitm.cxx550
-rw-r--r--vcl/source/treelist/svtabbx.cxx1078
-rw-r--r--vcl/source/treelist/transfer.cxx2227
-rw-r--r--vcl/source/treelist/transfer2.cxx516
-rw-r--r--vcl/source/treelist/treelist.cxx1524
-rw-r--r--vcl/source/treelist/treelistbox.cxx3612
-rw-r--r--vcl/source/treelist/treelistentry.cxx235
-rw-r--r--vcl/source/treelist/uiobject.cxx185
-rw-r--r--vcl/source/treelist/viewdataentry.cxx87
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: */