diff options
Diffstat (limited to 'svtools/source')
122 files changed, 59293 insertions, 0 deletions
diff --git a/svtools/source/brwbox/brwbox1.cxx b/svtools/source/brwbox/brwbox1.cxx new file mode 100644 index 000000000..4eafda7e9 --- /dev/null +++ b/svtools/source/brwbox/brwbox1.cxx @@ -0,0 +1,2397 @@ +/* -*- 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 <svtools/brwbox.hxx> +#include <svtools/brwhead.hxx> +#include <o3tl/numeric.hxx> +#include <o3tl/safeint.hxx> +#include "datwin.hxx" +#include <tools/debug.hxx> +#include <tools/fract.hxx> +#include <sal/log.hxx> +#include <vcl/InterimItemWindow.hxx> +#include <vcl/scrbar.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +#include <algorithm> +#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp> +#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <tools/multisel.hxx> +#include "brwimpl.hxx" + + +#define SCROLL_FLAGS (ScrollFlags::Clip | ScrollFlags::NoChildren) + +using namespace com::sun::star::accessibility::AccessibleEventId; +using namespace com::sun::star::accessibility::AccessibleTableModelChangeType; +using com::sun::star::accessibility::AccessibleTableModelChange; +using namespace ::com::sun::star::uno; +using namespace svt; + +namespace +{ + void disposeAndClearHeaderCell(::svt::BrowseBoxImpl::THeaderCellMap& _rHeaderCell) + { + ::std::for_each( + _rHeaderCell.begin(), + _rHeaderCell.end(), + ::svt::BrowseBoxImpl::THeaderCellMapFunctorDispose() + ); + _rHeaderCell.clear(); + } +} + +void BrowseBox::ConstructImpl( BrowserMode nMode ) +{ + SAL_INFO("svtools", "BrowseBox:ConstructImpl " << this ); + bMultiSelection = false; + pColSel = nullptr; + pVScroll = nullptr; + pDataWin = VclPtr<BrowserDataWin>::Create( this ).get(); + m_pImpl.reset( new ::svt::BrowseBoxImpl() ); + + InitSettings_Impl( this ); + InitSettings_Impl( pDataWin ); + + bBootstrapped = false; + m_nDataRowHeight = 0; + nTitleLines = 1; + nFirstCol = 0; + nTopRow = 0; + nCurRow = BROWSER_ENDOFSELECTION; + nCurColId = 0; + nResizeX = 0; + nMinResizeX = 0; + nDragX = 0; + nResizeCol = 0; + bResizing = false; + bSelect = false; + bSelecting = false; + bScrolling = false; + bSelectionIsVisible = false; + bNotToggleSel = false; + bRowDividerDrag = false; + bHit = false; + mbInteractiveRowHeight = false; + bHideSelect = false; + bHideCursor = TRISTATE_FALSE; + nRowCount = 0; + m_bFocusOnlyCursor = true; + m_aCursorColor = COL_TRANSPARENT; + m_nCurrentMode = BrowserMode::NONE; + nControlAreaWidth = USHRT_MAX; + uRow.nSel = BROWSER_ENDOFSELECTION; + + aHScroll->SetLineSize(1); + aHScroll->SetScrollHdl( LINK( this, BrowseBox, ScrollHdl ) ); + pDataWin->Show(); + + SetMode( nMode ); + bSelectionIsVisible = bKeepHighlight; + bHasFocus = HasChildPathFocus(); + pDataWin->nCursorHidden = + ( bHasFocus ? 0 : 1 ) + ( GetUpdateMode() ? 0 : 1 ); +} + +// we're just measuring the "real" NavigationBar +class MeasureStatusBar final : public InterimItemWindow +{ +private: + std::unique_ptr<weld::Label> m_xRecordText; + std::unique_ptr<weld::Entry> m_xAbsolute; + std::unique_ptr<weld::Label> m_xRecordOf; + std::unique_ptr<weld::Label> m_xRecordCount; +public: + MeasureStatusBar(vcl::Window *pParent) + : InterimItemWindow(pParent, "svx/ui/navigationbar.ui", "NavigationBar") + , m_xRecordText(m_xBuilder->weld_label("recordtext")) + , m_xAbsolute(m_xBuilder->weld_entry("entry-noframe")) + , m_xRecordOf(m_xBuilder->weld_label("recordof")) + , m_xRecordCount(m_xBuilder->weld_label("recordcount")) + { + vcl::Font aApplFont(Application::GetSettings().GetStyleSettings().GetToolFont()); + m_xAbsolute->set_font(aApplFont); + m_xRecordText->set_font(aApplFont); + m_xRecordOf->set_font(aApplFont); + m_xRecordCount->set_font(aApplFont); + + SetSizePixel(get_preferred_size()); + } + + virtual void dispose() override + { + m_xRecordCount.reset(); + m_xRecordOf.reset(); + m_xAbsolute.reset(); + m_xRecordText.reset(); + InterimItemWindow::dispose(); + } +}; + +tools::Long BrowseBox::GetBarHeight() const +{ + // tdf#115941 because some platforms have things like overlay scrollbars, take a max + // of a statusbar height and a scrollbar height as the control area height + + // (we can't ask the scrollbars for their size cause if we're zoomed they still have to be + // resized - which is done in UpdateScrollbars) + + return std::max(aStatusBarHeight->GetSizePixel().Height(), static_cast<tools::Long>(GetSettings().GetStyleSettings().GetScrollBarSize())); +} + +BrowseBox::BrowseBox( vcl::Window* pParent, WinBits nBits, BrowserMode nMode ) + :Control( pParent, nBits | WB_3DLOOK ) + ,DragSourceHelper( this ) + ,DropTargetHelper( this ) + ,aHScroll( VclPtr<ScrollBar>::Create(this, WB_HSCROLL) ) + // see NavigationBar ctor, here we just want to know its height + ,aStatusBarHeight(VclPtr<MeasureStatusBar>::Create(this)) +{ + ConstructImpl( nMode ); +} + +BrowseBox::~BrowseBox() +{ + disposeOnce(); +} + +void BrowseBox::DisposeAccessible() +{ + if (m_pImpl->m_pAccessible ) + { + disposeAndClearHeaderCell(m_pImpl->m_aColHeaderCellMap); + disposeAndClearHeaderCell(m_pImpl->m_aRowHeaderCellMap); + m_pImpl->m_pAccessible->dispose(); + m_pImpl->m_pAccessible = nullptr; + } +} + +void BrowseBox::dispose() +{ + SAL_INFO("svtools", "BrowseBox:dispose " << this ); + + DisposeAccessible(); + + Hide(); + pDataWin->pHeaderBar.disposeAndClear(); + pDataWin->pCornerWin.disposeAndClear(); + pDataWin.disposeAndClear(); + pVScroll.disposeAndClear(); + aHScroll.disposeAndClear(); + aStatusBarHeight.disposeAndClear(); + + // free columns-space + mvCols.clear(); + pColSel.reset(); + if ( bMultiSelection ) + delete uRow.pSel; + DragSourceHelper::dispose(); + DropTargetHelper::dispose(); + Control::dispose(); +} + + +short BrowseBox::GetCursorHideCount() const +{ + return pDataWin->nCursorHidden; +} + + +void BrowseBox::DoShowCursor() +{ + if (!pDataWin) + return; + short nHiddenCount = --pDataWin->nCursorHidden; + if (PaintCursorIfHiddenOnce()) + { + if (1 == nHiddenCount) + DrawCursor(); + } + else + { + if (0 == nHiddenCount) + DrawCursor(); + } +} + + +void BrowseBox::DoHideCursor() +{ + short nHiddenCount = ++pDataWin->nCursorHidden; + if (PaintCursorIfHiddenOnce()) + { + if (2 == nHiddenCount) + DrawCursor(); + } + else + { + if (1 == nHiddenCount) + DrawCursor(); + } +} + + +void BrowseBox::SetRealRowCount( const OUString &rRealRowCount ) +{ + pDataWin->aRealRowCount = rRealRowCount; +} + + +void BrowseBox::SetFont( const vcl::Font& rNewFont ) +{ + pDataWin->SetFont( rNewFont ); + ImpGetDataRowHeight(); +} + +const vcl::Font& BrowseBox::GetFont() const +{ + return pDataWin->GetFont(); +} + +sal_uLong BrowseBox::GetDefaultColumnWidth( const OUString& _rText ) const +{ + return pDataWin->GetTextWidth( _rText ) + pDataWin->GetTextWidth(OUString('0')) * 4; +} + + +void BrowseBox::InsertHandleColumn( sal_uLong nWidth ) +{ + +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( ColCount() == 0 || mvCols[0]->GetId() != HandleColumnId , "BrowseBox::InsertHandleColumn: there is already a handle column" ); + { + for (auto const & col : mvCols) + OSL_ENSURE( col->GetId() != HandleColumnId, "BrowseBox::InsertHandleColumn: there is a non-Handle column with handle ID" ); + } +#endif + + mvCols.insert( mvCols.begin(), std::unique_ptr<BrowserColumn>(new BrowserColumn( 0, OUString(), nWidth, GetZoom() )) ); + FreezeColumn( 0 ); + + // adjust headerbar + if ( pDataWin->pHeaderBar ) + { + pDataWin->pHeaderBar->SetPosSizePixel( + Point(nWidth, 0), + Size( GetOutputSizePixel().Width() - nWidth, GetTitleHeight() ) + ); + } + + ColumnInserted( 0 ); +} + + +void BrowseBox::InsertDataColumn( sal_uInt16 nItemId, const OUString& rText, + tools::Long nWidth, HeaderBarItemBits nBits, sal_uInt16 nPos ) +{ + + OSL_ENSURE( nItemId != HandleColumnId, "BrowseBox::InsertDataColumn: nItemId is HandleColumnId" ); + OSL_ENSURE( nItemId != BROWSER_INVALIDID, "BrowseBox::InsertDataColumn: nItemId is reserved value BROWSER_INVALIDID" ); + +#if OSL_DEBUG_LEVEL > 0 + { + for (auto const& col : mvCols) + OSL_ENSURE( col->GetId() != nItemId, "BrowseBox::InsertDataColumn: duplicate column Id" ); + } +#endif + + if ( nPos < mvCols.size() ) + { + mvCols.emplace( mvCols.begin() + nPos, new BrowserColumn( nItemId, rText, nWidth, GetZoom() ) ); + } + else + { + mvCols.emplace_back( new BrowserColumn( nItemId, rText, nWidth, GetZoom() ) ); + } + if ( nCurColId == 0 ) + nCurColId = nItemId; + + if ( pDataWin->pHeaderBar ) + { + // Handle column not in the header bar + sal_uInt16 nHeaderPos = nPos; + if (nHeaderPos != HEADERBAR_APPEND && GetColumnId(0) == HandleColumnId ) + nHeaderPos--; + pDataWin->pHeaderBar->InsertItem( + nItemId, rText, nWidth, nBits, nHeaderPos ); + } + ColumnInserted( nPos ); +} + +sal_uInt16 BrowseBox::ToggleSelectedColumn() +{ + sal_uInt16 nSelectedColId = BROWSER_INVALIDID; + if ( pColSel && pColSel->GetSelectCount() ) + { + DoHideCursor(); + ToggleSelection(); + tools::Long nSelected = pColSel->FirstSelected(); + if (nSelected != static_cast<tools::Long>(SFX_ENDOFSELECTION)) + nSelectedColId = mvCols[nSelected]->GetId(); + pColSel->SelectAll(false); + } + return nSelectedColId; +} + +void BrowseBox::SetToggledSelectedColumn(sal_uInt16 _nSelectedColumnId) +{ + if ( pColSel && _nSelectedColumnId != BROWSER_INVALIDID ) + { + pColSel->Select( GetColumnPos( _nSelectedColumnId ) ); + ToggleSelection(); + SAL_INFO("svtools", "BrowseBox::SetToggledSelectedColumn " << this ); + DoShowCursor(); + } +} + +void BrowseBox::FreezeColumn( sal_uInt16 nItemId ) +{ + // get the position in the current array + size_t nItemPos = GetColumnPos( nItemId ); + if ( nItemPos >= mvCols.size() ) + // not available! + return; + + // doesn't the state change? + if ( mvCols[ nItemPos ]->IsFrozen() ) + return; + + // remark the column selection + sal_uInt16 nSelectedColId = ToggleSelectedColumn(); + + // to be moved? + if ( nItemPos != 0 && !mvCols[ nItemPos-1 ]->IsFrozen() ) + { + // move to the right of the last frozen column + sal_uInt16 nFirstScrollable = FrozenColCount(); + std::unique_ptr<BrowserColumn> pColumn = std::move(mvCols[ nItemPos ]); + mvCols.erase( mvCols.begin() + nItemPos ); + nItemPos = nFirstScrollable; + mvCols.insert( mvCols.begin() + nItemPos, std::move(pColumn) ); + } + + // adjust the number of the first scrollable and visible column + if ( nFirstCol <= nItemPos ) + nFirstCol = nItemPos + 1; + + // toggle the freeze-state of the column + mvCols[ nItemPos ]->Freeze(); + + // align the scrollbar-range + UpdateScrollbars(); + + // repaint + Control::Invalidate(); + pDataWin->Invalidate(); + + // remember the column selection + SetToggledSelectedColumn(nSelectedColId); +} + + +void BrowseBox::SetColumnPos( sal_uInt16 nColumnId, sal_uInt16 nPos ) +{ + // never set pos of the handle column + if ( nColumnId == HandleColumnId ) + return; + + // get the position in the current array + sal_uInt16 nOldPos = GetColumnPos( nColumnId ); + if ( nOldPos >= mvCols.size() ) + // not available! + return; + + // does the state change? + if (nOldPos == nPos) + return; + + // remark the column selection + sal_uInt16 nSelectedColId = ToggleSelectedColumn(); + + // determine old column area + Size aDataWinSize( pDataWin->GetSizePixel() ); + if ( pDataWin->pHeaderBar ) + aDataWinSize.AdjustHeight(pDataWin->pHeaderBar->GetSizePixel().Height() ); + + tools::Rectangle aFromRect( GetFieldRect( nColumnId) ); + aFromRect.AdjustRight(2*MIN_COLUMNWIDTH ); + + sal_uInt16 nNextPos = nOldPos + 1; + if ( nOldPos > nPos ) + nNextPos = nOldPos - 1; + + BrowserColumn *pNextCol = mvCols[ nNextPos ].get(); + tools::Rectangle aNextRect(GetFieldRect( pNextCol->GetId() )); + + // move column internally + { + std::unique_ptr<BrowserColumn> pTemp = std::move(mvCols[nOldPos]); + mvCols.erase( mvCols.begin() + nOldPos ); + mvCols.insert( mvCols.begin() + nPos, std::move(pTemp) ); + } + + // determine new column area + tools::Rectangle aToRect( GetFieldRect( nColumnId ) ); + aToRect.AdjustRight(2*MIN_COLUMNWIDTH ); + + // do scroll, let redraw + if( pDataWin->GetBackground().IsScrollable() ) + { + tools::Long nScroll = -aFromRect.GetWidth(); + tools::Rectangle aScrollArea; + if ( nOldPos > nPos ) + { + tools::Long nFrozenWidth = GetFrozenWidth(); + if ( aToRect.Left() < nFrozenWidth ) + aToRect.SetLeft( nFrozenWidth ); + aScrollArea = tools::Rectangle(Point(aToRect.Left(),0), + Point(aNextRect.Right(),aDataWinSize.Height())); + nScroll *= -1; // reverse direction + } + else + aScrollArea = tools::Rectangle(Point(aNextRect.Left(),0), + Point(aToRect.Right(),aDataWinSize.Height())); + + pDataWin->Scroll( nScroll, 0, aScrollArea ); + aToRect.SetTop( 0 ); + aToRect.SetBottom( aScrollArea.Bottom() ); + Invalidate( aToRect ); + } + else + pDataWin->Window::Invalidate( InvalidateFlags::NoChildren ); + + // adjust header bar positions + if ( pDataWin->pHeaderBar ) + { + sal_uInt16 nNewPos = nPos; + if ( GetColumnId(0) == HandleColumnId ) + --nNewPos; + pDataWin->pHeaderBar->MoveItem(nColumnId,nNewPos); + } + // remember the column selection + SetToggledSelectedColumn(nSelectedColId); + + if ( !isAccessibleAlive() ) + return; + + commitTableEvent( + TABLE_MODEL_CHANGED, + Any( AccessibleTableModelChange( + COLUMNS_REMOVED, + -1, + -1, + nOldPos, + nOldPos + ) + ), + Any() + ); + + commitTableEvent( + TABLE_MODEL_CHANGED, + Any( AccessibleTableModelChange( + COLUMNS_INSERTED, + -1, + -1, + nPos, + nPos + ) + ), + Any() + ); + +} + + +void BrowseBox::SetColumnTitle( sal_uInt16 nItemId, const OUString& rTitle ) +{ + + // never set title of the handle-column + if ( nItemId == HandleColumnId ) + return; + + // get the position in the current array + sal_uInt16 nItemPos = GetColumnPos( nItemId ); + if ( nItemPos >= mvCols.size() ) + // not available! + return; + + // does the state change? + BrowserColumn *pCol = mvCols[ nItemPos ].get(); + if ( pCol->Title() == rTitle ) + return; + + OUString sOld(pCol->Title()); + + pCol->Title() = rTitle; + + // adjust headerbar column + if ( pDataWin->pHeaderBar ) + pDataWin->pHeaderBar->SetItemText( nItemId, rTitle ); + else + { + // redraw visible columns + if ( GetUpdateMode() && ( pCol->IsFrozen() || nItemPos > nFirstCol ) ) + Invalidate( tools::Rectangle( Point(0,0), + Size( GetOutputSizePixel().Width(), GetTitleHeight() ) ) ); + } + + if ( isAccessibleAlive() ) + { + commitTableEvent( TABLE_COLUMN_DESCRIPTION_CHANGED, + Any( rTitle ), + Any( sOld ) + ); + } +} + + +void BrowseBox::SetColumnWidth( sal_uInt16 nItemId, sal_uLong nWidth ) +{ + + // get the position in the current array + size_t nItemPos = GetColumnPos( nItemId ); + if ( nItemPos >= mvCols.size() ) + return; + + // does the state change? + if ( nWidth < LONG_MAX && mvCols[ nItemPos ]->Width() == nWidth ) + return; + + tools::Long nOldWidth = mvCols[ nItemPos ]->Width(); + + // adjust last column, if necessary + if ( IsVisible() && nItemPos == mvCols.size() - 1 ) + { + tools::Long nMaxWidth = pDataWin->GetSizePixel().Width(); + nMaxWidth -= pDataWin->bAutoSizeLastCol + ? GetFieldRect(nItemId).Left() + : GetFrozenWidth(); + if ( pDataWin->bAutoSizeLastCol || nWidth > o3tl::make_unsigned(nMaxWidth) ) + { + nWidth = nMaxWidth > 16 ? nMaxWidth : nOldWidth; + } + } + + // OV + // In AutoSizeLastColumn(), we call SetColumnWidth with nWidth==0xffff. + // Thus, check here, if the width has actually changed. + if( static_cast<sal_uLong>(nOldWidth) == nWidth ) + return; + + // do we want to display the change immediately? + bool bUpdate = GetUpdateMode() && + ( mvCols[ nItemPos ]->IsFrozen() || nItemPos >= nFirstCol ); + + if ( bUpdate ) + { + // Selection hidden + DoHideCursor(); + ToggleSelection(); + //!pDataWin->Update(); + //!Control::Update(); + } + + // set width + mvCols[ nItemPos ]->SetWidth(nWidth, GetZoom()); + + // scroll and invalidate + if ( bUpdate ) + { + // get X-Pos of the column changed + tools::Long nX = 0; + for ( size_t nCol = 0; nCol < nItemPos; ++nCol ) + { + BrowserColumn *pCol = mvCols[ nCol ].get(); + if ( pCol->IsFrozen() || nCol >= nFirstCol ) + nX += pCol->Width(); + } + + // actually scroll+invalidate + pDataWin->GetOutDev()->SetClipRegion(); + bool bSelVis = bSelectionIsVisible; + bSelectionIsVisible = false; + if( GetBackground().IsScrollable() ) + { + + tools::Rectangle aScrRect( nX + std::min( static_cast<sal_uLong>(nOldWidth), nWidth ), 0, + GetSizePixel().Width() , // the header is longer than the datawin + pDataWin->GetPosPixel().Y() - 1 ); + Control::Scroll( nWidth-nOldWidth, 0, aScrRect, SCROLL_FLAGS ); + aScrRect.SetBottom( pDataWin->GetSizePixel().Height() ); + pDataWin->Scroll( nWidth-nOldWidth, 0, aScrRect, SCROLL_FLAGS ); + tools::Rectangle aInvRect( nX, 0, nX + std::max( nWidth, static_cast<sal_uLong>(nOldWidth) ), USHRT_MAX ); + Control::Invalidate( aInvRect, InvalidateFlags::NoChildren ); + pDataWin->Invalidate( aInvRect ); + } + else + { + Control::Invalidate( InvalidateFlags::NoChildren ); + pDataWin->Window::Invalidate( InvalidateFlags::NoChildren ); + } + + + //!pDataWin->Update(); + //!Control::Update(); + bSelectionIsVisible = bSelVis; + ToggleSelection(); + DoShowCursor(); + } + UpdateScrollbars(); + + // adjust headerbar column + if ( pDataWin->pHeaderBar ) + pDataWin->pHeaderBar->SetItemSize( + nItemId ? nItemId : USHRT_MAX - 1, nWidth ); + + // adjust last column + if ( nItemPos != mvCols.size() - 1 ) + AutoSizeLastColumn(); +} + + +void BrowseBox::AutoSizeLastColumn() +{ + if ( pDataWin->bAutoSizeLastCol && + pDataWin->GetUpdateMode() ) + { + sal_uInt16 nId = GetColumnId( static_cast<sal_uInt16>(mvCols.size()) - 1 ); + SetColumnWidth( nId, LONG_MAX ); + ColumnResized( nId ); + } +} + + +void BrowseBox::RemoveColumn( sal_uInt16 nItemId ) +{ + + // get column position + sal_uInt16 nPos = GetColumnPos(nItemId); + if ( nPos >= ColCount() ) + // not available + return; + + // correct column selection + if ( pColSel ) + pColSel->Remove( nPos ); + + // correct column cursor + if ( nCurColId == nItemId ) + nCurColId = 0; + + // delete column + mvCols.erase( mvCols.begin() + nPos ); + if ( nFirstCol >= nPos && nFirstCol > FrozenColCount() ) + { + OSL_ENSURE(nFirstCol > 0,"FirstCol must be greater zero!"); + --nFirstCol; + } + + // handlecolumn not in headerbar + if (nItemId) + { + if ( pDataWin->pHeaderBar ) + pDataWin->pHeaderBar->RemoveItem( nItemId ); + } + else + { + // adjust headerbar + if ( pDataWin->pHeaderBar ) + { + pDataWin->pHeaderBar->SetPosSizePixel( + Point(0, 0), + Size( GetOutputSizePixel().Width(), GetTitleHeight() ) + ); + } + } + + // correct vertical scrollbar + UpdateScrollbars(); + + // trigger repaint, if necessary + if ( GetUpdateMode() ) + { + pDataWin->Invalidate(); + Control::Invalidate(); + if ( pDataWin->bAutoSizeLastCol && nPos ==ColCount() ) + SetColumnWidth( GetColumnId( nPos - 1 ), LONG_MAX ); + } + + if ( !isAccessibleAlive() ) + return; + + commitTableEvent( + TABLE_MODEL_CHANGED, + Any( AccessibleTableModelChange(COLUMNS_REMOVED, + -1, + -1, + nPos, + nPos + ) + ), + Any() + ); + + commitHeaderBarEvent( + CHILD, + Any(), + Any( CreateAccessibleColumnHeader( nPos ) ), + true + ); +} + + +void BrowseBox::RemoveColumns() +{ + size_t nOldCount = mvCols.size(); + + // remove all columns + mvCols.clear(); + + // correct column selection + if ( pColSel ) + { + pColSel->SelectAll(false); + pColSel->SetTotalRange( Range( 0, 0 ) ); + } + + // correct column cursor + nCurColId = 0; + nFirstCol = 0; + + if ( pDataWin->pHeaderBar ) + pDataWin->pHeaderBar->Clear( ); + + // correct vertical scrollbar + UpdateScrollbars(); + + // trigger repaint if necessary + if ( GetUpdateMode() ) + { + pDataWin->Invalidate(); + Control::Invalidate(); + } + + if ( !isAccessibleAlive() ) + return; + + if ( mvCols.size() == nOldCount ) + return; + + // all columns should be removed, so we remove the column header bar and append it again + // to avoid to notify every column remove + commitBrowseBoxEvent( + CHILD, + Any(), + Any(m_pImpl->getAccessibleHeaderBar(AccessibleBrowseBoxObjType::ColumnHeaderBar)) + ); + + // and now append it again + commitBrowseBoxEvent( + CHILD, + Any(m_pImpl->getAccessibleHeaderBar(AccessibleBrowseBoxObjType::ColumnHeaderBar)), + Any() + ); + + // notify a table model change + commitTableEvent( + TABLE_MODEL_CHANGED, + Any ( AccessibleTableModelChange( COLUMNS_REMOVED, + -1, + -1, + 0, + nOldCount + ) + ), + Any() + ); +} + + +OUString BrowseBox::GetColumnTitle( sal_uInt16 nId ) const +{ + + sal_uInt16 nItemPos = GetColumnPos( nId ); + if ( nItemPos >= mvCols.size() ) + return OUString(); + return mvCols[ nItemPos ]->Title(); +} + +sal_Int32 BrowseBox::GetRowCount() const +{ + return nRowCount; +} + +sal_uInt16 BrowseBox::ColCount() const +{ + return static_cast<sal_uInt16>(mvCols.size()); +} + +tools::Long BrowseBox::ImpGetDataRowHeight() const +{ + BrowseBox *pThis = const_cast<BrowseBox*>(this); + pThis->m_nDataRowHeight = pThis->CalcReverseZoom(pDataWin->GetTextHeight() + 4); + pThis->Resize(); + pDataWin->Invalidate(); + return m_nDataRowHeight; +} + +void BrowseBox::SetDataRowHeight( tools::Long nPixel ) +{ + + m_nDataRowHeight = CalcReverseZoom(nPixel); + Resize(); + pDataWin->Invalidate(); +} + +void BrowseBox::SetTitleLines( sal_uInt16 nLines ) +{ + + nTitleLines = nLines; +} + +sal_Int32 BrowseBox::ScrollColumns( sal_Int32 nCols ) +{ + + if ( nFirstCol + nCols < 0 || + o3tl::make_unsigned(nFirstCol + nCols) >= mvCols.size() ) + return 0; + + // implicitly hides cursor while scrolling + StartScroll(); + bScrolling = true; + bool bScrollable = pDataWin->GetBackground().IsScrollable(); + bool bInvalidateView = false; + + // scrolling one column to the right? + if ( nCols == 1 ) + { + // update internal value and scrollbar + ++nFirstCol; + aHScroll->SetThumbPos( nFirstCol - FrozenColCount() ); + + if ( !bScrollable ) + { + bInvalidateView = true; + } + else + { + tools::Long nDelta = mvCols[ nFirstCol-1 ]->Width(); + tools::Long nFrozenWidth = GetFrozenWidth(); + + tools::Rectangle aScrollRect( Point( nFrozenWidth + nDelta, 0 ), + Size ( GetOutputSizePixel().Width() - nFrozenWidth - nDelta, + GetTitleHeight() - 1 + ) ); + + // scroll the header bar area (if there is no dedicated HeaderBar control) + if ( !pDataWin->pHeaderBar && nTitleLines ) + { + // actually scroll + Scroll( -nDelta, 0, aScrollRect, SCROLL_FLAGS ); + + // invalidate the area of the column which was scrolled out to the left hand side + tools::Rectangle aInvalidateRect( aScrollRect ); + aInvalidateRect.SetLeft( nFrozenWidth ); + aInvalidateRect.SetRight( nFrozenWidth + nDelta - 1 ); + Invalidate( aInvalidateRect ); + } + + // scroll the data-area + aScrollRect.SetBottom( pDataWin->GetOutputSizePixel().Height() ); + + // actually scroll + pDataWin->Scroll( -nDelta, 0, aScrollRect, SCROLL_FLAGS ); + + // invalidate the area of the column which was scrolled out to the left hand side + aScrollRect.SetLeft( nFrozenWidth ); + aScrollRect.SetRight( nFrozenWidth + nDelta - 1 ); + pDataWin->Invalidate( aScrollRect ); + } + } + + // scrolling one column to the left? + else if ( nCols == -1 ) + { + --nFirstCol; + aHScroll->SetThumbPos( nFirstCol - FrozenColCount() ); + + if ( !bScrollable ) + { + bInvalidateView = true; + } + else + { + tools::Long nDelta = mvCols[ nFirstCol ]->Width(); + tools::Long nFrozenWidth = GetFrozenWidth(); + + tools::Rectangle aScrollRect( Point( nFrozenWidth, 0 ), + Size ( GetOutputSizePixel().Width() - nFrozenWidth, + GetTitleHeight() - 1 + ) ); + + // scroll the header bar area (if there is no dedicated HeaderBar control) + if ( !pDataWin->pHeaderBar && nTitleLines ) + { + Scroll( nDelta, 0, aScrollRect, SCROLL_FLAGS ); + } + + // scroll the data-area + aScrollRect.SetBottom( pDataWin->GetOutputSizePixel().Height() ); + pDataWin->Scroll( nDelta, 0, aScrollRect, SCROLL_FLAGS ); + } + } + else + { + if ( GetUpdateMode() ) + { + Invalidate( tools::Rectangle( + Point( GetFrozenWidth(), 0 ), + Size( GetOutputSizePixel().Width(), GetTitleHeight() ) ) ); + pDataWin->Invalidate( tools::Rectangle( + Point( GetFrozenWidth(), 0 ), + pDataWin->GetSizePixel() ) ); + } + + nFirstCol = nFirstCol + static_cast<sal_uInt16>(nCols); + aHScroll->SetThumbPos( nFirstCol - FrozenColCount() ); + } + + // adjust external headerbar, if necessary + if ( pDataWin->pHeaderBar ) + { + tools::Long nWidth = 0; + for ( size_t nCol = 0; + nCol < mvCols.size() && nCol < nFirstCol; + ++nCol ) + { + // not the handle column + if ( mvCols[ nCol ]->GetId() ) + nWidth += mvCols[ nCol ]->Width(); + } + + pDataWin->pHeaderBar->SetOffset( nWidth ); + } + + if( bInvalidateView ) + { + Control::Invalidate( InvalidateFlags::NoChildren ); + pDataWin->Window::Invalidate( InvalidateFlags::NoChildren ); + } + + // implicitly show cursor after scrolling + if ( nCols ) + { + pDataWin->Update(); + PaintImmediately(); + } + bScrolling = false; + EndScroll(); + + return nCols; +} + + +sal_Int32 BrowseBox::ScrollRows( sal_Int32 nRows ) +{ + // compute new top row + sal_Int32 nTmpMin = std::min( static_cast<sal_Int32>(nTopRow + nRows), static_cast<sal_Int32>(nRowCount - 1) ); + + sal_Int32 nNewTopRow = std::max<sal_Int32>( nTmpMin, 0 ); + + if ( nNewTopRow == nTopRow ) + return 0; + + sal_uInt16 nVisibleRows = + static_cast<sal_uInt16>(pDataWin->GetOutputSizePixel().Height() / GetDataRowHeight() + 1); + + VisibleRowsChanged(nNewTopRow, nVisibleRows); + + // compute new top row again (nTopRow might have changed!) + nTmpMin = std::min( static_cast<tools::Long>(nTopRow + nRows), static_cast<tools::Long>(nRowCount - 1) ); + + nNewTopRow = std::max<tools::Long>( nTmpMin, 0 ); + + StartScroll(); + + // scroll area on screen and/or repaint + tools::Long nDeltaY = GetDataRowHeight() * ( nNewTopRow - nTopRow ); + sal_Int32 nOldTopRow = nTopRow; + nTopRow = nNewTopRow; + + if ( GetUpdateMode() ) + { + pVScroll->SetRange( Range( 0L, nRowCount ) ); + pVScroll->SetThumbPos( nTopRow ); + + if( pDataWin->GetBackground().IsScrollable() && + std::abs( nDeltaY ) > 0 && + std::abs( nDeltaY ) < pDataWin->GetSizePixel().Height() ) + { + pDataWin->Scroll( 0, static_cast<short>(-nDeltaY), SCROLL_FLAGS ); + } + else + pDataWin->Invalidate(); + + if ( nTopRow - nOldTopRow ) + pDataWin->Update(); + } + + EndScroll(); + + return nTopRow - nOldTopRow; +} + + +void BrowseBox::RowModified( sal_Int32 nRow, sal_uInt16 nColId ) +{ + + if ( !GetUpdateMode() ) + return; + + tools::Rectangle aRect; + if ( nColId == BROWSER_INVALIDID ) + // invalidate the whole row + aRect = tools::Rectangle( Point( 0, (nRow-nTopRow) * GetDataRowHeight() ), + Size( pDataWin->GetSizePixel().Width(), GetDataRowHeight() ) ); + else + { + // invalidate the specific field + aRect = GetFieldRectPixel( nRow, nColId, false ); + } + pDataWin->Invalidate( aRect ); +} + + +void BrowseBox::Clear() +{ + // adjust the total number of rows + DoHideCursor(); + sal_Int32 nOldRowCount = nRowCount; + nRowCount = 0; + if(bMultiSelection) + { + assert(uRow.pSel); + uRow.pSel->Reset(); + } + else + uRow.nSel = BROWSER_ENDOFSELECTION; + nCurRow = BROWSER_ENDOFSELECTION; + nTopRow = 0; + nCurColId = 0; + + // nFirstCol may not be reset, else the scrolling code will become confused. + // nFirstCol may only be changed when adding or deleting columns + // nFirstCol = 0; -> wrong! + aHScroll->SetThumbPos( 0 ); + pVScroll->SetThumbPos( 0 ); + + Invalidate(); + UpdateScrollbars(); + SetNoSelection(); + DoShowCursor(); + CursorMoved(); + + if ( !isAccessibleAlive() ) + return; + + // all rows should be removed, so we remove the row header bar and append it again + // to avoid to notify every row remove + if ( nOldRowCount == nRowCount ) + return; + + commitBrowseBoxEvent( + CHILD, + Any(), + Any( m_pImpl->getAccessibleHeaderBar( AccessibleBrowseBoxObjType::RowHeaderBar ) ) + ); + + // and now append it again + commitBrowseBoxEvent( + CHILD, + Any( m_pImpl->getAccessibleHeaderBar( AccessibleBrowseBoxObjType::RowHeaderBar ) ), + Any() + ); + + // notify a table model change + commitTableEvent( + TABLE_MODEL_CHANGED, + Any( AccessibleTableModelChange(ROWS_REMOVED, + 0, + nOldRowCount, + -1, + -1) + ), + Any() + ); +} + +void BrowseBox::RowInserted( sal_Int32 nRow, sal_Int32 nNumRows, bool bDoPaint, bool bKeepSelection ) +{ + + if (nRow < 0) + nRow = 0; + else if (nRow > nRowCount) // maximal = nRowCount + nRow = nRowCount; + + if ( nNumRows <= 0 ) + return; + + // adjust total row count + bool bLastRow = nRow >= nRowCount; + nRowCount += nNumRows; + + DoHideCursor(); + + // must we paint the new rows? + sal_Int32 nOldCurRow = nCurRow; + Size aSz = pDataWin->GetOutputSizePixel(); + if ( bDoPaint && nRow >= nTopRow && + nRow <= nTopRow + aSz.Height() / GetDataRowHeight() ) + { + tools::Long nY = (nRow-nTopRow) * GetDataRowHeight(); + if ( !bLastRow ) + { + // scroll down the rows behind the new row + pDataWin->GetOutDev()->SetClipRegion(); + if( pDataWin->GetBackground().IsScrollable() ) + { + pDataWin->Scroll( 0, GetDataRowHeight() * nNumRows, + tools::Rectangle( Point( 0, nY ), + Size( aSz.Width(), aSz.Height() - nY ) ), + SCROLL_FLAGS ); + } + else + pDataWin->Window::Invalidate( InvalidateFlags::NoChildren ); + } + else + // scroll would cause a repaint, so we must explicitly invalidate + pDataWin->Invalidate( tools::Rectangle( Point( 0, nY ), + Size( aSz.Width(), nNumRows * GetDataRowHeight() ) ) ); + } + + // correct top row if necessary + if ( nRow < nTopRow ) + nTopRow += nNumRows; + + // adjust the selection + if ( bMultiSelection ) + uRow.pSel->Insert( nRow, nNumRows ); + else if ( uRow.nSel != BROWSER_ENDOFSELECTION && nRow <= uRow.nSel ) + uRow.nSel += nNumRows; + + // adjust the cursor + if ( nCurRow == BROWSER_ENDOFSELECTION ) + GoToRow( 0, false, bKeepSelection ); + else if ( nRow <= nCurRow ) + { + nCurRow += nNumRows; + GoToRow( nCurRow, false, bKeepSelection ); + } + + // adjust the vertical scrollbar + if ( bDoPaint ) + { + UpdateScrollbars(); + AutoSizeLastColumn(); + } + + DoShowCursor(); + // notify accessible that rows were inserted + if ( isAccessibleAlive() ) + { + commitTableEvent( + TABLE_MODEL_CHANGED, + Any( AccessibleTableModelChange( + ROWS_INSERTED, + nRow, + nRow + nNumRows, + -1, + -1 + ) + ), + Any() + ); + + for (tools::Long i = nRow+1 ; i <= nRowCount ; ++i) + { + commitHeaderBarEvent( + CHILD, + Any( CreateAccessibleRowHeader( i ) ), + Any(), + false + ); + } + } + + if ( nCurRow != nOldCurRow ) + CursorMoved(); + + DBG_ASSERT(nRowCount > 0,"BrowseBox: nRowCount <= 0"); + DBG_ASSERT(nCurRow >= 0,"BrowseBox: nCurRow < 0"); + DBG_ASSERT(nCurRow < nRowCount,"nCurRow >= nRowCount"); +} + + +void BrowseBox::RowRemoved( sal_Int32 nRow, sal_Int32 nNumRows, bool bDoPaint ) +{ + + if ( nRow < 0 ) + nRow = 0; + else if ( nRow >= nRowCount ) + nRow = nRowCount - 1; + + if ( nNumRows <= 0 ) + return; + + if ( nRowCount <= 0 ) + return; + + if ( bDoPaint ) + { + // hide cursor and selection + SAL_INFO("svtools", "BrowseBox::HideCursor " << this ); + ToggleSelection(); + DoHideCursor(); + } + + // adjust total row count + nRowCount -= nNumRows; + if (nRowCount < 0) nRowCount = 0; + sal_Int32 nOldCurRow = nCurRow; + + // adjust the selection + if ( bMultiSelection ) + // uRow.pSel->Remove( nRow, nNumRows ); + for ( tools::Long i = 0; i < nNumRows; i++ ) + uRow.pSel->Remove( nRow ); + else if ( nRow < uRow.nSel && uRow.nSel >= nNumRows ) + uRow.nSel -= nNumRows; + else if ( nRow <= uRow.nSel ) + uRow.nSel = BROWSER_ENDOFSELECTION; + + // adjust the cursor + if ( nRowCount == 0 ) // don't compare nRowCount with nNumRows as nNumRows already was subtracted from nRowCount + nCurRow = BROWSER_ENDOFSELECTION; + else if ( nRow < nCurRow ) + { + nCurRow -= std::min( nCurRow - nRow, nNumRows ); + // with the above nCurRow points a) to the first row after the removed block or b) to the same line + // as before, but moved up nNumRows + // case a) needs an additional correction if the last n lines were deleted, as 'the first row after the + // removed block' is an invalid position then + // FS - 09/28/99 - 68429 + if (nCurRow == nRowCount) + --nCurRow; + } + else if( nRow == nCurRow && nCurRow == nRowCount ) + nCurRow = nRowCount-1; + + // is the deleted row visible? + Size aSz = pDataWin->GetOutputSizePixel(); + if ( nRow >= nTopRow && + nRow <= nTopRow + aSz.Height() / GetDataRowHeight() ) + { + if ( bDoPaint ) + { + // scroll up the rows behind the deleted row + // if there are Rows behind + if (nRow < nRowCount) + { + tools::Long nY = (nRow-nTopRow) * GetDataRowHeight(); + pDataWin->GetOutDev()->SetClipRegion(); + if( pDataWin->GetBackground().IsScrollable() ) + { + pDataWin->Scroll( 0, - static_cast<short>(GetDataRowHeight()) * nNumRows, + tools::Rectangle( Point( 0, nY ), Size( aSz.Width(), + aSz.Height() - nY + nNumRows*GetDataRowHeight() ) ), + SCROLL_FLAGS ); + } + else + pDataWin->Window::Invalidate( InvalidateFlags::NoChildren ); + } + else + { + // Repaint the Rect of the deleted row + tools::Rectangle aRect( + Point( 0, (nRow-nTopRow)*GetDataRowHeight() ), + Size( pDataWin->GetSizePixel().Width(), + nNumRows * GetDataRowHeight() ) ); + pDataWin->Invalidate( aRect ); + } + } + } + // is the deleted row above of the visible area? + else if ( nRow < nTopRow ) + nTopRow = nTopRow >= nNumRows ? nTopRow-nNumRows : 0; + + if ( bDoPaint ) + { + // reshow cursor and selection + ToggleSelection(); + SAL_INFO("svtools", "BrowseBox::ShowCursor " << this ); + DoShowCursor(); + + // adjust the vertical scrollbar + UpdateScrollbars(); + AutoSizeLastColumn(); + } + + if ( isAccessibleAlive() ) + { + if ( nRowCount == 0 ) + { + // all columns should be removed, so we remove the column header bar and append it again + // to avoid to notify every column remove + commitBrowseBoxEvent( + CHILD, + Any(), + Any( m_pImpl->getAccessibleHeaderBar( AccessibleBrowseBoxObjType::RowHeaderBar ) ) + ); + + // and now append it again + commitBrowseBoxEvent( + CHILD, + Any(m_pImpl->getAccessibleHeaderBar(AccessibleBrowseBoxObjType::RowHeaderBar)), + Any() + ); + commitBrowseBoxEvent( + CHILD, + Any(), + Any( m_pImpl->getAccessibleTable() ) + ); + + // and now append it again + commitBrowseBoxEvent( + CHILD, + Any( m_pImpl->getAccessibleTable() ), + Any() + ); + } + else + { + commitTableEvent( + TABLE_MODEL_CHANGED, + Any( AccessibleTableModelChange( + ROWS_REMOVED, + nRow, + nRow + nNumRows, + -1, + -1 + ) + ), + Any() + ); + + for (tools::Long i = nRow+1 ; i <= (nRow+nNumRows) ; ++i) + { + commitHeaderBarEvent( + CHILD, + Any(), + Any( CreateAccessibleRowHeader( i ) ), + false + ); + } + } + } + + if ( nOldCurRow != nCurRow ) + CursorMoved(); + + DBG_ASSERT(nRowCount >= 0,"BrowseBox: nRowCount < 0"); + DBG_ASSERT(nCurRow >= 0 || nRowCount == 0,"BrowseBox: nCurRow < 0 && nRowCount != 0"); + DBG_ASSERT(nCurRow < nRowCount,"nCurRow >= nRowCount"); +} + + +bool BrowseBox::GoToRow( sal_Int32 nRow) +{ + return GoToRow(nRow, false); +} + + +bool BrowseBox::GoToRow( sal_Int32 nRow, bool bRowColMove, bool bKeepSelection ) +{ + sal_Int32 nOldCurRow = nCurRow; + + // nothing to do? + if ( nRow == nCurRow && ( bMultiSelection || uRow.nSel == nRow ) ) + return true; + + // out of range? + if ( nRow < 0 || nRow >= nRowCount ) + return false; + + // not allowed? + if ( !bRowColMove && !IsCursorMoveAllowed( nRow, nCurColId ) ) + return false; + + // compute the last visible row + Size aSz( pDataWin->GetSizePixel() ); + sal_uInt16 nVisibleRows = sal_uInt16( aSz.Height() / GetDataRowHeight() - 1 ); + sal_Int32 nLastRow = nTopRow + nVisibleRows; + + // suspend Updates + pDataWin->EnterUpdateLock(); + + // remove old highlight, if necessary + if ( !bMultiSelection && !bKeepSelection ) + ToggleSelection(); + DoHideCursor(); + + // must we scroll? + bool bWasVisible = bSelectionIsVisible; + if (! bMultiSelection) + { + if( !bKeepSelection ) + bSelectionIsVisible = false; + } + if ( nRow < nTopRow ) + ScrollRows( nRow - nTopRow ); + else if ( nRow > nLastRow ) + ScrollRows( nRow - nLastRow ); + bSelectionIsVisible = bWasVisible; + + // adjust cursor (selection) and thumb + if ( GetUpdateMode() ) + pVScroll->SetThumbPos( nTopRow ); + + // relative positioning (because nCurRow might have changed in the meantime)! + if (nCurRow != BROWSER_ENDOFSELECTION ) + nCurRow = nCurRow + (nRow - nOldCurRow); + + // make sure that the current position is valid + if (nCurRow == BROWSER_ENDOFSELECTION && nRowCount > 0) + nCurRow = 0; + else if ( nCurRow >= nRowCount ) + nCurRow = nRowCount - 1; + aSelRange = Range( nCurRow, nCurRow ); + + // display new highlight if necessary + if ( !bMultiSelection && !bKeepSelection ) + uRow.nSel = nRow; + + // resume Updates + pDataWin->LeaveUpdateLock(); + + // Cursor+Highlight + if ( !bMultiSelection && !bKeepSelection) + ToggleSelection(); + DoShowCursor(); + if ( !bRowColMove && nOldCurRow != nCurRow ) + CursorMoved(); + + if ( !bMultiSelection && !bKeepSelection ) + { + if ( !bSelecting ) + Select(); + else + bSelect = true; + } + return true; +} + + +bool BrowseBox::GoToColumnId( sal_uInt16 nColId) +{ + return GoToColumnId(nColId, true); +} + + +bool BrowseBox::GoToColumnId( sal_uInt16 nColId, bool bMakeVisible, bool bRowColMove) +{ + if (!bColumnCursor) + return false; + + // allowed? + if (!bRowColMove && !IsCursorMoveAllowed( nCurRow, nColId ) ) + return false; + + if ( nColId != nCurColId || (bMakeVisible && !IsFieldVisible(nCurRow, nColId, true))) + { + sal_uInt16 nNewPos = GetColumnPos(nColId); + BrowserColumn* pColumn = (nNewPos < mvCols.size()) ? mvCols[ nNewPos ].get() : nullptr; + DBG_ASSERT( pColumn, "no column object - invalid id?" ); + if ( !pColumn ) + return false; + + DoHideCursor(); + nCurColId = nColId; + + bool bScrolled = false; + + sal_uInt16 nFirstPos = nFirstCol; + sal_uInt16 nWidth = static_cast<sal_uInt16>(pColumn->Width()); + sal_uInt16 nLastPos = GetColumnAtXPosPixel( + pDataWin->GetSizePixel().Width()-nWidth ); + sal_uInt16 nFrozen = FrozenColCount(); + if ( bMakeVisible && nLastPos && + nNewPos >= nFrozen && ( nNewPos < nFirstPos || nNewPos > nLastPos ) ) + { + if ( nNewPos < nFirstPos ) + ScrollColumns( nNewPos-nFirstPos ); + else if ( nNewPos > nLastPos ) + ScrollColumns( nNewPos-nLastPos ); + bScrolled = true; + } + + DoShowCursor(); + if (!bRowColMove) + { + //try to move to nCurRow, nColId + CursorMoveAttempt aAttempt(nCurRow, nColId, bScrolled); + //Detect if we are already in a call to BrowseBox::GoToColumnId + //but the attempt is impossible and we are simply recursing + //into BrowseBox::GoToColumnId with the same impossible to + //fulfill conditions + if (m_aGotoStack.empty() || aAttempt != m_aGotoStack.top()) + { + m_aGotoStack.push(aAttempt); + CursorMoved(); + m_aGotoStack.pop(); + } + } + return true; + } + return true; +} + + +bool BrowseBox::GoToRowColumnId( sal_Int32 nRow, sal_uInt16 nColId ) +{ + + // out of range? + if ( nRow < 0 || nRow >= nRowCount ) + return false; + + if (!bColumnCursor) + return false; + + // nothing to do ? + if ( nRow == nCurRow && ( bMultiSelection || uRow.nSel == nRow ) && + nColId == nCurColId && IsFieldVisible(nCurRow, nColId, true)) + return true; + + // allowed? + if (!IsCursorMoveAllowed(nRow, nColId)) + return false; + + DoHideCursor(); + bool bMoved = GoToRow(nRow, true) && GoToColumnId(nColId, true, true); + DoShowCursor(); + + if (bMoved) + CursorMoved(); + + return bMoved; +} + + +void BrowseBox::SetNoSelection() +{ + + // is there no selection + if ( ( !pColSel || !pColSel->GetSelectCount() ) && + ( ( !bMultiSelection && uRow.nSel == BROWSER_ENDOFSELECTION ) || + ( bMultiSelection && !uRow.pSel->GetSelectCount() ) ) ) + // nothing to do + return; + + SAL_INFO("svtools", "BrowseBox::HideCursor " << this ); + ToggleSelection(); + + // unselect all + if ( bMultiSelection ) + uRow.pSel->SelectAll(false); + else + uRow.nSel = BROWSER_ENDOFSELECTION; + if ( pColSel ) + pColSel->SelectAll(false); + if ( !bSelecting ) + Select(); + else + bSelect = true; + + // restore screen + SAL_INFO("svtools", "BrowseBox::ShowCursor " << this ); + + if ( isAccessibleAlive() ) + { + commitTableEvent( + SELECTION_CHANGED, + Any(), + Any() + ); + } +} + + +void BrowseBox::SelectAll() +{ + + if ( !bMultiSelection ) + return; + + SAL_INFO("svtools", "BrowseBox::HideCursor " << this ); + ToggleSelection(); + + // select all rows + if ( pColSel ) + pColSel->SelectAll(false); + uRow.pSel->SelectAll(); + + // don't highlight handle column + BrowserColumn *pFirstCol = mvCols[ 0 ].get(); + tools::Long nOfsX = pFirstCol->GetId() ? 0 : pFirstCol->Width(); + + // highlight the row selection + if ( !bHideSelect ) + { + tools::Rectangle aHighlightRect; + sal_uInt16 nVisibleRows = + static_cast<sal_uInt16>(pDataWin->GetOutputSizePixel().Height() / GetDataRowHeight() + 1); + for ( sal_Int32 nRow = std::max<sal_Int32>( nTopRow, uRow.pSel->FirstSelected() ); + nRow != BROWSER_ENDOFSELECTION && nRow < nTopRow + nVisibleRows; + nRow = uRow.pSel->NextSelected() ) + aHighlightRect.Union( tools::Rectangle( + Point( nOfsX, (nRow-nTopRow)*GetDataRowHeight() ), + Size( pDataWin->GetSizePixel().Width(), GetDataRowHeight() ) ) ); + pDataWin->Invalidate( aHighlightRect ); + } + + if ( !bSelecting ) + Select(); + else + bSelect = true; + + // restore screen + SAL_INFO("svtools", "BrowseBox::ShowCursor " << this ); + + if ( !isAccessibleAlive() ) + return; + + commitTableEvent( + SELECTION_CHANGED, + Any(), + Any() + ); + commitHeaderBarEvent( + SELECTION_CHANGED, + Any(), + Any(), + true + ); // column header event + + commitHeaderBarEvent( + SELECTION_CHANGED, + Any(), + Any(), + false + ); // row header event +} + + +void BrowseBox::SelectRow( sal_Int32 nRow, bool _bSelect, bool bExpand ) +{ + + if ( !bMultiSelection ) + { + // deselecting is impossible, selecting via cursor + if ( _bSelect ) + GoToRow(nRow, false); + return; + } + + SAL_INFO("svtools", "BrowseBox::HideCursor " << this ); + + // remove old selection? + if ( !bExpand || !bMultiSelection ) + { + ToggleSelection(); + if ( bMultiSelection ) + uRow.pSel->SelectAll(false); + else + uRow.nSel = BROWSER_ENDOFSELECTION; + if ( pColSel ) + pColSel->SelectAll(false); + } + + // set new selection + if ( !bHideSelect + && ( ( bMultiSelection + && uRow.pSel->GetTotalRange().Max() >= nRow + && uRow.pSel->Select( nRow, _bSelect ) + ) + || ( !bMultiSelection + && ( uRow.nSel = nRow ) != BROWSER_ENDOFSELECTION ) + ) + ) + { + // don't highlight handle column + BrowserColumn *pFirstCol = mvCols[ 0 ].get(); + tools::Long nOfsX = pFirstCol->GetId() ? 0 : pFirstCol->Width(); + + // highlight only newly selected part + tools::Rectangle aRect( + Point( nOfsX, (nRow-nTopRow)*GetDataRowHeight() ), + Size( pDataWin->GetSizePixel().Width(), GetDataRowHeight() ) ); + pDataWin->Invalidate( aRect ); + } + + if ( !bSelecting ) + Select(); + else + bSelect = true; + + // restore screen + SAL_INFO("svtools", "BrowseBox::ShowCursor " << this ); + + if ( !isAccessibleAlive() ) + return; + + commitTableEvent( + SELECTION_CHANGED, + Any(), + Any() + ); + commitHeaderBarEvent( + SELECTION_CHANGED, + Any(), + Any(), + false + ); // row header event +} + + +sal_Int32 BrowseBox::GetSelectRowCount() const +{ + + return bMultiSelection ? uRow.pSel->GetSelectCount() : + uRow.nSel == BROWSER_ENDOFSELECTION ? 0 : 1; +} + + +void BrowseBox::SelectColumnPos( sal_uInt16 nNewColPos, bool _bSelect, bool bMakeVisible ) +{ + + if ( !bColumnCursor || nNewColPos == BROWSER_INVALIDID ) + return; + + if ( !bMultiSelection ) + { + if ( _bSelect ) + GoToColumnId( mvCols[ nNewColPos ]->GetId(), bMakeVisible ); + return; + } + else + { + if ( !GoToColumnId( mvCols[ nNewColPos ]->GetId(), bMakeVisible ) ) + return; + } + + SAL_INFO("svtools", "BrowseBox::HideCursor " << this ); + ToggleSelection(); + if ( bMultiSelection ) + uRow.pSel->SelectAll(false); + else + uRow.nSel = BROWSER_ENDOFSELECTION; + pColSel->SelectAll(false); + + if ( pColSel->Select( nNewColPos, _bSelect ) ) + { + // GoToColumnId( mvCols->GetObject(nNewColPos)->GetId(), bMakeVisible ); + + // only highlight painted areas + pDataWin->Update(); + tools::Rectangle aFieldRectPix( GetFieldRectPixel( nCurRow, nCurColId, false ) ); + tools::Rectangle aRect( + Point( aFieldRectPix.Left() - MIN_COLUMNWIDTH, 0 ), + Size( mvCols[ nNewColPos ]->Width(), + pDataWin->GetOutputSizePixel().Height() ) ); + pDataWin->Invalidate( aRect ); + if ( !bSelecting ) + Select(); + else + bSelect = true; + + if ( isAccessibleAlive() ) + { + commitTableEvent( + SELECTION_CHANGED, + Any(), + Any() + ); + commitHeaderBarEvent( + SELECTION_CHANGED, + Any(), + Any(), + true + ); // column header event + } + } + + // restore screen + SAL_INFO("svtools", "BrowseBox::ShowCursor " << this ); +} + + +sal_uInt16 BrowseBox::GetSelectColumnCount() const +{ + + // while bAutoSelect (==!pColSel), 1 if any rows (yes rows!) else none + return pColSel ? static_cast<sal_uInt16>(pColSel->GetSelectCount()) : + nCurRow >= 0 ? 1 : 0; +} + + +sal_Int32 BrowseBox::FirstSelectedColumn( ) const +{ + return pColSel ? pColSel->FirstSelected() : BROWSER_ENDOFSELECTION; +} + + +sal_Int32 BrowseBox::FirstSelectedRow() +{ + + return bMultiSelection ? uRow.pSel->FirstSelected() : uRow.nSel; +} + + +sal_Int32 BrowseBox::NextSelectedRow() +{ + + return bMultiSelection ? uRow.pSel->NextSelected() : BROWSER_ENDOFSELECTION; +} + + +sal_Int32 BrowseBox::LastSelectedRow() +{ + + return bMultiSelection ? uRow.pSel->LastSelected() : uRow.nSel; +} + + +bool BrowseBox::IsRowSelected( sal_Int32 nRow ) const +{ + + return bMultiSelection ? uRow.pSel->IsSelected(nRow) : nRow == uRow.nSel; +} + + +bool BrowseBox::IsColumnSelected( sal_uInt16 nColumnId ) const +{ + + return pColSel ? pColSel->IsSelected( GetColumnPos(nColumnId) ) : + nCurColId == nColumnId; +} + + +void BrowseBox::MakeFieldVisible +( + sal_Int32 nRow, // line number of the field (starting with 0) + sal_uInt16 nColId // column ID of the field +) + +/* [Description] + + Makes visible the field described in 'nRow' and 'nColId' by scrolling + accordingly. + +*/ + +{ + if (!pDataWin) + return; + + Size aTestSize = pDataWin->GetSizePixel(); + + if ( !bBootstrapped || aTestSize.IsEmpty() ) + return; + + // is it visible already? + bool bVisible = IsFieldVisible( nRow, nColId, true/*bComplete*/ ); + if ( bVisible ) + return; + + // calculate column position, field rectangle and painting area + sal_uInt16 nColPos = GetColumnPos( nColId ); + tools::Rectangle aFieldRect = GetFieldRectPixel( nRow, nColId, false ); + tools::Rectangle aDataRect( Point(0, 0), pDataWin->GetSizePixel() ); + + // positioned outside on the left? + if ( nColPos >= FrozenColCount() && nColPos < nFirstCol ) + // => scroll to the right + ScrollColumns( nColPos - nFirstCol ); + + // while outside on the right + while ( aDataRect.Right() < aFieldRect.Right() ) + { + // => scroll to the left + if ( ScrollColumns( 1 ) != 1 ) + // no more need to scroll + break; + aFieldRect = GetFieldRectPixel( nRow, nColId, false ); + } + + // positioned outside above? + if ( nRow < nTopRow ) + // scroll further to the bottom + ScrollRows( nRow - nTopRow ); + + // positioned outside below? + sal_Int32 nBottomRow = nTopRow + GetVisibleRows(); + // decrement nBottomRow to make it the number of the last visible line + // (count starts with 0!). + // Example: BrowseBox contains exactly one entry. nBottomRow := 0 + 1 - 1 + if( nBottomRow ) + nBottomRow--; + + if ( nRow > nBottomRow ) + // scroll further to the top + ScrollRows( nRow - nBottomRow ); +} + + +bool BrowseBox::IsFieldVisible( sal_Int32 nRow, sal_uInt16 nColumnId, + bool bCompletely ) const +{ + + // hidden by frozen column? + sal_uInt16 nColPos = GetColumnPos( nColumnId ); + if ( nColPos >= FrozenColCount() && nColPos < nFirstCol ) + return false; + + tools::Rectangle aRect( ImplFieldRectPixel( nRow, nColumnId ) ); + if ( aRect.IsEmpty() ) + return false; + + // get the visible area + tools::Rectangle aOutRect( Point(0, 0), pDataWin->GetOutputSizePixel() ); + + if ( bCompletely ) + // test if the field is completely visible + return aOutRect.Contains( aRect ); + else + // test if the field is partly of completely visible + return !aOutRect.Intersection( aRect ).IsEmpty(); +} + + +tools::Rectangle BrowseBox::GetFieldRectPixel( sal_Int32 nRow, sal_uInt16 nColumnId, + bool bRelToBrowser) const +{ + + // get the rectangle relative to DataWin + tools::Rectangle aRect( ImplFieldRectPixel( nRow, nColumnId ) ); + if ( aRect.IsEmpty() ) + return aRect; + + // adjust relative to BrowseBox's output area + Point aTopLeft( aRect.TopLeft() ); + if ( bRelToBrowser ) + { + aTopLeft = pDataWin->OutputToScreenPixel( aTopLeft ); + aTopLeft = ScreenToOutputPixel( aTopLeft ); + } + + return tools::Rectangle( aTopLeft, aRect.GetSize() ); +} + + +tools::Rectangle BrowseBox::GetRowRectPixel( sal_Int32 nRow ) const +{ + + // get the rectangle relative to DataWin + tools::Rectangle aRect; + if ( nTopRow > nRow ) + // row is above visible area + return aRect; + aRect = tools::Rectangle( + Point( 0, GetDataRowHeight() * (nRow-nTopRow) ), + Size( pDataWin->GetOutputSizePixel().Width(), GetDataRowHeight() ) ); + if ( aRect.Top() > pDataWin->GetOutputSizePixel().Height() ) + // row is below visible area + return aRect; + + // adjust relative to BrowseBox's output area + Point aTopLeft( aRect.TopLeft() ); + aTopLeft = pDataWin->OutputToScreenPixel( aTopLeft ); + aTopLeft = ScreenToOutputPixel( aTopLeft ); + + return tools::Rectangle( aTopLeft, aRect.GetSize() ); +} + + +tools::Rectangle BrowseBox::ImplFieldRectPixel( sal_Int32 nRow, sal_uInt16 nColumnId ) const +{ + + // compute the X-coordinate relative to DataWin by accumulation + tools::Long nColX = 0; + sal_uInt16 nFrozenCols = FrozenColCount(); + size_t nCol; + for ( nCol = 0; + nCol < mvCols.size() && mvCols[ nCol ]->GetId() != nColumnId; + ++nCol ) + if ( mvCols[ nCol ]->IsFrozen() || nCol >= nFirstCol ) + nColX += mvCols[ nCol ]->Width(); + + if ( nCol >= mvCols.size() || ( nCol >= nFrozenCols && nCol < nFirstCol ) ) + return tools::Rectangle(); + + // compute the Y-coordinate relative to DataWin + tools::Long nRowY = GetDataRowHeight(); + if ( nRow != BROWSER_ENDOFSELECTION ) // #105497# OJ + nRowY = ( nRow - nTopRow ) * GetDataRowHeight(); + + // assemble the Rectangle relative to DataWin + return tools::Rectangle( + Point( nColX + MIN_COLUMNWIDTH, nRowY ), + Size( (mvCols[nCol]->Width() == LONG_MAX + ? LONG_MAX - (nColX + MIN_COLUMNWIDTH) : mvCols[ nCol ]->Width() - 2*MIN_COLUMNWIDTH), + GetDataRowHeight() - 1 ) ); +} + + +sal_Int32 BrowseBox::GetRowAtYPosPixel( tools::Long nY, bool bRelToBrowser ) const +{ + + // compute the Y-coordinate + if ( bRelToBrowser ) + { + Point aDataTopLeft = pDataWin->OutputToScreenPixel( Point(0, 0) ); + Point aTopLeft = OutputToScreenPixel( Point(0, 0) ); + nY -= aDataTopLeft.Y() - aTopLeft.Y(); + } + + // no row there (e.g. in the header) + if ( nY < 0 || nY >= pDataWin->GetOutputSizePixel().Height() ) + return -1; + + return nY / GetDataRowHeight() + nTopRow; +} + + +tools::Rectangle BrowseBox::GetFieldRect( sal_uInt16 nColumnId ) const +{ + + return GetFieldRectPixel( nCurRow, nColumnId ); +} + + +sal_uInt16 BrowseBox::GetColumnAtXPosPixel( tools::Long nX ) const +{ + + // accumulate the widths of the visible columns + tools::Long nColX = 0; + for ( size_t nCol = 0; nCol < mvCols.size(); ++nCol ) + { + BrowserColumn *pCol = mvCols[ nCol ].get(); + if ( pCol->IsFrozen() || nCol >= nFirstCol ) + nColX += pCol->Width(); + + if ( nColX > nX ) + return nCol; + } + + return BROWSER_INVALIDID; +} + +bool BrowseBox::ReserveControlArea(sal_uInt16 nWidth) +{ + if (nWidth != nControlAreaWidth) + { + OSL_ENSURE(nWidth,"Control area of 0 is not allowed, Use USHRT_MAX instead!"); + nControlAreaWidth = nWidth; + UpdateScrollbars(); + return true; + } + return false; +} + +tools::Rectangle BrowseBox::GetControlArea() const +{ + auto nHeight = aHScroll->GetSizePixel().Height(); + auto nEndRight = aHScroll->GetPosPixel().X(); + + return tools::Rectangle( + Point( 0, GetOutputSizePixel().Height() - nHeight ), + Size( nEndRight, nHeight ) ); +} + +void BrowseBox::SetMode( BrowserMode nMode ) +{ + + pDataWin->bAutoHScroll = BrowserMode::AUTO_HSCROLL == ( nMode & BrowserMode::AUTO_HSCROLL ); + pDataWin->bAutoVScroll = BrowserMode::AUTO_VSCROLL == ( nMode & BrowserMode::AUTO_VSCROLL ); + pDataWin->bNoHScroll = BrowserMode::NO_HSCROLL == ( nMode & BrowserMode::NO_HSCROLL ); + pDataWin->bNoVScroll = BrowserMode::NO_VSCROLL == ( nMode & BrowserMode::NO_VSCROLL ); + + DBG_ASSERT( !( pDataWin->bAutoHScroll && pDataWin->bNoHScroll ), + "BrowseBox::SetMode: AutoHScroll *and* NoHScroll?" ); + DBG_ASSERT( !( pDataWin->bAutoVScroll && pDataWin->bNoVScroll ), + "BrowseBox::SetMode: AutoVScroll *and* NoVScroll?" ); + if ( pDataWin->bAutoHScroll ) + pDataWin->bNoHScroll = false; + if ( pDataWin->bAutoVScroll ) + pDataWin->bNoVScroll = false; + + if ( pDataWin->bNoHScroll ) + aHScroll->Hide(); + + nControlAreaWidth = USHRT_MAX; + + tools::Long nOldRowSel = bMultiSelection ? uRow.pSel->FirstSelected() : uRow.nSel; + MultiSelection *pOldRowSel = bMultiSelection ? uRow.pSel : nullptr; + + pVScroll.disposeAndClear(); + + bMultiSelection = bool( nMode & BrowserMode::MULTISELECTION ); + bColumnCursor = bool( nMode & BrowserMode::COLUMNSELECTION ); + bKeepHighlight = bool( nMode & BrowserMode::KEEPHIGHLIGHT ); + + bHideSelect = ((nMode & BrowserMode::HIDESELECT) == BrowserMode::HIDESELECT); + // default: do not hide the cursor at all (untaken scrolling and such) + bHideCursor = TRISTATE_FALSE; + + if ( BrowserMode::HIDECURSOR == ( nMode & BrowserMode::HIDECURSOR ) ) + { + bHideCursor = TRISTATE_TRUE; + } + + m_bFocusOnlyCursor = ((nMode & BrowserMode::CURSOR_WO_FOCUS) == BrowserMode::NONE); + + bHLines = ( nMode & BrowserMode::HLINES ) == BrowserMode::HLINES; + bVLines = ( nMode & BrowserMode::VLINES ) == BrowserMode::VLINES; + + constexpr WinBits nVScrollWinBits = WB_VSCROLL; + pVScroll = ( nMode & BrowserMode::TRACKING_TIPS ) == BrowserMode::TRACKING_TIPS + ? VclPtr<BrowserScrollBar>::Create( this, nVScrollWinBits, pDataWin.get() ) + : VclPtr<ScrollBar>::Create( this, nVScrollWinBits ); + pVScroll->SetLineSize( 1 ); + pVScroll->SetPageSize(1); + pVScroll->SetScrollHdl( LINK( this, BrowseBox, ScrollHdl ) ); + + pDataWin->bAutoSizeLastCol = + BrowserMode::AUTOSIZE_LASTCOL == ( nMode & BrowserMode::AUTOSIZE_LASTCOL ); + + // create a headerbar. what happens, if a headerbar has to be created and + // there already are columns? + if ( BrowserMode::HEADERBAR_NEW == ( nMode & BrowserMode::HEADERBAR_NEW ) ) + { + if (!pDataWin->pHeaderBar) + pDataWin->pHeaderBar = CreateHeaderBar( this ); + } + else + { + pDataWin->pHeaderBar.disposeAndClear(); + } + + if ( bColumnCursor ) + { + if (!pColSel) + pColSel.reset(new MultiSelection); + pColSel->SetTotalRange( Range( 0, mvCols.size()-1 ) ); + } + else + { + pColSel.reset(); + } + + if ( bMultiSelection ) + { + if ( pOldRowSel ) + uRow.pSel = pOldRowSel; + else + uRow.pSel = new MultiSelection; + } + else + { + uRow.nSel = nOldRowSel; + delete pOldRowSel; + } + + if ( bBootstrapped ) + { + StateChanged( StateChangedType::InitShow ); + if ( bMultiSelection && !pOldRowSel && + nOldRowSel != BROWSER_ENDOFSELECTION ) + uRow.pSel->Select( nOldRowSel ); + } + + if ( pDataWin ) + pDataWin->Invalidate(); + + // no cursor on handle column + if ( nCurColId == HandleColumnId ) + nCurColId = GetColumnId( 1 ); + + m_nCurrentMode = nMode; +} + + +void BrowseBox::VisibleRowsChanged( sal_Int32, sal_uInt16 ) +{ + + // old behavior: automatically correct NumRows: + if ( nRowCount < GetRowCount() ) + { + RowInserted(nRowCount,GetRowCount() - nRowCount, false); + } + else if ( nRowCount > GetRowCount() ) + { + RowRemoved(nRowCount-(nRowCount - GetRowCount()),nRowCount - GetRowCount(), false); + } +} + + +bool BrowseBox::IsCursorMoveAllowed( sal_Int32, sal_uInt16 ) const + +/* [Description] + + This virtual method is always called before the cursor is moved directly. + By means of 'return false', we avoid doing this if e.g. a record + contradicts any rules. + + This method is not called, if the cursor movement results from removing or + deleting a row/column (thus, in cases where only a "cursor correction" happens). + + The base implementation currently always returns true. +*/ + +{ + return true; +} + + +tools::Long BrowseBox::GetDataRowHeight() const +{ + return CalcZoom(m_nDataRowHeight ? m_nDataRowHeight : ImpGetDataRowHeight()); +} + + +VclPtr<BrowserHeader> BrowseBox::CreateHeaderBar( BrowseBox* pParent ) +{ + VclPtr<BrowserHeader> pNewBar = VclPtr<BrowserHeader>::Create( pParent ); + pNewBar->SetStartDragHdl( LINK( this, BrowseBox, StartDragHdl ) ); + return pNewBar; +} + +void BrowseBox::SetHeaderBar( BrowserHeader* pHeaderBar ) +{ + pDataWin->pHeaderBar.disposeAndClear(); + pDataWin->pHeaderBar = pHeaderBar; + pDataWin->pHeaderBar->SetStartDragHdl( LINK( this, BrowseBox, StartDragHdl ) ); +} + +tools::Long BrowseBox::GetTitleHeight() const +{ + tools::Long nHeight; + // ask the header bar for the text height (if possible), as the header bar's font is adjusted with + // our (and the header's) zoom factor + HeaderBar* pHeaderBar = pDataWin->pHeaderBar; + if ( pHeaderBar ) + nHeight = pHeaderBar->GetTextHeight(); + else + nHeight = GetTextHeight(); + + return nTitleLines ? nTitleLines * nHeight + 4 : 0; +} + +tools::Long BrowseBox::CalcReverseZoom(tools::Long nVal) const +{ + if (IsZoom()) + { + const Fraction& rZoom = GetZoom(); + double n = static_cast<double>(nVal); + n *= static_cast<double>(rZoom.GetDenominator()); + if (!rZoom.GetNumerator()) + throw o3tl::divide_by_zero(); + n /= static_cast<double>(rZoom.GetNumerator()); + nVal = n>0 ? static_cast<tools::Long>(n + 0.5) : -static_cast<tools::Long>(-n + 0.5); + } + + return nVal; +} + +void BrowseBox::CursorMoved() +{ + // before implementing more here, please adjust the EditBrowseBox + + if ( isAccessibleAlive() && HasFocus() ) + commitTableEvent( + ACTIVE_DESCENDANT_CHANGED, + Any( CreateAccessibleCell( GetCurRow(),GetColumnPos( GetCurColumnId() ) ) ), + Any() + ); +} + +void BrowseBox::LoseFocus() +{ + SAL_INFO("svtools", "BrowseBox::LoseFocus " << this ); + + if ( bHasFocus ) + { + SAL_INFO("svtools", "BrowseBox::HideCursor " << this ); + DoHideCursor(); + + if ( !bKeepHighlight ) + { + ToggleSelection(); + bSelectionIsVisible = false; + } + + bHasFocus = false; + } + Control::LoseFocus(); +} + + +void BrowseBox::GetFocus() +{ + SAL_INFO("svtools", "BrowseBox::GetFocus " << this ); + + if ( !bHasFocus ) + { + if ( !bSelectionIsVisible ) + { + bSelectionIsVisible = true; + if ( bBootstrapped ) + ToggleSelection(); + } + + bHasFocus = true; + DoShowCursor(); + } + Control::GetFocus(); +} + + +sal_uInt16 BrowseBox::GetVisibleRows() const +{ + return static_cast<sal_uInt16>((pDataWin->GetOutputSizePixel().Height() - 1 )/ GetDataRowHeight() + 1); +} + +BrowserDataWin& BrowseBox::GetDataWindow() const +{ + return *pDataWin; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/brwbox/brwbox2.cxx b/svtools/source/brwbox/brwbox2.cxx new file mode 100644 index 000000000..98eb5e88d --- /dev/null +++ b/svtools/source/brwbox/brwbox2.cxx @@ -0,0 +1,1971 @@ +/* -*- 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/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <tools/debug.hxx> +#include <svtools/brwbox.hxx> +#include <svtools/brwhead.hxx> +#include "datwin.hxx" +#include <svtools/colorcfg.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/scrbar.hxx> +#include <vcl/settings.hxx> + +#include <tools/multisel.hxx> +#include <tools/fract.hxx> +#include <algorithm> +#include <memory> + +using namespace ::com::sun::star::datatransfer; + + +void BrowseBox::StartDrag( sal_Int8 /* _nAction */, const Point& /* _rPosPixel */ ) +{ + // not interested in this event +} + + +sal_Int8 BrowseBox::AcceptDrop( const AcceptDropEvent& _rEvt ) +{ + AcceptDropEvent aTransformed( _rEvt ); + aTransformed.maPosPixel = pDataWin->ScreenToOutputPixel( OutputToScreenPixel( _rEvt.maPosPixel ) ); + return pDataWin->AcceptDrop( aTransformed ); +} + + +sal_Int8 BrowseBox::ExecuteDrop( const ExecuteDropEvent& _rEvt ) +{ + ExecuteDropEvent aTransformed( _rEvt ); + aTransformed.maPosPixel = pDataWin->ScreenToOutputPixel( OutputToScreenPixel( _rEvt.maPosPixel ) ); + return pDataWin->ExecuteDrop( aTransformed ); +} + + +sal_Int8 BrowseBox::AcceptDrop( const BrowserAcceptDropEvent& ) +{ + // not interested in this event + return DND_ACTION_NONE; +} + + +sal_Int8 BrowseBox::ExecuteDrop( const BrowserExecuteDropEvent& ) +{ + // not interested in this event + return DND_ACTION_NONE; +} + + +const DataFlavorExVector& BrowseBox::GetDataFlavors() const +{ + if (pDataWin->bCallingDropCallback) + return pDataWin->GetDataFlavorExVector(); + return GetDataFlavorExVector(); +} + + +bool BrowseBox::IsDropFormatSupported( SotClipboardFormatId _nFormat ) const +{ + if ( pDataWin->bCallingDropCallback ) + return pDataWin->IsDropFormatSupported( _nFormat ); + + return DropTargetHelper::IsDropFormatSupported( _nFormat ); +} + + +void BrowseBox::Command( const CommandEvent& rEvt ) +{ + if ( !pDataWin->bInCommand ) + Control::Command( rEvt ); +} + + +void BrowseBox::StateChanged( StateChangedType nStateChange ) +{ + Control::StateChanged( nStateChange ); + + if ( StateChangedType::Mirroring == nStateChange ) + { + pDataWin->EnableRTL( IsRTLEnabled() ); + + HeaderBar* pHeaderBar = pDataWin->pHeaderBar; + if ( pHeaderBar ) + pHeaderBar->EnableRTL( IsRTLEnabled() ); + aHScroll->EnableRTL( IsRTLEnabled() ); + if( pVScroll ) + pVScroll->EnableRTL( IsRTLEnabled() ); + Resize(); + } + else if ( StateChangedType::InitShow == nStateChange ) + { + bBootstrapped = true; // must be set first! + + Resize(); + if ( bMultiSelection ) + uRow.pSel->SetTotalRange( Range( 0, nRowCount - 1 ) ); + if ( nRowCount == 0 ) + nCurRow = BROWSER_ENDOFSELECTION; + else if ( nCurRow == BROWSER_ENDOFSELECTION ) + nCurRow = 0; + + + if ( HasFocus() ) + { + bSelectionIsVisible = true; + bHasFocus = true; + } + UpdateScrollbars(); + AutoSizeLastColumn(); + CursorMoved(); + } + else if (StateChangedType::Zoom == nStateChange) + { + pDataWin->SetZoom(GetZoom()); + HeaderBar* pHeaderBar = pDataWin->pHeaderBar; + if (pHeaderBar) + pHeaderBar->SetZoom(GetZoom()); + + // let the columns calculate their new widths and adjust the header bar + for (auto & pCol : mvCols) + { + pCol->ZoomChanged(GetZoom()); + if ( pHeaderBar ) + pHeaderBar->SetItemSize( pCol->GetId(), pCol->Width() ); + } + + // all our controls have to be repositioned + Resize(); + } + else if (StateChangedType::Enable == nStateChange) + { + // do we have a handle column? + bool bHandleCol = !mvCols.empty() && (0 == mvCols[ 0 ]->GetId()); + // do we have a header bar? + bool bHeaderBar(pDataWin->pHeaderBar); + + if ( nTitleLines + && ( !bHeaderBar + || bHandleCol + ) + ) + // we draw the text in our header bar in a color dependent on the enabled state. So if this state changed + // -> redraw + Invalidate(tools::Rectangle(Point(0, 0), Size(GetOutputSizePixel().Width(), GetTitleHeight() - 1))); + } +} + + +void BrowseBox::Select() +{ +} + + +void BrowseBox::DoubleClick( const BrowserMouseEvent & ) +{ +} + + +tools::Long BrowseBox::QueryMinimumRowHeight() +{ + return CalcZoom( 5 ); +} + + +void BrowseBox::ImplStartTracking() +{ +} + + +void BrowseBox::ImplEndTracking() +{ +} + + +void BrowseBox::RowHeightChanged() +{ +} + + +void BrowseBox::ColumnResized( sal_uInt16 ) +{ +} + + +void BrowseBox::ColumnMoved( sal_uInt16 ) +{ +} + + +void BrowseBox::StartScroll() +{ + DoHideCursor(); +} + + +void BrowseBox::EndScroll() +{ + UpdateScrollbars(); + AutoSizeLastColumn(); + DoShowCursor(); +} + + +void BrowseBox::ToggleSelection() +{ + + // selection highlight-toggling allowed? + if ( bHideSelect ) + return; + if ( bNotToggleSel || !IsUpdateMode() || !bSelectionIsVisible ) + return; + + // only highlight painted areas! + bNotToggleSel = true; + + // accumulate areas of rows to highlight + std::vector<tools::Rectangle> aHighlightList; + sal_Int32 nLastRowInRect = 0; // for the CFront + + // don't highlight handle column + BrowserColumn *pFirstCol = mvCols.empty() ? nullptr : mvCols[ 0 ].get(); + tools::Long nOfsX = (!pFirstCol || pFirstCol->GetId()) ? 0 : pFirstCol->Width(); + + // accumulate old row selection + sal_Int32 nBottomRow = nTopRow + + pDataWin->GetOutputSizePixel().Height() / GetDataRowHeight(); + if ( nBottomRow > GetRowCount() && GetRowCount() ) + nBottomRow = GetRowCount(); + for ( sal_Int32 nRow = bMultiSelection ? uRow.pSel->FirstSelected() : uRow.nSel; + nRow != BROWSER_ENDOFSELECTION && nRow <= nBottomRow; + nRow = bMultiSelection ? uRow.pSel->NextSelected() : BROWSER_ENDOFSELECTION ) + { + if ( nRow < nTopRow ) + continue; + + tools::Rectangle aAddRect( + Point( nOfsX, (nRow-nTopRow)*GetDataRowHeight() ), + Size( pDataWin->GetSizePixel().Width(), GetDataRowHeight() ) ); + if ( !aHighlightList.empty() && nLastRowInRect == ( nRow - 1 ) ) + aHighlightList[ 0 ].Union( aAddRect ); + else + aHighlightList.emplace( aHighlightList.begin(), aAddRect ); + nLastRowInRect = nRow; + } + + // unhighlight the old selection (if any) + while ( !aHighlightList.empty() ) + { + pDataWin->Invalidate( aHighlightList.back() ); + aHighlightList.pop_back(); + } + + // unhighlight old column selection (if any) + for ( tools::Long nColId = pColSel ? pColSel->FirstSelected() : BROWSER_ENDOFSELECTION; + nColId != BROWSER_ENDOFSELECTION; + nColId = pColSel->NextSelected() ) + { + tools::Rectangle aRect( GetFieldRectPixel(nCurRow, + mvCols[ nColId ]->GetId(), + false ) ); + aRect.AdjustLeft( -(MIN_COLUMNWIDTH) ); + aRect.AdjustRight(MIN_COLUMNWIDTH ); + aRect.SetTop( 0 ); + aRect.SetBottom( pDataWin->GetOutputSizePixel().Height() ); + pDataWin->Invalidate( aRect ); + } + + bNotToggleSel = false; +} + + +void BrowseBox::DrawCursor() +{ + bool bReallyHide = false; + if ( bHideCursor == TRISTATE_INDET ) + { + if ( !GetSelectRowCount() && !GetSelectColumnCount() ) + bReallyHide = true; + } + else if ( bHideCursor == TRISTATE_TRUE ) + { + bReallyHide = true; + } + + bReallyHide |= !bSelectionIsVisible || !IsUpdateMode() || bScrolling || nCurRow < 0; + + if (PaintCursorIfHiddenOnce()) + bReallyHide |= ( GetCursorHideCount() > 1 ); + else + bReallyHide |= ( GetCursorHideCount() > 0 ); + + // no cursor on handle column + if ( nCurColId == HandleColumnId ) + nCurColId = GetColumnId(1); + + // calculate cursor rectangle + tools::Rectangle aCursor; + if ( bColumnCursor ) + { + aCursor = GetFieldRectPixel( nCurRow, nCurColId, false ); + aCursor.AdjustLeft( -(MIN_COLUMNWIDTH) ); + aCursor.AdjustRight(1 ); + aCursor.AdjustBottom(1 ); + } + else + aCursor = tools::Rectangle( + Point( ( !mvCols.empty() && mvCols[ 0 ]->GetId() == 0 ) ? + mvCols[ 0 ]->Width() : 0, + (nCurRow - nTopRow) * GetDataRowHeight() + 1 ), + Size( pDataWin->GetOutputSizePixel().Width() + 1, + GetDataRowHeight() - 2 ) ); + if ( bHLines ) + { + if ( !bMultiSelection ) + aCursor.AdjustTop( -1 ); + aCursor.AdjustBottom( -1 ); + } + + if (m_aCursorColor == COL_TRANSPARENT) + { + // on these platforms, the StarView focus works correctly + if ( bReallyHide ) + static_cast<Control*>(pDataWin.get())->HideFocus(); + else + static_cast<Control*>(pDataWin.get())->ShowFocus( aCursor ); + } + else + { + Color rCol = bReallyHide ? pDataWin->GetOutDev()->GetFillColor() : m_aCursorColor; + Color aOldFillColor = pDataWin->GetOutDev()->GetFillColor(); + Color aOldLineColor = pDataWin->GetOutDev()->GetLineColor(); + pDataWin->GetOutDev()->SetFillColor(); + pDataWin->GetOutDev()->SetLineColor( rCol ); + pDataWin->GetOutDev()->DrawRect( aCursor ); + pDataWin->GetOutDev()->SetLineColor( aOldLineColor ); + pDataWin->GetOutDev()->SetFillColor( aOldFillColor ); + } +} + + +sal_uLong BrowseBox::GetColumnWidth( sal_uInt16 nId ) const +{ + + sal_uInt16 nItemPos = GetColumnPos( nId ); + if ( nItemPos >= mvCols.size() ) + return 0; + return mvCols[ nItemPos ]->Width(); +} + + +sal_uInt16 BrowseBox::GetColumnId( sal_uInt16 nPos ) const +{ + + if ( nPos >= mvCols.size() ) + return BROWSER_INVALIDID; + return mvCols[ nPos ]->GetId(); +} + + +sal_uInt16 BrowseBox::GetColumnPos( sal_uInt16 nId ) const +{ + for ( size_t nPos = 0; nPos < mvCols.size(); ++nPos ) + if ( mvCols[ nPos ]->GetId() == nId ) + return nPos; + return BROWSER_INVALIDID; +} + + +bool BrowseBox::IsFrozen( sal_uInt16 nColumnId ) const +{ + for (auto const & pCol : mvCols) + if ( pCol->GetId() == nColumnId ) + return pCol->IsFrozen(); + return false; +} + + +void BrowseBox::ExpandRowSelection( const BrowserMouseEvent& rEvt ) +{ + DoHideCursor(); + + // expand the last selection + if ( bMultiSelection ) + { + Range aJustifiedRange( aSelRange ); + aJustifiedRange.Justify(); + + bool bSelectThis = ( bSelect != aJustifiedRange.Contains( rEvt.GetRow() ) ); + + if ( aJustifiedRange.Contains( rEvt.GetRow() ) ) + { + // down and up + while ( rEvt.GetRow() < aSelRange.Max() ) + { // ZTC/Mac bug - don't put these statements together! + SelectRow( aSelRange.Max(), bSelectThis ); + --aSelRange.Max(); + } + while ( rEvt.GetRow() > aSelRange.Max() ) + { // ZTC/Mac bug - don't put these statements together! + SelectRow( aSelRange.Max(), bSelectThis ); + ++aSelRange.Max(); + } + } + else + { + // up and down + bool bOldSelecting = bSelecting; + bSelecting = true; + while ( rEvt.GetRow() < aSelRange.Max() ) + { // ZTC/Mac bug - don't put these statements together! + --aSelRange.Max(); + if ( !IsRowSelected( aSelRange.Max() ) ) + { + SelectRow( aSelRange.Max(), bSelectThis ); + bSelect = true; + } + } + while ( rEvt.GetRow() > aSelRange.Max() ) + { // ZTC/Mac bug - don't put these statements together! + ++aSelRange.Max(); + if ( !IsRowSelected( aSelRange.Max() ) ) + { + SelectRow( aSelRange.Max(), bSelectThis ); + bSelect = true; + } + } + bSelecting = bOldSelecting; + if ( bSelect ) + Select(); + } + } + else + if (!IsRowSelected(rEvt.GetRow())) + SelectRow( rEvt.GetRow() ); + + GoToRow( rEvt.GetRow(), false ); + DoShowCursor(); +} + + +void BrowseBox::Resize() +{ + if ( !bBootstrapped && IsReallyVisible() ) + BrowseBox::StateChanged( StateChangedType::InitShow ); + if ( mvCols.empty() ) + { + pDataWin->bResizeOnPaint = true; + return; + } + pDataWin->bResizeOnPaint = false; + + // calc the size of the scrollbars + sal_uLong nSBSize = GetBarHeight(); + if (IsZoom()) + nSBSize = static_cast<sal_uLong>(nSBSize * static_cast<double>(GetZoom())); + + DoHideCursor(); + sal_uInt16 nOldVisibleRows = 0; + //fdo#42694, post #i111125# GetDataRowHeight() can be 0 + if (GetDataRowHeight()) + nOldVisibleRows = static_cast<sal_uInt16>(pDataWin->GetOutputSizePixel().Height() / GetDataRowHeight() + 1); + + // did we need a horizontal scroll bar or is there a Control Area? + if ( !pDataWin->bNoHScroll && + ( ( mvCols.size() - FrozenColCount() ) > 1 ) ) + aHScroll->Show(); + else + aHScroll->Hide(); + + // calculate the size of the data window + tools::Long nDataHeight = GetOutputSizePixel().Height() - GetTitleHeight(); + if ( aHScroll->IsVisible() || ( nControlAreaWidth != USHRT_MAX ) ) + nDataHeight -= nSBSize; + + tools::Long nDataWidth = GetOutputSizePixel().Width(); + if ( pVScroll->IsVisible() ) + nDataWidth -= nSBSize; + + // adjust position and size of data window + pDataWin->SetPosSizePixel( + Point( 0, GetTitleHeight() ), + Size( nDataWidth, nDataHeight ) ); + + sal_uInt16 nVisibleRows = 0; + + if (GetDataRowHeight()) + nVisibleRows = static_cast<sal_uInt16>(pDataWin->GetOutputSizePixel().Height() / GetDataRowHeight() + 1); + + // TopRow is unchanged, but the number of visible lines has changed. + if ( nVisibleRows != nOldVisibleRows ) + VisibleRowsChanged(nTopRow, nVisibleRows); + + UpdateScrollbars(); + + // Control-Area + tools::Rectangle aInvalidArea( GetControlArea() ); + aInvalidArea.SetRight( GetOutputSizePixel().Width() ); + aInvalidArea.SetLeft( 0 ); + Invalidate( aInvalidArea ); + + // external header-bar + HeaderBar* pHeaderBar = pDataWin->pHeaderBar; + if ( pHeaderBar ) + { + // take the handle column into account + BrowserColumn *pFirstCol = mvCols[ 0 ].get(); + tools::Long nOfsX = pFirstCol->GetId() ? 0 : pFirstCol->Width(); + pHeaderBar->SetPosSizePixel( Point( nOfsX, 0 ), Size( GetOutputSizePixel().Width() - nOfsX, GetTitleHeight() ) ); + } + + AutoSizeLastColumn(); // adjust last column width + DoShowCursor(); +} + + +void BrowseBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + + // initializations + if (!bBootstrapped && IsReallyVisible()) + BrowseBox::StateChanged(StateChangedType::InitShow); + if (mvCols.empty()) + return; + + BrowserColumn *pFirstCol = mvCols[ 0 ].get(); + bool bHandleCol = pFirstCol && pFirstCol->GetId() == 0; + bool bHeaderBar(pDataWin->pHeaderBar); + + // draw delimitational lines + if (!pDataWin->bNoHScroll) + rRenderContext.DrawLine(Point(0, aHScroll->GetPosPixel().Y()), + Point(GetOutputSizePixel().Width(), + aHScroll->GetPosPixel().Y())); + + if (nTitleLines) + { + if (!bHeaderBar) + { + rRenderContext.DrawLine(Point(0, GetTitleHeight() - 1), + Point(GetOutputSizePixel().Width(), GetTitleHeight() - 1)); + } + else if (bHandleCol) + { + rRenderContext.DrawLine(Point(0, GetTitleHeight() - 1), + Point(pFirstCol->Width(), GetTitleHeight() - 1)); + } + } + + // Title Bar + // If there is a handle column and if the header bar is available, only + // take the HandleColumn into account + if (!(nTitleLines && (!bHeaderBar || bHandleCol))) + return; + + // iterate through columns to redraw + tools::Long nX = 0; + size_t nCol; + for (nCol = 0; nCol < mvCols.size() && nX < rRect.Right(); ++nCol) + { + // skip invisible columns between frozen and scrollable area + if (nCol < nFirstCol && !mvCols[nCol]->IsFrozen()) + nCol = nFirstCol; + + // only the handle column? + if (bHeaderBar && bHandleCol && nCol > 0) + break; + + BrowserColumn* pCol = mvCols[nCol].get(); + + // draw the column and increment position + if ( pCol->Width() > 4 ) + { + ButtonFrame aButtonFrame( Point( nX, 0 ), + Size( pCol->Width()-1, GetTitleHeight()-1 ), + pCol->Title(), !IsEnabled()); + aButtonFrame.Draw(rRenderContext); + rRenderContext.DrawLine(Point(nX + pCol->Width() - 1, 0), + Point(nX + pCol->Width() - 1, GetTitleHeight() - 1)); + } + else + { + rRenderContext.Push(vcl::PushFlags::FILLCOLOR); + rRenderContext.SetFillColor(COL_BLACK); + rRenderContext.DrawRect(tools::Rectangle(Point(nX, 0), Size(pCol->Width(), GetTitleHeight() - 1))); + rRenderContext.Pop(); + } + + // skip column + nX += pCol->Width(); + } + + // retouching + if ( !bHeaderBar && nCol == mvCols.size() ) + { + const StyleSettings &rSettings = rRenderContext.GetSettings().GetStyleSettings(); + Color aColFace(rSettings.GetFaceColor()); + rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR); + rRenderContext.SetFillColor(aColFace); + rRenderContext.SetLineColor(aColFace); + rRenderContext.DrawRect(tools::Rectangle(Point(nX, 0), + Point(rRect.Right(), GetTitleHeight() - 2 ))); + rRenderContext.Pop(); + } +} + +void BrowseBox::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags ) +{ + // we need pixel coordinates + Size aRealSize = GetSizePixel(); + Point aRealPos = pDev->LogicToPixel(rPos); + + if ((aRealSize.Width() < 3) || (aRealSize.Height() < 3)) + // we want to have two pixels frame ... + return; + + vcl::Font aFont = pDataWin->GetDrawPixelFont( pDev ); + // the 'normal' painting uses always the data window as device to output to, so we have to calc the new font + // relative to the data wins current settings + + pDev->Push(); + pDev->SetMapMode(); + pDev->SetFont( aFont ); + if (nFlags & SystemTextColorFlags::Mono) + pDev->SetTextColor(COL_BLACK); + else + pDev->SetTextColor(pDataWin->GetTextColor()); + + // draw a frame + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + pDev->SetLineColor(rStyleSettings.GetDarkShadowColor()); + pDev->DrawLine(Point(aRealPos.X(), aRealPos.Y()), + Point(aRealPos.X(), aRealPos.Y() + aRealSize.Height() - 1)); + pDev->DrawLine(Point(aRealPos.X(), aRealPos.Y()), + Point(aRealPos.X() + aRealSize.Width() - 1, aRealPos.Y())); + pDev->SetLineColor(rStyleSettings.GetShadowColor()); + pDev->DrawLine(Point(aRealPos.X() + aRealSize.Width() - 1, aRealPos.Y() + 1), + Point(aRealPos.X() + aRealSize.Width() - 1, aRealPos.Y() + aRealSize.Height() - 1)); + pDev->DrawLine(Point(aRealPos.X() + aRealSize.Width() - 1, aRealPos.Y() + aRealSize.Height() - 1), + Point(aRealPos.X() + 1, aRealPos.Y() + aRealSize.Height() - 1)); + + HeaderBar* pBar = pDataWin->pHeaderBar; + + // we're drawing onto a foreign device, so we have to fake the DataRowHeight for the subsequent ImplPaintData + // (as it is based on the settings of our data window, not the foreign device) + if (!m_nDataRowHeight) + ImpGetDataRowHeight(); + tools::Long nHeightLogic = PixelToLogic(Size(0, m_nDataRowHeight), MapMode(MapUnit::Map10thMM)).Height(); + tools::Long nForeignHeightPixel = pDev->LogicToPixel(Size(0, nHeightLogic), MapMode(MapUnit::Map10thMM)).Height(); + + tools::Long nOriginalHeight = m_nDataRowHeight; + m_nDataRowHeight = nForeignHeightPixel; + + // this counts for the column widths, too + size_t nPos; + for ( nPos = 0; nPos < mvCols.size(); ++nPos ) + { + BrowserColumn* pCurrent = mvCols[ nPos ].get(); + + tools::Long nWidthLogic = PixelToLogic(Size(pCurrent->Width(), 0), MapMode(MapUnit::Map10thMM)).Width(); + tools::Long nForeignWidthPixel = pDev->LogicToPixel(Size(nWidthLogic, 0), MapMode(MapUnit::Map10thMM)).Width(); + + pCurrent->SetWidth(nForeignWidthPixel, GetZoom()); + if ( pBar ) + pBar->SetItemSize( pCurrent->GetId(), pCurrent->Width() ); + } + + // a smaller area for the content + aRealPos.AdjustX( 1 ); + aRealPos.AdjustY( 1 ); + aRealSize.AdjustWidth( -2 ); + aRealSize.AdjustHeight( -2 ); + + // let the header bar draw itself + if ( pBar ) + { + // the title height with respect to the font set for the given device + tools::Long nTitleHeight = PixelToLogic(Size(0, GetTitleHeight()), MapMode(MapUnit::Map10thMM)).Height(); + nTitleHeight = pDev->LogicToPixel(Size(0, nTitleHeight), MapMode(MapUnit::Map10thMM)).Height(); + + BrowserColumn* pFirstCol = !mvCols.empty() ? mvCols[ 0 ].get() : nullptr; + + Point aHeaderPos(pFirstCol && (pFirstCol->GetId() == 0) ? pFirstCol->Width() : 0, 0); + Size aHeaderSize(aRealSize.Width() - aHeaderPos.X(), nTitleHeight); + + aHeaderPos += aRealPos; + // do this before converting to logics ! + + // the header's draw expects logic coordinates, again + aHeaderPos = pDev->PixelToLogic(aHeaderPos); + + Size aOrigSize(pBar->GetSizePixel()); + pBar->SetSizePixel(aHeaderSize); + pBar->Draw(pDev, aHeaderPos, nFlags); + pBar->SetSizePixel(aOrigSize); + + // draw the "upper left cell" (the intersection between the header bar and the handle column) + if (pFirstCol && (pFirstCol->GetId() == 0) && (pFirstCol->Width() > 4)) + { + ButtonFrame aButtonFrame( aRealPos, + Size( pFirstCol->Width()-1, nTitleHeight-1 ), + pFirstCol->Title(), !IsEnabled()); + aButtonFrame.Draw( *pDev ); + + pDev->Push( vcl::PushFlags::LINECOLOR ); + pDev->SetLineColor( COL_BLACK ); + + pDev->DrawLine( Point( aRealPos.X(), aRealPos.Y() + nTitleHeight-1 ), + Point( aRealPos.X() + pFirstCol->Width() - 1, aRealPos.Y() + nTitleHeight-1 ) ); + pDev->DrawLine( Point( aRealPos.X() + pFirstCol->Width() - 1, aRealPos.Y() ), + Point( aRealPos.X() + pFirstCol->Width() - 1, aRealPos.Y() + nTitleHeight-1 ) ); + + pDev->Pop(); + } + + aRealPos.AdjustY(aHeaderSize.Height() ); + aRealSize.AdjustHeight( -(aHeaderSize.Height()) ); + } + + // draw our own content (with clipping) + vcl::Region aRegion(tools::Rectangle(aRealPos, aRealSize)); + pDev->SetClipRegion( pDev->PixelToLogic( aRegion ) ); + + // do we have to paint the background + bool bBackground = pDataWin->IsControlBackground(); + if ( bBackground ) + { + tools::Rectangle aRect( aRealPos, aRealSize ); + pDev->SetFillColor( pDataWin->GetControlBackground() ); + pDev->DrawRect( aRect ); + } + + ImplPaintData( *pDev, tools::Rectangle( aRealPos, aRealSize ), true ); + + // restore the column widths/data row height + m_nDataRowHeight = nOriginalHeight; + for ( nPos = 0; nPos < mvCols.size(); ++nPos ) + { + BrowserColumn* pCurrent = mvCols[ nPos ].get(); + + tools::Long nForeignWidthLogic = pDev->PixelToLogic(Size(pCurrent->Width(), 0), MapMode(MapUnit::Map10thMM)).Width(); + tools::Long nWidthPixel = LogicToPixel(Size(nForeignWidthLogic, 0), MapMode(MapUnit::Map10thMM)).Width(); + + pCurrent->SetWidth(nWidthPixel, GetZoom()); + if ( pBar ) + pBar->SetItemSize( pCurrent->GetId(), pCurrent->Width() ); + } + + pDev->Pop(); +} + +void BrowseBox::ImplPaintData(OutputDevice& _rOut, const tools::Rectangle& _rRect, bool _bForeignDevice) +{ + Point aOverallAreaPos = _bForeignDevice ? _rRect.TopLeft() : Point(0,0); + Size aOverallAreaSize = _bForeignDevice ? _rRect.GetSize() : pDataWin->GetOutputSizePixel(); + Point aOverallAreaBRPos = _bForeignDevice ? _rRect.BottomRight() : Point( aOverallAreaSize.Width(), aOverallAreaSize.Height() ); + + tools::Long nDataRowHeight = GetDataRowHeight(); + + // compute relative rows to redraw + sal_uLong nRelTopRow = 0; + sal_uLong nRelBottomRow = aOverallAreaSize.Height(); + if (!_bForeignDevice && nDataRowHeight) + { + nRelTopRow = (static_cast<sal_uLong>(_rRect.Top()) / nDataRowHeight); + nRelBottomRow = static_cast<sal_uLong>(_rRect.Bottom()) / nDataRowHeight; + } + + // cache frequently used values + Point aPos( aOverallAreaPos.X(), nRelTopRow * nDataRowHeight + aOverallAreaPos.Y() ); + _rOut.SetLineColor( COL_WHITE ); + const AllSettings& rAllSets = _rOut.GetSettings(); + const StyleSettings &rSettings = rAllSets.GetStyleSettings(); + const Color &rHighlightTextColor = rSettings.GetHighlightTextColor(); + const Color &rHighlightFillColor = rSettings.GetHighlightColor(); + Color aOldTextColor = _rOut.GetTextColor(); + Color aOldFillColor = _rOut.GetFillColor(); + Color aOldLineColor = _rOut.GetLineColor(); + tools::Long nHLineX = 0 == mvCols[ 0 ]->GetId() ? mvCols[ 0 ]->Width() : 0; + nHLineX += aOverallAreaPos.X(); + + Color aDelimiterLineColor( ::svtools::ColorConfig().GetColorValue( ::svtools::CALCGRID ).nColor ); + + // redraw the invalid fields + for ( sal_uLong nRelRow = nRelTopRow; + nRelRow <= nRelBottomRow && static_cast<sal_uLong>(nTopRow)+nRelRow < o3tl::make_unsigned(nRowCount); + ++nRelRow, aPos.AdjustY(nDataRowHeight ) ) + { + // get row + // check valid area, to be on the safe side: + DBG_ASSERT( static_cast<sal_uInt16>(nTopRow+nRelRow) < nRowCount, "BrowseBox::ImplPaintData: invalid seek" ); + if ( (nTopRow+tools::Long(nRelRow)) < 0 || static_cast<sal_uInt16>(nTopRow+nRelRow) >= nRowCount ) + continue; + + // prepare row + sal_uLong nRow = nTopRow+nRelRow; + if ( !SeekRow( nRow) ) { + OSL_FAIL("BrowseBox::ImplPaintData: SeekRow failed"); + } + _rOut.SetClipRegion(); + aPos.setX( aOverallAreaPos.X() ); + + + // #73325# don't paint the row outside the painting rectangle (DG) + // prepare auto-highlight + tools::Rectangle aRowRect( Point( _rRect.Left(), aPos.Y() ), + Size( _rRect.GetSize().Width(), nDataRowHeight ) ); + + bool bRowSelected = !bHideSelect + && IsRowSelected( nRow ); + if ( bRowSelected ) + { + _rOut.SetTextColor( rHighlightTextColor ); + _rOut.SetFillColor( rHighlightFillColor ); + _rOut.SetLineColor(); + _rOut.DrawRect( aRowRect ); + } + + // iterate through columns to redraw + size_t nCol; + for ( nCol = 0; nCol < mvCols.size(); ++nCol ) + { + // get column + BrowserColumn *pCol = mvCols[ nCol ].get(); + + // at end of invalid area + if ( aPos.X() >= _rRect.Right() ) + break; + + // skip invisible columns between frozen and scrollable area + if ( nCol < nFirstCol && !pCol->IsFrozen() ) + { + nCol = nFirstCol; + pCol = (nCol < mvCols.size() ) ? mvCols[ nCol ].get() : nullptr; + if (!pCol) + { // FS - 21.05.99 - 66325 + // actually this has been fixed elsewhere (in the right place), + // but let's make sure... + OSL_FAIL("BrowseBox::PaintData : nFirstCol is probably invalid !"); + break; + } + } + + // prepare Column-AutoHighlight + bool bColAutoHighlight = bColumnCursor + && IsColumnSelected( pCol->GetId() ); + if ( bColAutoHighlight ) + { + _rOut.SetClipRegion(); + _rOut.SetTextColor( rHighlightTextColor ); + _rOut.SetFillColor( rHighlightFillColor ); + _rOut.SetLineColor(); + tools::Rectangle aFieldRect( aPos, + Size( pCol->Width(), nDataRowHeight ) ); + _rOut.DrawRect( aFieldRect ); + } + + if (!m_bFocusOnlyCursor && (pCol->GetId() == GetCurColumnId()) && (nRow == static_cast<sal_uLong>(GetCurRow()))) + DrawCursor(); + + // draw a single field. + // else something is drawn to, e.g. handle column + if (pCol->Width()) + { + // clip the column's output to the field area + if (_bForeignDevice) + { // (not necessary if painting onto the data window) + Size aFieldSize(pCol->Width(), nDataRowHeight); + + if (aPos.X() + aFieldSize.Width() > aOverallAreaBRPos.X()) + aFieldSize.setWidth( aOverallAreaBRPos.X() - aPos.X() ); + + if (aPos.Y() + aFieldSize.Height() > aOverallAreaBRPos.Y() + 1) + { + // for non-handle cols we don't clip vertically : we just don't draw the cell if the line isn't completely visible + if (pCol->GetId() != 0) + continue; + aFieldSize.setHeight( aOverallAreaBRPos.Y() + 1 - aPos.Y() ); + } + + vcl::Region aClipToField(tools::Rectangle(aPos, aFieldSize)); + _rOut.SetClipRegion(aClipToField); + } + pCol->Draw( *this, _rOut, aPos ); + if (_bForeignDevice) + _rOut.SetClipRegion(); + } + + // reset Column-auto-highlight + if ( bColAutoHighlight ) + { + _rOut.SetTextColor( aOldTextColor ); + _rOut.SetFillColor( aOldFillColor ); + _rOut.SetLineColor( aOldLineColor ); + } + + // skip column + aPos.AdjustX(pCol->Width() ); + } + + // reset auto-highlight + if ( bRowSelected ) + { + _rOut.SetTextColor( aOldTextColor ); + _rOut.SetFillColor( aOldFillColor ); + _rOut.SetLineColor( aOldLineColor ); + } + + if ( bHLines ) + { + // draw horizontal delimitation lines + _rOut.SetClipRegion(); + _rOut.Push( vcl::PushFlags::LINECOLOR ); + _rOut.SetLineColor( aDelimiterLineColor ); + tools::Long nY = aPos.Y() + nDataRowHeight - 1; + if (nY <= aOverallAreaBRPos.Y()) + _rOut.DrawLine( Point( nHLineX, nY ), + Point( bVLines + ? std::min(tools::Long(aPos.X() - 1), aOverallAreaBRPos.X()) + : aOverallAreaBRPos.X(), + nY ) ); + _rOut.Pop(); + } + } + + if (aPos.Y() > aOverallAreaBRPos.Y() + 1) + aPos.setY( aOverallAreaBRPos.Y() + 1 ); + // needed for some of the following drawing + + // retouching + _rOut.SetClipRegion(); + aOldLineColor = _rOut.GetLineColor(); + aOldFillColor = _rOut.GetFillColor(); + _rOut.SetFillColor( rSettings.GetFaceColor() ); + if ( !mvCols.empty() && ( mvCols[ 0 ]->GetId() == 0 ) && ( aPos.Y() <= _rRect.Bottom() ) ) + { + // fill rectangle gray below handle column + // DG: fill it only until the end of the drawing rect and not to the end, as this may overpaint handle columns + _rOut.SetLineColor( COL_BLACK ); + _rOut.DrawRect( tools::Rectangle( + Point( aOverallAreaPos.X() - 1, aPos.Y() - 1 ), + Point( aOverallAreaPos.X() + mvCols[ 0 ]->Width() - 1, + _rRect.Bottom() + 1) ) ); + } + _rOut.SetFillColor( aOldFillColor ); + + // draw vertical delimitational line between frozen and scrollable cols + _rOut.SetLineColor( COL_BLACK ); + tools::Long nFrozenWidth = GetFrozenWidth()-1; + _rOut.DrawLine( Point( aOverallAreaPos.X() + nFrozenWidth, aPos.Y() ), + Point( aOverallAreaPos.X() + nFrozenWidth, bHLines + ? aPos.Y() - 1 + : aOverallAreaBRPos.Y() ) ); + + // draw vertical delimitational lines? + if ( bVLines ) + { + _rOut.SetLineColor( aDelimiterLineColor ); + Point aVertPos( aOverallAreaPos.X() - 1, aOverallAreaPos.Y() ); + tools::Long nDeltaY = aOverallAreaBRPos.Y(); + for ( size_t nCol = 0; nCol < mvCols.size(); ++nCol ) + { + // get column + BrowserColumn *pCol = mvCols[ nCol ].get(); + + // skip invisible columns between frozen and scrollable area + if ( nCol < nFirstCol && !pCol->IsFrozen() ) + { + nCol = nFirstCol; + pCol = mvCols[ nCol ].get(); + } + + // skip column + aVertPos.AdjustX(pCol->Width() ); + + // at end of invalid area + // invalid area is first reached when X > Right + // and not >= + if ( aVertPos.X() > _rRect.Right() ) + break; + + // draw a single line + if ( pCol->GetId() != 0 ) + _rOut.DrawLine( aVertPos, Point( aVertPos.X(), + bHLines + ? aPos.Y() - 1 + : aPos.Y() + nDeltaY ) ); + } + } + + _rOut.SetLineColor( aOldLineColor ); +} + +void BrowseBox::PaintData( vcl::Window const & rWin, vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) +{ + if (!bBootstrapped && IsReallyVisible()) + BrowseBox::StateChanged(StateChangedType::InitShow); + + // initializations + if (mvCols.empty() || !rWin.IsUpdateMode()) + return; + if (pDataWin->bResizeOnPaint) + Resize(); + // MI: who was that? Window::Update(); + + ImplPaintData(rRenderContext, rRect, false); +} + +void BrowseBox::UpdateScrollbars() +{ + + if ( !bBootstrapped || !IsUpdateMode() ) + return; + + // protect against recursion + if ( pDataWin->bInUpdateScrollbars ) + { + pDataWin->bHadRecursion = true; + return; + } + pDataWin->bInUpdateScrollbars = true; + + // the size of the corner window (and the width of the VSB/height of the HSB) + sal_uLong nCornerSize = GetBarHeight(); + if (IsZoom()) + nCornerSize = static_cast<sal_uLong>(nCornerSize * static_cast<double>(GetZoom())); + + bool bNeedsVScroll = false; + sal_Int32 nMaxRows = 0; + if (GetDataRowHeight()) + { + // needs VScroll? + nMaxRows = (pDataWin->GetSizePixel().Height()) / GetDataRowHeight(); + bNeedsVScroll = pDataWin->bAutoVScroll + ? nTopRow || ( nRowCount > nMaxRows ) + : !pDataWin->bNoVScroll; + } + Size aDataWinSize = pDataWin->GetSizePixel(); + if ( !bNeedsVScroll ) + { + if ( pVScroll->IsVisible() ) + { + pVScroll->Hide(); + Size aNewSize( aDataWinSize ); + aNewSize.setWidth( GetOutputSizePixel().Width() ); + aDataWinSize = aNewSize; + } + } + else if ( !pVScroll->IsVisible() ) + { + Size aNewSize( aDataWinSize ); + aNewSize.setWidth( GetOutputSizePixel().Width() - nCornerSize ); + aDataWinSize = aNewSize; + } + + // needs HScroll? + sal_uLong nLastCol = GetColumnAtXPosPixel( aDataWinSize.Width() - 1 ); + + sal_uInt16 nFrozenCols = FrozenColCount(); + bool bNeedsHScroll = pDataWin->bAutoHScroll + ? ( nFirstCol > nFrozenCols ) || ( nLastCol <= mvCols.size() ) + : !pDataWin->bNoHScroll; + if ( !bNeedsHScroll ) + { + if ( aHScroll->IsVisible() ) + { + aHScroll->Hide(); + } + aDataWinSize.setHeight( GetOutputSizePixel().Height() - GetTitleHeight() ); + if ( nControlAreaWidth != USHRT_MAX ) + aDataWinSize.AdjustHeight( -sal_Int32(nCornerSize) ); + } + else if ( !aHScroll->IsVisible() ) + { + Size aNewSize( aDataWinSize ); + aNewSize.setHeight( GetOutputSizePixel().Height() - GetTitleHeight() - nCornerSize ); + aDataWinSize = aNewSize; + } + + // adjust position and Width of horizontal scrollbar + sal_uLong nHScrX = nControlAreaWidth == USHRT_MAX + ? 0 + : nControlAreaWidth; + + aHScroll->SetPosSizePixel( + Point( nHScrX, GetOutputSizePixel().Height() - nCornerSize ), + Size( aDataWinSize.Width() - nHScrX, nCornerSize ) ); + + // total scrollable columns + short nScrollCols = short(mvCols.size()) - static_cast<short>(nFrozenCols); + + // visible columns + short nVisibleHSize = nLastCol == BROWSER_INVALIDID + ? static_cast<short>( mvCols.size() - nFirstCol ) + : static_cast<short>( nLastCol - nFirstCol ); + + short nRange = std::max( nScrollCols, short(0) ); + aHScroll->SetVisibleSize( nVisibleHSize ); + aHScroll->SetRange( Range( 0, nRange )); + if ( bNeedsHScroll && !aHScroll->IsVisible() ) + aHScroll->Show(); + + // adjust position and height of vertical scrollbar + pVScroll->SetPageSize( nMaxRows ); + + if ( nTopRow > nRowCount ) + { + nTopRow = nRowCount - 1; + OSL_FAIL("BrowseBox: nTopRow > nRowCount"); + } + + if ( pVScroll->GetThumbPos() != nTopRow ) + pVScroll->SetThumbPos( nTopRow ); + tools::Long nVisibleSize = std::min( std::min( nRowCount, nMaxRows ), (nRowCount-nTopRow) ); + pVScroll->SetVisibleSize( nVisibleSize ? nVisibleSize : 1 ); + pVScroll->SetRange( Range( 0, nRowCount ) ); + pVScroll->SetPosSizePixel( + Point( aDataWinSize.Width(), GetTitleHeight() ), + Size( nCornerSize, aDataWinSize.Height()) ); + tools::Long nLclDataRowHeight = GetDataRowHeight(); + if ( nLclDataRowHeight > 0 && nRowCount < tools::Long( aDataWinSize.Height() / nLclDataRowHeight ) ) + ScrollRows( -nTopRow ); + if ( bNeedsVScroll && !pVScroll->IsVisible() ) + pVScroll->Show(); + + pDataWin->SetPosSizePixel( + Point( 0, GetTitleHeight() ), + aDataWinSize ); + + // needs corner-window? + // (do that AFTER positioning BOTH scrollbars) + sal_uLong nActualCorderWidth = 0; + if (aHScroll->IsVisible() && pVScroll && pVScroll->IsVisible() ) + { + // if we have both scrollbars, the corner window fills the point of intersection of these two + nActualCorderWidth = nCornerSize; + } + else if ( !aHScroll->IsVisible() && ( nControlAreaWidth != USHRT_MAX ) ) + { + // if we have no horizontal scrollbar, but a control area, we need the corner window to + // fill the space between the control are and the right border + nActualCorderWidth = GetOutputSizePixel().Width() - nControlAreaWidth; + } + if ( nActualCorderWidth ) + { + if ( !pDataWin->pCornerWin ) + pDataWin->pCornerWin = VclPtr<ScrollBarBox>::Create( this, 0 ); + pDataWin->pCornerWin->SetPosSizePixel( + Point( GetOutputSizePixel().Width() - nActualCorderWidth, aHScroll->GetPosPixel().Y() ), + Size( nActualCorderWidth, nCornerSize ) ); + pDataWin->pCornerWin->Show(); + } + else + pDataWin->pCornerWin.disposeAndClear(); + + // scroll headerbar, if necessary + if ( pDataWin->pHeaderBar ) + { + tools::Long nWidth = 0; + for ( size_t nCol = 0; + nCol < mvCols.size() && nCol < nFirstCol; + ++nCol ) + { + // not the handle column + if ( mvCols[ nCol ]->GetId() ) + nWidth += mvCols[ nCol ]->Width(); + } + + pDataWin->pHeaderBar->SetOffset( nWidth ); + } + + pDataWin->bInUpdateScrollbars = false; + if ( pDataWin->bHadRecursion ) + { + pDataWin->bHadRecursion = false; + UpdateScrollbars(); + } +} + + +void BrowseBox::SetUpdateMode( bool bUpdate ) +{ + + bool bWasUpdate = IsUpdateMode(); + if ( bWasUpdate == bUpdate ) + return; + + Control::SetUpdateMode( bUpdate ); + // If WB_CLIPCHILDREN is st at the BrowseBox (to minimize flicker), + // the data window is not invalidated by SetUpdateMode. + if( bUpdate ) + pDataWin->Invalidate(); + pDataWin->SetUpdateMode( bUpdate ); + + + if ( bUpdate ) + { + if ( bBootstrapped ) + { + UpdateScrollbars(); + AutoSizeLastColumn(); + } + DoShowCursor(); + } + else + DoHideCursor(); +} + + +bool BrowseBox::GetUpdateMode() const +{ + + return pDataWin->IsUpdateMode(); +} + + +tools::Long BrowseBox::GetFrozenWidth() const +{ + + tools::Long nWidth = 0; + for ( size_t nCol = 0; + nCol < mvCols.size() && mvCols[ nCol ]->IsFrozen(); + ++nCol ) + nWidth += mvCols[ nCol ]->Width(); + return nWidth; +} + + +void BrowseBox::ColumnInserted( sal_uInt16 nPos ) +{ + if ( pColSel ) + pColSel->Insert( nPos ); + UpdateScrollbars(); +} + + +sal_uInt16 BrowseBox::FrozenColCount() const +{ + std::size_t nCol; + for ( nCol = 0; + nCol < mvCols.size() && mvCols[ nCol ]->IsFrozen(); + ++nCol ) + /* empty loop */; + return nCol; //TODO: BrowserColumns::size_type -> sal_uInt16! +} + + +IMPL_LINK(BrowseBox, ScrollHdl, ScrollBar*, pBar, void) +{ + if ( pBar->GetDelta() == 0 ) + return; + + if ( pBar == aHScroll.get() ) + ScrollColumns( aHScroll->GetDelta() ); + if ( pBar == pVScroll ) + ScrollRows( pVScroll->GetDelta() ); +} + + +IMPL_LINK( BrowseBox, StartDragHdl, HeaderBar*, pBar, void ) +{ + pBar->SetDragSize( pDataWin->GetOutputSizePixel().Height() ); +} + + +// usually only the first column was resized + +void BrowseBox::MouseButtonDown( const MouseEvent& rEvt ) +{ + + GrabFocus(); + + // only mouse events in the title-line are supported + const Point &rEvtPos = rEvt.GetPosPixel(); + if ( rEvtPos.Y() >= GetTitleHeight() ) + return; + + tools::Long nX = 0; + tools::Long nWidth = GetOutputSizePixel().Width(); + for ( size_t nCol = 0; nCol < mvCols.size() && nX < nWidth; ++nCol ) + { + // is this column visible? + BrowserColumn *pCol = mvCols[ nCol ].get(); + if ( pCol->IsFrozen() || nCol >= nFirstCol ) + { + // compute right end of column + tools::Long nR = nX + pCol->Width() - 1; + + // at the end of a column (and not handle column)? + if ( pCol->GetId() && std::abs( nR - rEvtPos.X() ) < 2 ) + { + // start resizing the column + bResizing = true; + nResizeCol = nCol; + nDragX = nResizeX = rEvtPos.X(); + SetPointer( PointerStyle::HSplit ); + CaptureMouse(); + pDataWin->GetOutDev()->DrawLine( Point( nDragX, 0 ), + Point( nDragX, pDataWin->GetSizePixel().Height() ) ); + nMinResizeX = nX + MIN_COLUMNWIDTH; + return; + } + else if ( nX < rEvtPos.X() && nR > rEvtPos.X() ) + { + MouseButtonDown( BrowserMouseEvent( + this, rEvt, -1, nCol, pCol->GetId(), tools::Rectangle() ) ); + return; + } + nX = nR + 1; + } + } + + // event occurred out of data area + if ( rEvt.IsRight() ) + pDataWin->Command( + CommandEvent( Point( 1, LONG_MAX ), CommandEventId::ContextMenu, true ) ); + else + SetNoSelection(); +} + + +void BrowseBox::MouseMove( const MouseEvent& rEvt ) +{ + SAL_INFO("svtools", "BrowseBox::MouseMove( MouseEvent )" ); + + PointerStyle aNewPointer = PointerStyle::Arrow; + + sal_uInt16 nX = 0; + for ( size_t nCol = 0; + nCol < mvCols.size() && + ( nX + mvCols[ nCol ]->Width() ) < o3tl::make_unsigned(GetOutputSizePixel().Width()); + ++nCol ) + // is this column visible? + if ( mvCols[ nCol ]->IsFrozen() || nCol >= nFirstCol ) + { + // compute right end of column + BrowserColumn *pCol = mvCols[ nCol ].get(); + sal_uInt16 nR = static_cast<sal_uInt16>(nX + pCol->Width() - 1); + + // show resize-pointer? + if ( bResizing || ( pCol->GetId() && + std::abs( static_cast<tools::Long>(nR) - rEvt.GetPosPixel().X() ) < MIN_COLUMNWIDTH ) ) + { + aNewPointer = PointerStyle::HSplit; + if ( bResizing ) + { + // delete old auxiliary line + pDataWin->HideTracking() ; + + // check allowed width and new delta + nDragX = std::max( rEvt.GetPosPixel().X(), nMinResizeX ); + tools::Long nDeltaX = nDragX - nResizeX; + sal_uInt16 nId = GetColumnId(nResizeCol); + sal_uLong nOldWidth = GetColumnWidth(nId); + nDragX = nOldWidth + nDeltaX + nResizeX - nOldWidth; + + // draw new auxiliary line + pDataWin->ShowTracking( tools::Rectangle( Point( nDragX, 0 ), + Size( 1, pDataWin->GetSizePixel().Height() ) ), + ShowTrackFlags::Split|ShowTrackFlags::TrackWindow ); + } + + } + + nX = nR + 1; + } + + SetPointer( aNewPointer ); +} + + +void BrowseBox::MouseButtonUp( const MouseEvent & rEvt ) +{ + + if ( bResizing ) + { + // delete auxiliary line + pDataWin->HideTracking(); + + // width changed? + nDragX = std::max( rEvt.GetPosPixel().X(), nMinResizeX ); + if ( (nDragX - nResizeX) != static_cast<tools::Long>(mvCols[ nResizeCol ]->Width()) ) + { + // resize column + tools::Long nMaxX = pDataWin->GetSizePixel().Width(); + nDragX = std::min( nDragX, nMaxX ); + tools::Long nDeltaX = nDragX - nResizeX; + sal_uInt16 nId = GetColumnId(nResizeCol); + SetColumnWidth( GetColumnId(nResizeCol), GetColumnWidth(nId) + nDeltaX ); + ColumnResized( nId ); + } + + // end action + SetPointer( PointerStyle::Arrow ); + ReleaseMouse(); + bResizing = false; + } + else + MouseButtonUp( BrowserMouseEvent( pDataWin, + MouseEvent( Point( rEvt.GetPosPixel().X(), + rEvt.GetPosPixel().Y() - pDataWin->GetPosPixel().Y() ), + rEvt.GetClicks(), rEvt.GetMode(), rEvt.GetButtons(), + rEvt.GetModifier() ) ) ); +} + + +static bool bExtendedMode = false; +static bool bFieldMode = false; + +void BrowseBox::MouseButtonDown( const BrowserMouseEvent& rEvt ) +{ + + GrabFocus(); + + // adjust selection while and after double-click + if ( rEvt.GetClicks() == 2 ) + { + SetNoSelection(); + if ( rEvt.GetRow() >= 0 ) + { + GoToRow( rEvt.GetRow() ); + SelectRow( rEvt.GetRow(), true, false ); + } + else + { + if ( bColumnCursor && rEvt.GetColumn() != 0 ) + { + if ( rEvt.GetColumn() < mvCols.size() ) + SelectColumnPos( rEvt.GetColumn(), true, false); + } + } + DoubleClick( rEvt ); + } + // selections + else if ( ( rEvt.GetMode() & ( MouseEventModifiers::SELECT | MouseEventModifiers::SIMPLECLICK ) ) && + ( bColumnCursor || rEvt.GetRow() >= 0 ) ) + { + if ( rEvt.GetClicks() == 1 ) + { + // initialise flags + bHit = false; + + // selection out of range? + if ( rEvt.GetRow() >= nRowCount || + rEvt.GetColumnId() == BROWSER_INVALIDID ) + { + SetNoSelection(); + return; + } + + // while selecting, no cursor + bSelecting = true; + DoHideCursor(); + + // DataRow? + if ( rEvt.GetRow() >= 0 ) + { + // line selection? + if ( rEvt.GetColumnId() == HandleColumnId || !bColumnCursor ) + { + if ( bMultiSelection ) + { + // remove column-selection, if exists + if ( pColSel && pColSel->GetSelectCount() ) + { + ToggleSelection(); + if ( bMultiSelection ) + uRow.pSel->SelectAll(false); + else + uRow.nSel = BROWSER_ENDOFSELECTION; + if ( pColSel ) + pColSel->SelectAll(false); + bSelect = true; + } + + // expanding mode? + if ( rEvt.GetMode() & MouseEventModifiers::RANGESELECT ) + { + // select the further touched rows too + bSelect = true; + ExpandRowSelection( rEvt ); + return; + } + + // click in the selected area? + else if ( IsRowSelected( rEvt.GetRow() ) ) + { + // wait for Drag&Drop + bHit = true; + bExtendedMode = bool( rEvt.GetMode() & MouseEventModifiers::MULTISELECT ); + return; + } + + // extension mode? + else if ( rEvt.GetMode() & MouseEventModifiers::MULTISELECT ) + { + // determine the new selection range + // and selection/deselection + aSelRange = Range( rEvt.GetRow(), rEvt.GetRow() ); + SelectRow( rEvt.GetRow(), + !uRow.pSel->IsSelected( rEvt.GetRow() ) ); + bSelect = true; + return; + } + } + + // select directly + SetNoSelection(); + GoToRow( rEvt.GetRow() ); + SelectRow( rEvt.GetRow() ); + aSelRange = Range( rEvt.GetRow(), rEvt.GetRow() ); + bSelect = true; + } + else // Column/Field-Selection + { + // click in selected column + if ( IsColumnSelected( rEvt.GetColumn() ) || + IsRowSelected( rEvt.GetRow() ) ) + { + bHit = true; + bFieldMode = true; + return; + } + + SetNoSelection(); + GoToRowColumnId( rEvt.GetRow(), rEvt.GetColumnId() ); + bSelect = true; + } + } + else + { + if ( bMultiSelection && rEvt.GetColumnId() == HandleColumnId ) + { + // toggle all-selection + if ( uRow.pSel->GetSelectCount() > ( GetRowCount() / 2 ) ) + SetNoSelection(); + else + SelectAll(); + } + else + SelectColumnPos( GetColumnPos(rEvt.GetColumnId()), true, false); + } + + // turn cursor on again, if necessary + bSelecting = false; + DoShowCursor(); + if ( bSelect ) + Select(); + } + } +} + + +void BrowseBox::MouseButtonUp( const BrowserMouseEvent &rEvt ) +{ + + // D&D was possible, but did not occur + if ( bHit ) + { + aSelRange = Range( rEvt.GetRow(), rEvt.GetRow() ); + if ( bExtendedMode ) + SelectRow( rEvt.GetRow(), false ); + else + { + SetNoSelection(); + if ( bFieldMode ) + GoToRowColumnId( rEvt.GetRow(), rEvt.GetColumnId() ); + else + { + GoToRow( rEvt.GetRow() ); + SelectRow( rEvt.GetRow() ); + } + } + bSelect = true; + bExtendedMode = false; + bFieldMode = false; + bHit = false; + } + + // activate cursor + if ( bSelecting ) + { + bSelecting = false; + DoShowCursor(); + if ( bSelect ) + Select(); + } +} + + +void BrowseBox::KeyInput( const KeyEvent& rEvt ) +{ + if ( !ProcessKey( rEvt ) ) + Control::KeyInput( rEvt ); +} + + +bool BrowseBox::ProcessKey( const KeyEvent& rEvt ) +{ + + sal_uInt16 nCode = rEvt.GetKeyCode().GetCode(); + bool bShift = rEvt.GetKeyCode().IsShift(); + bool bCtrl = rEvt.GetKeyCode().IsMod1(); + bool bAlt = rEvt.GetKeyCode().IsMod2(); + + sal_uInt16 nId = BROWSER_NONE; + + if ( !bAlt && !bCtrl && !bShift ) + { + switch ( nCode ) + { + case KEY_DOWN: nId = BROWSER_CURSORDOWN; break; + case KEY_UP: nId = BROWSER_CURSORUP; break; + case KEY_HOME: nId = BROWSER_CURSORHOME; break; + case KEY_END: nId = BROWSER_CURSOREND; break; + case KEY_TAB: + if ( !bColumnCursor ) + break; + [[fallthrough]]; + case KEY_RIGHT: nId = BROWSER_CURSORRIGHT; break; + case KEY_LEFT: nId = BROWSER_CURSORLEFT; break; + case KEY_SPACE: nId = BROWSER_SELECT; break; + } + if ( BROWSER_NONE != nId ) + SetNoSelection(); + + switch ( nCode ) + { + case KEY_PAGEDOWN: nId = BROWSER_CURSORPAGEDOWN; break; + case KEY_PAGEUP: nId = BROWSER_CURSORPAGEUP; break; + } + } + + if ( !bAlt && !bCtrl && bShift ) + switch ( nCode ) + { + case KEY_DOWN: nId = BROWSER_SELECTDOWN; break; + case KEY_UP: nId = BROWSER_SELECTUP; break; + case KEY_TAB: + if ( !bColumnCursor ) + break; + nId = BROWSER_CURSORLEFT; break; + case KEY_HOME: nId = BROWSER_SELECTHOME; break; + case KEY_END: nId = BROWSER_SELECTEND; break; + } + + + if ( !bAlt && bCtrl && !bShift ) + switch ( nCode ) + { + case KEY_DOWN: nId = BROWSER_CURSORDOWN; break; + case KEY_UP: nId = BROWSER_CURSORUP; break; + case KEY_PAGEDOWN: nId = BROWSER_CURSORENDOFFILE; break; + case KEY_PAGEUP: nId = BROWSER_CURSORTOPOFFILE; break; + case KEY_HOME: nId = BROWSER_CURSORTOPOFSCREEN; break; + case KEY_END: nId = BROWSER_CURSORENDOFSCREEN; break; + case KEY_SPACE: nId = BROWSER_ENHANCESELECTION; break; + case KEY_LEFT: nId = BROWSER_MOVECOLUMNLEFT; break; + case KEY_RIGHT: nId = BROWSER_MOVECOLUMNRIGHT; break; + } + + if ( nId != BROWSER_NONE ) + Dispatch( nId ); + return nId != BROWSER_NONE; +} + +void BrowseBox::ChildFocusIn() +{ +} + +void BrowseBox::ChildFocusOut() +{ +} + +void BrowseBox::Dispatch( sal_uInt16 nId ) +{ + + tools::Long nRowsOnPage = pDataWin->GetSizePixel().Height() / GetDataRowHeight(); + + switch ( nId ) + { + case BROWSER_SELECTCOLUMN: + if ( ColCount() ) + SelectColumnId( GetCurColumnId() ); + break; + + case BROWSER_CURSORDOWN: + if ( ( GetCurRow() + 1 ) < nRowCount ) + GoToRow( GetCurRow() + 1, false ); + break; + case BROWSER_CURSORUP: + if ( GetCurRow() > 0 ) + GoToRow( GetCurRow() - 1, false ); + break; + case BROWSER_SELECTHOME: + if ( GetRowCount() ) + { + DoHideCursor(); + for ( sal_Int32 nRow = GetCurRow(); nRow >= 0; --nRow ) + SelectRow( nRow ); + GoToRow( 0, true ); + DoShowCursor(); + } + break; + case BROWSER_SELECTEND: + if ( GetRowCount() ) + { + DoHideCursor(); + sal_Int32 nRows = GetRowCount(); + for ( sal_Int32 nRow = GetCurRow(); nRow < nRows; ++nRow ) + SelectRow( nRow ); + GoToRow( GetRowCount() - 1, true ); + DoShowCursor(); + } + break; + case BROWSER_SELECTDOWN: + { + if ( GetRowCount() && ( GetCurRow() + 1 ) < nRowCount ) + { + // deselect the current row, if it isn't the first + // and there is no other selected row above + sal_Int32 nRow = GetCurRow(); + bool bLocalSelect = ( !IsRowSelected( nRow ) || + GetSelectRowCount() == 1 || IsRowSelected( nRow - 1 ) ); + SelectRow( nRow, bLocalSelect ); + bool bDone = GoToRow( GetCurRow() + 1, false ); + if ( bDone ) + SelectRow( GetCurRow() ); + } + else + ScrollRows( 1 ); + break; + } + case BROWSER_SELECTUP: + if ( GetRowCount() ) + { + // deselect the current row, if it isn't the first + // and there is no other selected row under + sal_Int32 nRow = GetCurRow(); + bool bLocalSelect = ( !IsRowSelected( nRow ) || + GetSelectRowCount() == 1 || IsRowSelected( nRow + 1 ) ); + SelectRow( nCurRow, bLocalSelect ); + bool bDone = GoToRow( nRow - 1, false ); + if ( bDone ) + SelectRow( GetCurRow() ); + } + break; + case BROWSER_CURSORPAGEDOWN: + ScrollRows( nRowsOnPage ); + break; + case BROWSER_CURSORPAGEUP: + ScrollRows( -nRowsOnPage ); + break; + case BROWSER_CURSOREND: + if ( bColumnCursor ) + { + sal_uInt16 nNewId = GetColumnId(ColCount() -1); + nNewId != HandleColumnId && GoToColumnId( nNewId ); + break; + } + [[fallthrough]]; + case BROWSER_CURSORENDOFFILE: + GoToRow( nRowCount - 1, false ); + break; + case BROWSER_CURSORRIGHT: + if ( bColumnCursor ) + { + sal_uInt16 nNewPos = GetColumnPos( GetCurColumnId() ) + 1; + sal_uInt16 nNewId = GetColumnId( nNewPos ); + if (nNewId != BROWSER_INVALIDID) // At end of row ? + GoToColumnId( nNewId ); + else + { + sal_uInt16 nColId = GetColumnId(0); + if ( nColId == BROWSER_INVALIDID || nColId == HandleColumnId ) + nColId = GetColumnId(1); + if ( GetRowCount() ) + { + if ( nCurRow < GetRowCount() - 1 ) + { + GoToRowColumnId( nCurRow + 1, nColId ); + } + } + else if ( ColCount() ) + GoToColumnId( nColId ); + } + } + else + ScrollColumns( 1 ); + break; + case BROWSER_CURSORHOME: + if ( bColumnCursor ) + { + sal_uInt16 nNewId = GetColumnId(1); + if (nNewId != HandleColumnId) + { + GoToColumnId( nNewId ); + } + break; + } + [[fallthrough]]; + case BROWSER_CURSORTOPOFFILE: + GoToRow( 0, false ); + break; + case BROWSER_CURSORLEFT: + if ( bColumnCursor ) + { + sal_uInt16 nNewPos = GetColumnPos( GetCurColumnId() ) - 1; + sal_uInt16 nNewId = GetColumnId( nNewPos ); + if (nNewId != HandleColumnId) + GoToColumnId( nNewId ); + else + { + if ( GetRowCount() ) + { + if (nCurRow > 0) + { + GoToRowColumnId(nCurRow - 1, GetColumnId(ColCount() -1)); + } + } + else if ( ColCount() ) + GoToColumnId( GetColumnId(ColCount() -1) ); + } + } + else + ScrollColumns( -1 ); + break; + case BROWSER_ENHANCESELECTION: + if ( GetRowCount() ) + SelectRow( GetCurRow(), !IsRowSelected( GetCurRow() ) ); + break; + case BROWSER_SELECT: + if ( GetRowCount() ) + SelectRow( GetCurRow(), !IsRowSelected( GetCurRow() ), false ); + break; + case BROWSER_MOVECOLUMNLEFT: + case BROWSER_MOVECOLUMNRIGHT: + { // check if column moving is allowed + BrowserHeader* pHeaderBar = pDataWin->pHeaderBar; + if ( pHeaderBar && pHeaderBar->IsDragable() ) + { + sal_uInt16 nColId = GetCurColumnId(); + bool bColumnSelected = IsColumnSelected(nColId); + sal_uInt16 nNewPos = GetColumnPos(nColId); + bool bMoveAllowed = false; + if ( BROWSER_MOVECOLUMNLEFT == nId && nNewPos > 1 ) + { + --nNewPos; + bMoveAllowed = true; + } + else if ( BROWSER_MOVECOLUMNRIGHT == nId && nNewPos < (ColCount()-1) ) + { + ++nNewPos; + bMoveAllowed = true; + } + + if ( bMoveAllowed ) + { + SetColumnPos( nColId, nNewPos ); + ColumnMoved( nColId ); + MakeFieldVisible(GetCurRow(), nColId); + if ( bColumnSelected ) + SelectColumnId(nColId); + } + } + } + break; + } +} + + +void BrowseBox::SetCursorColor(const Color& _rCol) +{ + if (_rCol == m_aCursorColor) + return; + + // ensure the cursor is hidden + DoHideCursor(); + if (!m_bFocusOnlyCursor) + DoHideCursor(); + + m_aCursorColor = _rCol; + + if (!m_bFocusOnlyCursor) + DoShowCursor(); + DoShowCursor(); +} + +tools::Rectangle BrowseBox::calcHeaderRect(bool _bIsColumnBar, bool _bOnScreen) +{ + vcl::Window* pParent = nullptr; + if ( !_bOnScreen ) + pParent = GetAccessibleParentWindow(); + + Point aTopLeft; + tools::Long nWidth; + tools::Long nHeight; + if ( _bIsColumnBar ) + { + nWidth = pDataWin->GetOutputSizePixel().Width(); + nHeight = GetDataRowHeight(); + } + else + { + aTopLeft.setY( GetDataRowHeight() ); + nWidth = GetColumnWidth(0); + nHeight = GetWindowExtentsRelative( pParent ).GetHeight() - aTopLeft.Y() - GetControlArea().GetSize().Height(); + } + aTopLeft += GetWindowExtentsRelative( pParent ).TopLeft(); + return tools::Rectangle(aTopLeft,Size(nWidth,nHeight)); +} + +tools::Rectangle BrowseBox::calcTableRect(bool _bOnScreen) +{ + vcl::Window* pParent = nullptr; + if ( !_bOnScreen ) + pParent = GetAccessibleParentWindow(); + + tools::Rectangle aRect( GetWindowExtentsRelative( pParent ) ); + tools::Rectangle aRowBar = calcHeaderRect(false, pParent == nullptr); + + tools::Long nX = aRowBar.Right() - aRect.Left(); + tools::Long nY = aRowBar.Top() - aRect.Top(); + Size aSize(aRect.GetSize()); + + return tools::Rectangle(aRowBar.TopRight(), Size(aSize.Width() - nX, aSize.Height() - nY - GetBarHeight()) ); +} + +tools::Rectangle BrowseBox::GetFieldRectPixelAbs( sal_Int32 _nRowId, sal_uInt16 _nColId, bool /*_bIsHeader*/, bool _bOnScreen ) +{ + vcl::Window* pParent = nullptr; + if ( !_bOnScreen ) + pParent = GetAccessibleParentWindow(); + + tools::Rectangle aRect = GetFieldRectPixel(_nRowId,_nColId,_bOnScreen); + + Point aTopLeft = aRect.TopLeft(); + aTopLeft += GetWindowExtentsRelative( pParent ).TopLeft(); + + return tools::Rectangle(aTopLeft,aRect.GetSize()); +} + +// ------------------------------------------------------------------------- EOF + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/brwbox/brwbox3.cxx b/svtools/source/brwbox/brwbox3.cxx new file mode 100644 index 000000000..32401d312 --- /dev/null +++ b/svtools/source/brwbox/brwbox3.cxx @@ -0,0 +1,561 @@ +/* -*- 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 <svtools/brwbox.hxx> +#include <vcl/AccessibleBrowseBoxObjType.hxx> +#include <vcl/accessiblefactory.hxx> +#include <unotools/accessiblestatesethelper.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <tools/multisel.hxx> +#include "brwimpl.hxx" +#include <com/sun/star/accessibility/AccessibleStateType.hpp> + +// Accessibility ============================================================== + +using namespace ::com::sun::star::uno; +using ::com::sun::star::accessibility::XAccessible; +using namespace ::com::sun::star::accessibility; + + +namespace svt +{ + using namespace ::com::sun::star::lang; + using namespace utl; + + static Reference< XAccessible > getHeaderCell( BrowseBoxImpl::THeaderCellMap& _raHeaderCells, + sal_Int32 _nPos, + AccessibleBrowseBoxObjType _eType, + const Reference< XAccessible >& _rParent, + BrowseBox& _rBrowseBox, + vcl::IAccessibleFactory const & rFactory + ) + { + Reference< XAccessible > xRet; + BrowseBoxImpl::THeaderCellMap::iterator aFind = _raHeaderCells.find( _nPos ); + if ( aFind == _raHeaderCells.end() ) + { + Reference< XAccessible > xAccessible = rFactory.createAccessibleBrowseBoxHeaderCell( + _nPos, + _rParent, + _rBrowseBox, + nullptr, + _eType + ); + aFind = _raHeaderCells.emplace( _nPos, xAccessible ).first; + } + if ( aFind != _raHeaderCells.end() ) + xRet = aFind->second; + return xRet; + } + + + Reference< XAccessible > BrowseBoxImpl::getAccessibleHeaderBar( AccessibleBrowseBoxObjType _eObjType ) + { + if ( m_pAccessible && m_pAccessible->isAlive() ) + return m_pAccessible->getHeaderBar( _eObjType ); + return nullptr; + } + + + Reference< XAccessible > BrowseBoxImpl::getAccessibleTable( ) + { + if ( m_pAccessible && m_pAccessible->isAlive() ) + return m_pAccessible->getTable( ); + return nullptr; + } +} + + +Reference< XAccessible > BrowseBox::CreateAccessible() +{ + vcl::Window* pParent = GetAccessibleParentWindow(); + DBG_ASSERT( pParent, "BrowseBox::CreateAccessible - parent not found" ); + + if( pParent && !m_pImpl->m_pAccessible) + { + Reference< XAccessible > xAccParent = pParent->GetAccessible(); + if( xAccParent.is() ) + { + m_pImpl->m_pAccessible = getAccessibleFactory().createAccessibleBrowseBox( + xAccParent, *this + ); + } + } + + Reference< XAccessible > xAccessible; + if ( m_pImpl->m_pAccessible ) + xAccessible = m_pImpl->m_pAccessible->getMyself(); + + return xAccessible; +} + + +// Children ------------------------------------------------------------------- + +Reference< XAccessible > BrowseBox::CreateAccessibleCell( sal_Int32 _nRow, sal_uInt16 _nColumnPos ) +{ + // BBINDEX_TABLE must be the table + OSL_ENSURE(m_pImpl->m_pAccessible,"Invalid call: Accessible is null"); + + return m_pImpl->m_aFactoryAccess.getFactory().createAccessibleBrowseBoxTableCell( + m_pImpl->getAccessibleTable(), + *this, + nullptr, + _nRow, + _nColumnPos, + OFFSET_DEFAULT + ); +} + + +Reference< XAccessible > BrowseBox::CreateAccessibleRowHeader( sal_Int32 _nRow ) +{ + return svt::getHeaderCell( + m_pImpl->m_aRowHeaderCellMap, + _nRow, + AccessibleBrowseBoxObjType::RowHeaderCell, + m_pImpl->getAccessibleHeaderBar(AccessibleBrowseBoxObjType::RowHeaderBar), + *this, + m_pImpl->m_aFactoryAccess.getFactory() + ); +} + + +Reference< XAccessible > BrowseBox::CreateAccessibleColumnHeader( sal_uInt16 _nColumnPos ) +{ + return svt::getHeaderCell( + m_pImpl->m_aColHeaderCellMap, + _nColumnPos, + AccessibleBrowseBoxObjType::ColumnHeaderCell, + m_pImpl->getAccessibleHeaderBar(AccessibleBrowseBoxObjType::ColumnHeaderBar), + *this, + m_pImpl->m_aFactoryAccess.getFactory() + ); +} + + +sal_Int32 BrowseBox::GetAccessibleControlCount() const +{ + return 0; +} + + +Reference< XAccessible > BrowseBox::CreateAccessibleControl( sal_Int32 ) +{ + SAL_WARN( "svtools", "BrowseBox::CreateAccessibleControl: to be overwritten!" ); + return nullptr; +} + + +// Conversions ---------------------------------------------------------------- + +bool BrowseBox::ConvertPointToCellAddress( + sal_Int32& rnRow, sal_uInt16& rnColumnPos, const Point& rPoint ) +{ + //! TODO has to be checked + rnRow = GetRowAtYPosPixel(rPoint.Y()); + rnColumnPos = GetColumnAtXPosPixel(rPoint.X()); + return rnRow != BROWSER_INVALIDID && rnColumnPos != BROWSER_INVALIDID; +} + + +bool BrowseBox::ConvertPointToRowHeader( sal_Int32& rnRow, const Point& rPoint ) +{ + rnRow = GetRowAtYPosPixel(rPoint.Y()); + // sal_uInt16 nColumnId = GetColumnAtXPosPixel(rPoint.X()); + return rnRow != BROWSER_INVALIDID;// && nColumnId == 0; +} + + +bool BrowseBox::ConvertPointToColumnHeader( sal_uInt16& _rnColumnPos, const Point& _rPoint ) +{ + _rnColumnPos = GetColumnAtXPosPixel(_rPoint.X()); + return _rnColumnPos != BROWSER_INVALIDID; +} + + +bool BrowseBox::ConvertPointToControlIndex( sal_Int32& _rnIndex, const Point& _rPoint ) +{ + //! TODO has to be checked + sal_Int32 nRow = 0; + sal_uInt16 nColumn = 0; + bool bRet = ConvertPointToCellAddress(nRow,nColumn,_rPoint); + if ( bRet ) + _rnIndex = nRow * ColCount() + nColumn; + + return bRet; +} + + +// Object data and state ------------------------------------------------------ + +OUString BrowseBox::GetAccessibleObjectName( AccessibleBrowseBoxObjType eObjType,sal_Int32 _nPosition) const +{ + OUString aRetText; + switch( eObjType ) + { + case AccessibleBrowseBoxObjType::BrowseBox: + aRetText = "BrowseBox"; + break; + case AccessibleBrowseBoxObjType::Table: + aRetText = "Table"; + break; + case AccessibleBrowseBoxObjType::RowHeaderBar: + aRetText = "RowHeaderBar"; + break; + case AccessibleBrowseBoxObjType::ColumnHeaderBar: + aRetText = "ColumnHeaderBar"; + break; + case AccessibleBrowseBoxObjType::TableCell: + if( ColCount() !=0 && GetRowCount()!=0) + { + + sal_Int32 columnId = _nPosition % ColCount() +1; + aRetText = GetColumnDescription( sal_Int16( columnId ) ); + sal_Int32 rowId = _nPosition / GetRowCount() + 1; + aRetText += OUString::number(rowId); + } + else + aRetText = "TableCell"; +#if OSL_DEBUG_LEVEL > 0 + aRetText += " [" + + OUString::number(GetCurRow()) + + "," + + OUString::number(sal_Int32(GetCurColumnId())) + + "]"; +#endif + break; + case AccessibleBrowseBoxObjType::RowHeaderCell: + { + sal_Int32 rowId = _nPosition + 1; + aRetText = OUString::number( rowId ); + } +#if OSL_DEBUG_LEVEL > 0 + aRetText += " [" + + OUString::number(GetCurRow()) + + "," + + OUString::number(sal_Int32(GetCurColumnId())) + + "]"; +#endif + break; + case AccessibleBrowseBoxObjType::ColumnHeaderCell: + aRetText = GetColumnDescription( sal_Int16( _nPosition ) ); +#if OSL_DEBUG_LEVEL > 0 + aRetText += " [" + + OUString::number(GetCurRow()) + + "," + + OUString::number(sal_Int32(GetCurColumnId())) + + "]"; +#endif + break; + default: + OSL_FAIL("BrowseBox::GetAccessibleName: invalid enum!"); + } + return aRetText; +} + + +OUString BrowseBox::GetAccessibleObjectDescription( AccessibleBrowseBoxObjType eObjType,sal_Int32 ) const +{ + OUString aRetText; + switch( eObjType ) + { + case AccessibleBrowseBoxObjType::BrowseBox: + aRetText = "BrowseBox description"; + break; + case AccessibleBrowseBoxObjType::Table: + // aRetText = "TABLE description"; + break; + case AccessibleBrowseBoxObjType::RowHeaderBar: + // aRetText = "ROWHEADERBAR description"; + break; + case AccessibleBrowseBoxObjType::ColumnHeaderBar: + // aRetText = "COLUMNHEADERBAR description"; + break; + case AccessibleBrowseBoxObjType::TableCell: + // aRetText = "TABLECELL description"; + break; + case AccessibleBrowseBoxObjType::RowHeaderCell: + // aRetText = "ROWHEADERCELL description"; + break; + case AccessibleBrowseBoxObjType::ColumnHeaderCell: + // aRetText = "COLUMNHEADERCELL description"; + break; + case AccessibleBrowseBoxObjType::CheckBoxCell: + break; + } + return aRetText; +} + + +OUString BrowseBox::GetRowDescription( sal_Int32 ) const +{ + return OUString(); +} + + +OUString BrowseBox::GetColumnDescription( sal_uInt16 _nColumn ) const +{ + return GetColumnTitle( GetColumnId( _nColumn ) ); +} + + +void BrowseBox::FillAccessibleStateSet( + ::utl::AccessibleStateSetHelper& rStateSet, + AccessibleBrowseBoxObjType eObjType ) const +{ + switch( eObjType ) + { + case AccessibleBrowseBoxObjType::BrowseBox: + case AccessibleBrowseBoxObjType::Table: + + rStateSet.AddState( AccessibleStateType::FOCUSABLE ); + if ( HasFocus() ) + rStateSet.AddState( AccessibleStateType::FOCUSED ); + if ( IsActive() ) + rStateSet.AddState( AccessibleStateType::ACTIVE ); + if ( GetUpdateMode() ) + rStateSet.AddState( AccessibleStateType::EDITABLE ); + if ( IsEnabled() ) + { + rStateSet.AddState( AccessibleStateType::ENABLED ); + rStateSet.AddState( AccessibleStateType::SENSITIVE ); + } + if ( IsReallyVisible() ) + rStateSet.AddState( AccessibleStateType::VISIBLE ); + if ( eObjType == AccessibleBrowseBoxObjType::Table ) + rStateSet.AddState( AccessibleStateType::MANAGES_DESCENDANTS ); + + break; + case AccessibleBrowseBoxObjType::RowHeaderBar: + rStateSet.AddState( AccessibleStateType::FOCUSABLE ); + rStateSet.AddState( AccessibleStateType::VISIBLE ); + if ( GetSelectRowCount() ) + rStateSet.AddState( AccessibleStateType::FOCUSED ); + rStateSet.AddState( AccessibleStateType::MANAGES_DESCENDANTS ); + break; + case AccessibleBrowseBoxObjType::ColumnHeaderBar: + rStateSet.AddState( AccessibleStateType::FOCUSABLE ); + rStateSet.AddState( AccessibleStateType::VISIBLE ); + if ( GetSelectColumnCount() ) + rStateSet.AddState( AccessibleStateType::FOCUSED ); + rStateSet.AddState( AccessibleStateType::MANAGES_DESCENDANTS ); + break; + case AccessibleBrowseBoxObjType::TableCell: + { + sal_Int32 nRow = GetCurRow(); + sal_uInt16 nColumn = GetCurColumnId(); + if ( IsFieldVisible(nRow,nColumn) ) + rStateSet.AddState( AccessibleStateType::VISIBLE ); + if ( !IsFrozen( nColumn ) ) + rStateSet.AddState( AccessibleStateType::FOCUSABLE ); + rStateSet.AddState( AccessibleStateType::TRANSIENT ); + } + break; + case AccessibleBrowseBoxObjType::RowHeaderCell: + case AccessibleBrowseBoxObjType::ColumnHeaderCell: + case AccessibleBrowseBoxObjType::CheckBoxCell: + OSL_FAIL("Illegal call here!"); + break; + } +} + +void BrowseBox::FillAccessibleStateSetForCell( ::utl::AccessibleStateSetHelper& _rStateSet, + sal_Int32 _nRow, sal_uInt16 _nColumnPos ) const +{ + //! TODO check if the state is valid for table cells + if ( IsCellVisible( _nRow, _nColumnPos ) ) + _rStateSet.AddState( AccessibleStateType::VISIBLE ); + if ( GetCurrRow() == _nRow && GetCurrColumn() == _nColumnPos ) + _rStateSet.AddState( AccessibleStateType::FOCUSED ); + else // only transient when column is not focused + _rStateSet.AddState( AccessibleStateType::TRANSIENT ); +} + + +void BrowseBox::GrabTableFocus() +{ + GrabFocus(); +} + +OUString BrowseBox::GetCellText(sal_Int32, sal_uInt16 ) const +{ + SAL_WARN("svtools", "This method has to be implemented by the derived classes! BUG!!"); + return OUString(); +} + + +void BrowseBox::commitHeaderBarEvent(sal_Int16 nEventId, + const Any& rNewValue, const Any& rOldValue, bool _bColumnHeaderBar ) +{ + if ( isAccessibleAlive() ) + m_pImpl->m_pAccessible->commitHeaderBarEvent( nEventId, + rNewValue, rOldValue, _bColumnHeaderBar ); +} + +void BrowseBox::commitTableEvent( sal_Int16 _nEventId, const Any& _rNewValue, const Any& _rOldValue ) +{ + if ( isAccessibleAlive() ) + m_pImpl->m_pAccessible->commitTableEvent( _nEventId, _rNewValue, _rOldValue ); +} + +void BrowseBox::commitBrowseBoxEvent( sal_Int16 _nEventId, const Any& _rNewValue, const Any& _rOldValue ) +{ + if ( isAccessibleAlive() ) + m_pImpl->m_pAccessible->commitEvent( _nEventId, _rNewValue, _rOldValue); +} + +::vcl::IAccessibleFactory& BrowseBox::getAccessibleFactory() +{ + return m_pImpl->m_aFactoryAccess.getFactory(); +} + +bool BrowseBox::isAccessibleAlive( ) const +{ + return ( nullptr != m_pImpl->m_pAccessible ) && m_pImpl->m_pAccessible->isAlive(); +} + +// IAccessibleTableProvider + +sal_Int32 BrowseBox::GetCurrRow() const +{ + return GetCurRow(); +} + +sal_uInt16 BrowseBox::GetCurrColumn() const +{ + return GetColumnPos( GetCurColumnId() ); +} + +bool BrowseBox::HasRowHeader() const +{ + return ( GetColumnId( 0 ) == HandleColumnId ); // HandleColumn == RowHeader +} + +bool BrowseBox::GoToCell( sal_Int32 _nRow, sal_uInt16 _nColumn ) +{ + return GoToRowColumnId( _nRow, GetColumnId( _nColumn ) ); +} + +void BrowseBox::SelectColumn( sal_uInt16 _nColumn, bool _bSelect ) +{ + SelectColumnPos( _nColumn, _bSelect ); +} + +bool BrowseBox::IsColumnSelected( sal_Int32 _nColumn ) const +{ + return ( pColSel && (0 <= _nColumn) && (_nColumn <= 0xFFF) ) && + pColSel->IsSelected( static_cast< sal_uInt16 >( _nColumn ) ); +} + +sal_Int32 BrowseBox::GetSelectedRowCount() const +{ + return GetSelectRowCount(); +} + +sal_Int32 BrowseBox::GetSelectedColumnCount() const +{ + const MultiSelection* pColumnSel = GetColumnSelection(); + return pColumnSel ? pColumnSel->GetSelectCount() : 0; +} + +void BrowseBox::GetAllSelectedRows( css::uno::Sequence< sal_Int32 >& _rRows ) const +{ + sal_Int32 nCount = GetSelectRowCount(); + if( nCount ) + { + _rRows.realloc( nCount ); + auto pRows = _rRows.getArray(); + pRows[ 0 ] = const_cast< BrowseBox* >( this )->FirstSelectedRow(); + for( sal_Int32 nIndex = 1; nIndex < nCount; ++nIndex ) + pRows[ nIndex ] = const_cast< BrowseBox* >( this )->NextSelectedRow(); + DBG_ASSERT( const_cast< BrowseBox* >( this )->NextSelectedRow() == BROWSER_ENDOFSELECTION, + "BrowseBox::GetAllSelectedRows - too many selected rows found" ); + } +} + +void BrowseBox::GetAllSelectedColumns( css::uno::Sequence< sal_Int32 >& _rColumns ) const +{ + const MultiSelection* pColumnSel = GetColumnSelection(); + sal_Int32 nCount = GetSelectedColumnCount(); + if( !(pColumnSel && nCount) ) + return; + + _rColumns.realloc( nCount ); + auto pColumns = _rColumns.getArray(); + + sal_Int32 nIndex = 0; + const size_t nRangeCount = pColumnSel->GetRangeCount(); + for( size_t nRange = 0; nRange < nRangeCount; ++nRange ) + { + const Range& rRange = pColumnSel->GetRange( nRange ); + // loop has to include aRange.Max() + for( sal_Int32 nCol = rRange.Min(); nCol <= static_cast<sal_Int32>(rRange.Max()); ++nCol ) + { + DBG_ASSERT( nIndex < nCount, + "GetAllSelectedColumns - range overflow" ); + pColumns[ nIndex ] = nCol; + ++nIndex; + } + } +} + +bool BrowseBox::IsCellVisible( sal_Int32 _nRow, sal_uInt16 _nColumnPos ) const +{ + return IsFieldVisible( _nRow, GetColumnId( _nColumnPos ) ); +} + +OUString BrowseBox::GetAccessibleCellText(sal_Int32 _nRow, sal_uInt16 _nColPos) const +{ + return GetCellText( _nRow, GetColumnId( _nColPos ) ); +} + + +bool BrowseBox::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 BrowseBox::GetWindowExtentsRelative(const vcl::Window *pRelativeWindow) const +{ + return Control::GetWindowExtentsRelative( pRelativeWindow ); +} + +void BrowseBox::GrabFocus() +{ + Control::GrabFocus(); +} + +Reference< XAccessible > BrowseBox::GetAccessible() +{ + return Control::GetAccessible(); +} + +vcl::Window* BrowseBox::GetAccessibleParentWindow() const +{ + return Control::GetAccessibleParentWindow(); +} + +vcl::Window* BrowseBox::GetWindowInstance() +{ + return this; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/brwbox/brwhead.cxx b/svtools/source/brwbox/brwhead.cxx new file mode 100644 index 000000000..86ed55d0d --- /dev/null +++ b/svtools/source/brwbox/brwhead.cxx @@ -0,0 +1,107 @@ +/* -*- 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 <svtools/brwhead.hxx> +#include <svtools/brwbox.hxx> +#include <vcl/commandevent.hxx> + + +BrowserHeader::BrowserHeader( BrowseBox* pParent, WinBits nWinBits ) + :HeaderBar( pParent, nWinBits ) + ,_pBrowseBox( pParent ) +{ + tools::Long nHeight = pParent->IsZoom() ? pParent->CalcZoom(pParent->GetTitleHeight()) : pParent->GetTitleHeight(); + + SetPosSizePixel( Point( 0, 0), + Size( pParent->GetOutputSizePixel().Width(), + nHeight ) ); + Show(); +} + + +BrowserHeader::~BrowserHeader() +{ + disposeOnce(); +} + +void BrowserHeader::dispose() +{ + _pBrowseBox.clear(); + HeaderBar::dispose(); +} + + +void BrowserHeader::Command( const CommandEvent& rCEvt ) +{ + if ( !GetCurItemId() && CommandEventId::ContextMenu == rCEvt.GetCommand() ) + { + Point aPos( rCEvt.GetMousePosPixel() ); + if ( _pBrowseBox->IsFrozen(0) ) + aPos.AdjustX(_pBrowseBox->GetColumnWidth(0) ); + _pBrowseBox->GetDataWindow().Command( CommandEvent( + Point( aPos.X(), aPos.Y() - GetSizePixel().Height() ), + CommandEventId::ContextMenu, rCEvt.IsMouseEvent() ) ); + } +} + + +void BrowserHeader::EndDrag() +{ + // call before other actions, it looks more nice in most cases + HeaderBar::EndDrag(); + PaintImmediately(); + + // not aborted? + sal_uInt16 nId = GetCurItemId(); + if ( !nId ) + return; + + // handle column? + if ( nId == USHRT_MAX-1 ) + nId = 0; + + if ( !IsItemMode() ) + { + // column resize + _pBrowseBox->SetColumnWidth( nId, GetItemSize( nId ) ); + _pBrowseBox->ColumnResized( nId ); + SetItemSize( nId, _pBrowseBox->GetColumnWidth( nId ) ); + } + else + { + // column drag + // did the position actually change? + // take the handle column into account + sal_uInt16 nOldPos = _pBrowseBox->GetColumnPos(nId), + nNewPos = GetItemPos( nId ); + + if (_pBrowseBox->GetColumnId(0) == BrowseBox::HandleColumnId) + nNewPos++; + + if (nOldPos != nNewPos) + { + _pBrowseBox->SetColumnPos( nId, nNewPos ); + _pBrowseBox->ColumnMoved( nId ); + } + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/brwbox/brwimpl.hxx b/svtools/source/brwbox/brwimpl.hxx new file mode 100644 index 000000000..af591fbcb --- /dev/null +++ b/svtools/source/brwbox/brwimpl.hxx @@ -0,0 +1,80 @@ +/* -*- 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 <vcl/accessibletableprovider.hxx> +#include <vcl/svtaccessiblefactory.hxx> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <osl/diagnose.h> +#include <tools/diagnose_ex.h> + +#include <map> + +namespace svt +{ + class BrowseBoxImpl + { + // member + public: + typedef ::std::map< sal_Int32, css::uno::Reference< css::accessibility::XAccessible > > THeaderCellMap; + + struct THeaderCellMapFunctorDispose + { + void operator()(const THeaderCellMap::value_type& _aType) + { + css::uno::Reference< css::lang::XComponent > xComp( _aType.second, css::uno::UNO_QUERY ); + OSL_ENSURE( xComp.is() || !_aType.second.is(), "THeaderCellMapFunctorDispose: invalid accessible cell (no XComponent)!" ); + if ( xComp.is() ) + try + { + xComp->dispose(); + } + catch( const css::uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "svtools", "THeaderCellMapFunctorDispose" ); + } + } + }; + + public: + vcl::AccessibleFactoryAccess m_aFactoryAccess; + vcl::IAccessibleBrowseBox* m_pAccessible; + THeaderCellMap m_aColHeaderCellMap; + THeaderCellMap m_aRowHeaderCellMap; + + public: + BrowseBoxImpl() : m_pAccessible(nullptr) + { + } + + + /// @see AccessibleBrowseBox::getHeaderBar + css::uno::Reference< css::accessibility::XAccessible > + getAccessibleHeaderBar( AccessibleBrowseBoxObjType _eObjType ); + + /// @see AccessibleBrowseBox::getTable + css::uno::Reference< css::accessibility::XAccessible > + getAccessibleTable( ); + + }; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/brwbox/datwin.cxx b/svtools/source/brwbox/datwin.cxx new file mode 100644 index 000000000..47541b623 --- /dev/null +++ b/svtools/source/brwbox/datwin.cxx @@ -0,0 +1,694 @@ +/* -*- 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 "datwin.hxx" +#include <o3tl/numeric.hxx> +#include <svtools/brwhead.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/help.hxx> +#include <vcl/settings.hxx> +#include <vcl/ptrstyle.hxx> +#include <tools/debug.hxx> +#include <tools/fract.hxx> + +void ButtonFrame::Draw( OutputDevice& rDev ) +{ + Color aOldFillColor = rDev.GetFillColor(); + Color aOldLineColor = rDev.GetLineColor(); + + const StyleSettings &rSettings = rDev.GetSettings().GetStyleSettings(); + Color aColLight( rSettings.GetLightColor() ); + Color aColShadow( rSettings.GetShadowColor() ); + Color aColFace( rSettings.GetFaceColor() ); + + rDev.SetLineColor( aColFace ); + rDev.SetFillColor( aColFace ); + rDev.DrawRect( aRect ); + + if( rDev.GetOutDevType() != OUTDEV_WINDOW ) + { + rDev.SetLineColor( aColLight ); + rDev.DrawLine( aRect.TopLeft(), Point( aRect.Right(), aRect.Top() ) ); + rDev.DrawLine( aRect.TopLeft(), Point( aRect.Left(), aRect.Bottom() - 1 ) ); + rDev.SetLineColor( aColShadow ); + rDev.DrawLine( aRect.BottomRight(), Point( aRect.Right(), aRect.Top() ) ); + rDev.DrawLine( aRect.BottomRight(), Point( aRect.Left(), aRect.Bottom() ) ); + } + + if ( !aText.isEmpty() ) + { + OUString aVal = rDev.GetEllipsisString(aText,aInnerRect.GetWidth() - 2*MIN_COLUMNWIDTH); + + vcl::Font aFont( rDev.GetFont() ); + bool bOldTransp = aFont.IsTransparent(); + if ( !bOldTransp ) + { + aFont.SetTransparent( true ); + rDev.SetFont( aFont ); + } + + Color aOldColor = rDev.GetTextColor(); + if (m_bDrawDisabled) + rDev.SetTextColor(rSettings.GetDisableColor()); + + rDev.DrawText( Point( + ( aInnerRect.Left() + aInnerRect.Right() ) / 2 - ( rDev.GetTextWidth(aVal) / 2 ), + aInnerRect.Top() ), aVal ); + + // restore settings + if ( !bOldTransp ) + { + aFont.SetTransparent(false); + rDev.SetFont( aFont ); + } + if (m_bDrawDisabled) + rDev.SetTextColor(aOldColor); + } + + rDev.SetLineColor( aOldLineColor ); + rDev.SetFillColor( aOldFillColor ); +} + +BrowserColumn::BrowserColumn( sal_uInt16 nItemId, + const OUString& rTitle, sal_uLong nWidthPixel, const Fraction& rCurrentZoom ) +: _nId( nItemId ), + _nWidth( nWidthPixel ), + _aTitle( rTitle ), + _bFrozen( false ) +{ + double n = static_cast<double>(_nWidth); + n *= static_cast<double>(rCurrentZoom.GetDenominator()); + if (!rCurrentZoom.GetNumerator()) + throw o3tl::divide_by_zero(); + n /= static_cast<double>(rCurrentZoom.GetNumerator()); + _nOriginalWidth = n>0 ? static_cast<tools::Long>(n+0.5) : -static_cast<tools::Long>(-n+0.5); +} + +BrowserColumn::~BrowserColumn() +{ +} + +void BrowserColumn::SetWidth(sal_uLong nNewWidthPixel, const Fraction& rCurrentZoom) +{ + _nWidth = nNewWidthPixel; + // Avoid overflow when called with LONG_MAX from + // BrowseBox::AutoSizeLastColumn: + if (_nWidth == LONG_MAX) + { + _nOriginalWidth = _nWidth; + } + else + { + double n = static_cast<double>(_nWidth); + n *= static_cast<double>(rCurrentZoom.GetDenominator()); + if (!rCurrentZoom.GetNumerator()) + throw o3tl::divide_by_zero(); + n /= static_cast<double>(rCurrentZoom.GetNumerator()); + _nOriginalWidth = n>0 ? static_cast<tools::Long>(n+0.5) : -static_cast<tools::Long>(-n+0.5); + } +} + +void BrowserColumn::Draw( BrowseBox const & rBox, OutputDevice& rDev, const Point& rPos ) +{ + if ( _nId == 0 ) + { + // paint handle column + ButtonFrame( rPos, Size( Width()-1, rBox.GetDataRowHeight()-1 ), + "", false ).Draw( rDev ); + Color aOldLineColor = rDev.GetLineColor(); + rDev.SetLineColor( COL_BLACK ); + rDev.DrawLine( + Point( rPos.X(), rPos.Y()+rBox.GetDataRowHeight()-1 ), + Point( rPos.X() + Width() - 1, rPos.Y()+rBox.GetDataRowHeight()-1 ) ); + rDev.DrawLine( + Point( rPos.X() + Width() - 1, rPos.Y() ), + Point( rPos.X() + Width() - 1, rPos.Y()+rBox.GetDataRowHeight()-1 ) ); + rDev.SetLineColor( aOldLineColor ); + + rBox.DoPaintField( rDev, + tools::Rectangle( + Point( rPos.X() + 2, rPos.Y() + 2 ), + Size( Width()-1, rBox.GetDataRowHeight()-1 ) ), + GetId(), + BrowseBox::BrowserColumnAccess() ); + } + else + { + // paint data column + tools::Long nWidth = Width() == LONG_MAX ? rBox.GetDataWindow().GetSizePixel().Width() : Width(); + + rBox.DoPaintField( rDev, + tools::Rectangle( + Point( rPos.X() + MIN_COLUMNWIDTH, rPos.Y() ), + Size( nWidth-2*MIN_COLUMNWIDTH, rBox.GetDataRowHeight()-1 ) ), + GetId(), + BrowseBox::BrowserColumnAccess() ); + } +} + + +void BrowserColumn::ZoomChanged(const Fraction& rNewZoom) +{ + double n(_nOriginalWidth * rNewZoom); + _nWidth = n>0 ? static_cast<tools::Long>(n+0.5) : -static_cast<tools::Long>(-n+0.5); +} + + +BrowserDataWin::BrowserDataWin( BrowseBox* pParent ) + :Control( pParent, WB_CLIPCHILDREN ) + ,DragSourceHelper( this ) + ,DropTargetHelper( this ) + ,pHeaderBar( nullptr ) + ,pCornerWin( nullptr ) + ,bInDtor( false ) + ,aMouseTimer("BrowserDataWin aMouseTimer") + ,bInPaint( false ) + ,bInCommand( false ) + ,bNoHScroll( false ) + ,bNoVScroll( false ) + ,bAutoHScroll(false) + ,bAutoVScroll(false) + ,bUpdateMode( true ) + ,bAutoSizeLastCol(false) + ,bResizeOnPaint( false ) + ,bUpdateOnUnlock( false ) + ,bInUpdateScrollbars( false ) + ,bHadRecursion( false ) + ,bCallingDropCallback( false ) + ,nUpdateLock( 0 ) + ,nCursorHidden( 0 ) + ,m_nDragRowDividerLimit( 0 ) + ,m_nDragRowDividerOffset( 0 ) +{ + aMouseTimer.SetInvokeHandler( LINK( this, BrowserDataWin, RepeatedMouseMove ) ); + aMouseTimer.SetTimeout( 100 ); +} + + +BrowserDataWin::~BrowserDataWin() +{ + disposeOnce(); +} + +void BrowserDataWin::dispose() +{ + bInDtor = true; + + aInvalidRegion.clear(); + pHeaderBar.clear(); + pCornerWin.clear(); + DragSourceHelper::dispose(); + DropTargetHelper::dispose(); + Control::dispose(); +} + + +void BrowserDataWin::LeaveUpdateLock() +{ + if ( !--nUpdateLock ) + { + DoOutstandingInvalidations(); + if (bUpdateOnUnlock ) + { + Control::PaintImmediately(); + bUpdateOnUnlock = false; + } + } +} + +void InitSettings_Impl(vcl::Window* pWin) +{ + const StyleSettings& rStyleSettings = pWin->GetSettings().GetStyleSettings(); + + pWin->ApplyControlFont(*pWin->GetOutDev(), rStyleSettings.GetFieldFont()); + pWin->ApplyControlForeground(*pWin->GetOutDev(), rStyleSettings.GetWindowTextColor()); + pWin->ApplyControlBackground(*pWin->GetOutDev(), rStyleSettings.GetWindowColor()); +} + + +void BrowserDataWin::Update() +{ + if ( !nUpdateLock ) + Control::PaintImmediately(); + else + bUpdateOnUnlock = true; +} + + +void BrowserDataWin::DataChanged( const DataChangedEvent& rDCEvt ) +{ + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + InitSettings_Impl(this); + Invalidate(); + InitSettings_Impl(GetParent()); + GetParent()->Invalidate(); + GetParent()->Resize(); + } + else + Control::DataChanged( rDCEvt ); +} + + +void BrowserDataWin::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + if (!nUpdateLock && GetUpdateMode()) + { + if (bInPaint) + { + aInvalidRegion.emplace_back(rRect); + return; + } + bInPaint = true; + GetParent()->PaintData(*this, rRenderContext, rRect); + bInPaint = false; + DoOutstandingInvalidations(); + } + else + { + aInvalidRegion.emplace_back(rRect); + } +} + +BrowseBox* BrowserDataWin::GetParent() const +{ + return static_cast<BrowseBox*>(Window::GetParent()); +} + +BrowseEvent BrowserDataWin::CreateBrowseEvent( const Point& rPosPixel ) +{ + BrowseBox *pBox = GetParent(); + + // seek to row under mouse + sal_Int32 nRelRow = rPosPixel.Y() < 0 + ? -1 + : rPosPixel.Y() / pBox->GetDataRowHeight(); + sal_Int32 nRow = nRelRow < 0 ? -1 : nRelRow + pBox->nTopRow; + + // find column under mouse + tools::Long nMouseX = rPosPixel.X(); + tools::Long nColX = 0; + size_t nCol; + for ( nCol = 0; + nCol < pBox->mvCols.size() && nColX < GetSizePixel().Width(); + ++nCol ) + if ( pBox->mvCols[ nCol ]->IsFrozen() || nCol >= pBox->nFirstCol ) + { + nColX += pBox->mvCols[ nCol ]->Width(); + if ( nMouseX < nColX ) + break; + } + sal_uInt16 nColId = BROWSER_INVALIDID; + if ( nCol < pBox->mvCols.size() ) + nColId = pBox->mvCols[ nCol ]->GetId(); + + // compute the field rectangle and field relative MouseEvent + tools::Rectangle aFieldRect; + if ( nCol < pBox->mvCols.size() ) + { + nColX -= pBox->mvCols[ nCol ]->Width(); + aFieldRect = tools::Rectangle( + Point( nColX, nRelRow * pBox->GetDataRowHeight() ), + Size( pBox->mvCols[ nCol ]->Width(), + pBox->GetDataRowHeight() ) ); + } + + // assemble and return the BrowseEvent + return BrowseEvent( this, nRow, nCol, nColId, aFieldRect ); +} + + +sal_Int8 BrowserDataWin::AcceptDrop( const AcceptDropEvent& _rEvt ) +{ + bCallingDropCallback = true; + sal_Int8 nReturn = GetParent()->AcceptDrop( BrowserAcceptDropEvent( this, _rEvt ) ); + bCallingDropCallback = false; + return nReturn; +} + + +sal_Int8 BrowserDataWin::ExecuteDrop( const ExecuteDropEvent& _rEvt ) +{ + bCallingDropCallback = true; + sal_Int8 nReturn = GetParent()->ExecuteDrop( BrowserExecuteDropEvent( this, _rEvt ) ); + bCallingDropCallback = false; + return nReturn; +} + + +void BrowserDataWin::StartDrag( sal_Int8 _nAction, const Point& _rPosPixel ) +{ + if ( !GetParent()->bRowDividerDrag ) + { + Point aEventPos( _rPosPixel ); + aEventPos.AdjustY(GetParent()->GetTitleHeight() ); + GetParent()->StartDrag( _nAction, aEventPos ); + } +} + + +void BrowserDataWin::Command( const CommandEvent& rEvt ) +{ + // scroll mouse event? + BrowseBox *pBox = GetParent(); + if ( ( (rEvt.GetCommand() == CommandEventId::Wheel) || + (rEvt.GetCommand() == CommandEventId::StartAutoScroll) || + (rEvt.GetCommand() == CommandEventId::AutoScroll) ) && + ( HandleScrollCommand( rEvt, pBox->aHScroll.get(), pBox->pVScroll ) ) ) + return; + + Point aEventPos( rEvt.GetMousePosPixel() ); + sal_Int32 nRow = pBox->GetRowAtYPosPixel( aEventPos.Y(), false); + MouseEvent aMouseEvt( aEventPos, 1, MouseEventModifiers::SELECT, MOUSE_LEFT ); + if ( CommandEventId::ContextMenu == rEvt.GetCommand() && rEvt.IsMouseEvent() && + nRow < pBox->GetRowCount() && !pBox->IsRowSelected(nRow) ) + { + bInCommand = true; + MouseButtonDown( aMouseEvt ); + if( bInDtor ) + return; + MouseButtonUp( aMouseEvt ); + if( bInDtor ) + return; + bInCommand = false; + } + + aEventPos.AdjustY(GetParent()->GetTitleHeight() ); + CommandEvent aEvt( aEventPos, rEvt.GetCommand(), + rEvt.IsMouseEvent(), rEvt.GetEventData() ); + bInCommand = true; + GetParent()->Command( aEvt ); + if( bInDtor ) + return; + bInCommand = false; + + if ( CommandEventId::StartDrag == rEvt.GetCommand() ) + MouseButtonUp( aMouseEvt ); + + Control::Command( rEvt ); +} + + +bool BrowserDataWin::ImplRowDividerHitTest( const BrowserMouseEvent& _rEvent ) const +{ + if ( ! ( GetParent()->IsInteractiveRowHeightEnabled() + && ( _rEvent.GetRow() >= 0 ) + && ( _rEvent.GetRow() < GetParent()->GetRowCount() ) + && ( _rEvent.GetColumnId() == BrowseBox::HandleColumnId ) + ) + ) + return false; + + tools::Long nDividerDistance = GetParent()->GetDataRowHeight() - ( _rEvent.GetPosPixel().Y() % GetParent()->GetDataRowHeight() ); + return ( nDividerDistance <= 4 ); +} + + +void BrowserDataWin::MouseButtonDown( const MouseEvent& rEvt ) +{ + aLastMousePos = OutputToScreenPixel( rEvt.GetPosPixel() ); + + BrowserMouseEvent aBrowserEvent( this, rEvt ); + if ( ( aBrowserEvent.GetClicks() == 1 ) && ImplRowDividerHitTest( aBrowserEvent ) ) + { + StartRowDividerDrag( aBrowserEvent.GetPosPixel() ); + return; + } + + GetParent()->MouseButtonDown( BrowserMouseEvent( this, rEvt ) ); +} + + +void BrowserDataWin::MouseMove( const MouseEvent& rEvt ) +{ + // avoid pseudo MouseMoves + Point aNewPos = OutputToScreenPixel( rEvt.GetPosPixel() ); + if ( aNewPos == aLastMousePos ) + return; + aLastMousePos = aNewPos; + + // transform to a BrowseEvent + BrowserMouseEvent aBrowserEvent( this, rEvt ); + GetParent()->MouseMove( aBrowserEvent ); + + // pointer shape + PointerStyle ePointerStyle = PointerStyle::Arrow; + if ( ImplRowDividerHitTest( aBrowserEvent ) ) + ePointerStyle = PointerStyle::VSizeBar; + SetPointer( ePointerStyle ); + + // dragging out of the visible area? + if ( rEvt.IsLeft() && + ( rEvt.GetPosPixel().Y() > GetSizePixel().Height() || + rEvt.GetPosPixel().Y() < 0 ) ) + { + // repeat the event + aRepeatEvt = rEvt; + aMouseTimer.Start(); + } + else + // killing old repeat-event + if ( aMouseTimer.IsActive() ) + aMouseTimer.Stop(); +} + + +IMPL_LINK_NOARG(BrowserDataWin, RepeatedMouseMove, Timer *, void) +{ + GetParent()->MouseMove( BrowserMouseEvent( this, aRepeatEvt ) ); +} + +void BrowserDataWin::MouseButtonUp( const MouseEvent& rEvt ) +{ + // avoid pseudo MouseMoves + Point aNewPos = OutputToScreenPixel( rEvt.GetPosPixel() ); + aLastMousePos = aNewPos; + + // simulate a move to the current position + MouseMove( rEvt ); + + // actual button up handling + ReleaseMouse(); + if ( aMouseTimer.IsActive() ) + aMouseTimer.Stop(); + GetParent()->MouseButtonUp( BrowserMouseEvent( this, rEvt ) ); +} + + +void BrowserDataWin::StartRowDividerDrag( const Point& _rStartPos ) +{ + tools::Long nDataRowHeight = GetParent()->GetDataRowHeight(); + // the exact separation pos of the two rows + tools::Long nDragRowDividerCurrentPos = _rStartPos.Y(); + if ( ( nDragRowDividerCurrentPos % nDataRowHeight ) > nDataRowHeight / 2 ) + nDragRowDividerCurrentPos += nDataRowHeight; + nDragRowDividerCurrentPos /= nDataRowHeight; + nDragRowDividerCurrentPos *= nDataRowHeight; + + m_nDragRowDividerOffset = nDragRowDividerCurrentPos - _rStartPos.Y(); + + m_nDragRowDividerLimit = nDragRowDividerCurrentPos - nDataRowHeight; + + GetParent()->bRowDividerDrag = true; + GetParent()->ImplStartTracking(); + + tools::Rectangle aDragSplitRect( 0, m_nDragRowDividerLimit, GetOutputSizePixel().Width(), nDragRowDividerCurrentPos ); + ShowTracking( aDragSplitRect ); + + StartTracking(); +} + + +void BrowserDataWin::Tracking( const TrackingEvent& rTEvt ) +{ + if ( !GetParent()->bRowDividerDrag ) + return; + + Point aMousePos = rTEvt.GetMouseEvent().GetPosPixel(); + // stop resizing at our bottom line + if ( aMousePos.Y() > GetOutputSizePixel().Height() ) + aMousePos.setY( GetOutputSizePixel().Height() ); + + if ( rTEvt.IsTrackingEnded() ) + { + HideTracking(); + GetParent()->bRowDividerDrag = false; + GetParent()->ImplEndTracking(); + + if ( !rTEvt.IsTrackingCanceled() ) + { + tools::Long nNewRowHeight = aMousePos.Y() + m_nDragRowDividerOffset - m_nDragRowDividerLimit; + + // care for minimum row height + if ( nNewRowHeight < GetParent()->QueryMinimumRowHeight() ) + nNewRowHeight = GetParent()->QueryMinimumRowHeight(); + + GetParent()->SetDataRowHeight( nNewRowHeight ); + GetParent()->RowHeightChanged(); + } + } + else + { + tools::Long nDragRowDividerCurrentPos = aMousePos.Y() + m_nDragRowDividerOffset; + + // care for minimum row height + if ( nDragRowDividerCurrentPos < m_nDragRowDividerLimit + GetParent()->QueryMinimumRowHeight() ) + nDragRowDividerCurrentPos = m_nDragRowDividerLimit + GetParent()->QueryMinimumRowHeight(); + + tools::Rectangle aDragSplitRect( 0, m_nDragRowDividerLimit, GetOutputSizePixel().Width(), nDragRowDividerCurrentPos ); + ShowTracking( aDragSplitRect ); + } +} + + +void BrowserDataWin::KeyInput( const KeyEvent& rEvt ) +{ + // pass to parent window + if ( !GetParent()->ProcessKey( rEvt ) ) + Control::KeyInput( rEvt ); +} + + +void BrowserDataWin::RequestHelp( const HelpEvent& rHEvt ) +{ + GetParent()->RequestHelp( rHEvt ); +} + + +BrowseEvent::BrowseEvent( vcl::Window* pWindow, + sal_Int32 nAbsRow, sal_uInt16 nColumn, sal_uInt16 nColumnId, + const tools::Rectangle& rRect ): + pWin(pWindow), + aRect(rRect), + nRow(nAbsRow), + nCol(nColumn), + nColId(nColumnId) +{ +} + + +BrowserMouseEvent::BrowserMouseEvent( BrowserDataWin *pWindow, + const MouseEvent& rEvt ): + MouseEvent(rEvt), + BrowseEvent( pWindow->CreateBrowseEvent( rEvt.GetPosPixel() ) ) +{ +} + + +BrowserMouseEvent::BrowserMouseEvent( vcl::Window *pWindow, const MouseEvent& rEvt, + sal_Int32 nAbsRow, sal_uInt16 nColumn, sal_uInt16 nColumnId, + const tools::Rectangle& rRect ): + MouseEvent(rEvt), + BrowseEvent( pWindow, nAbsRow, nColumn, nColumnId, rRect ) +{ +} + + +BrowserAcceptDropEvent::BrowserAcceptDropEvent( BrowserDataWin *pWindow, const AcceptDropEvent& rEvt ) + :AcceptDropEvent(rEvt) + ,BrowseEvent( pWindow->CreateBrowseEvent( rEvt.maPosPixel ) ) +{ +} + + +BrowserExecuteDropEvent::BrowserExecuteDropEvent( BrowserDataWin *pWindow, const ExecuteDropEvent& rEvt ) + :ExecuteDropEvent(rEvt) + ,BrowseEvent( pWindow->CreateBrowseEvent( rEvt.maPosPixel ) ) +{ +} + + +void BrowserDataWin::SetUpdateMode( bool bMode ) +{ + DBG_ASSERT( !bUpdateMode || aInvalidRegion.empty(), "invalid region not empty" ); + if ( bMode == bUpdateMode ) + return; + + bUpdateMode = bMode; + if ( bMode ) + DoOutstandingInvalidations(); +} + + +void BrowserDataWin::DoOutstandingInvalidations() +{ + for (const auto& rRect : aInvalidRegion) + Control::Invalidate( rRect ); + aInvalidRegion.clear(); +} + + +void BrowserDataWin::Invalidate( InvalidateFlags nFlags ) +{ + if ( !GetUpdateMode() ) + { + aInvalidRegion.clear(); + aInvalidRegion.emplace_back( Point( 0, 0 ), GetOutputSizePixel() ); + } + else + Window::Invalidate( nFlags ); +} + + +void BrowserDataWin::Invalidate( const tools::Rectangle& rRect, InvalidateFlags nFlags ) +{ + if ( !GetUpdateMode() ) + aInvalidRegion.emplace_back( rRect ); + else + Window::Invalidate( rRect, nFlags ); +} + +BrowserScrollBar::~BrowserScrollBar() +{ + disposeOnce(); +} + +void BrowserScrollBar::dispose() +{ + _pDataWin.clear(); + ScrollBar::dispose(); +} + +void BrowserScrollBar::Tracking( const TrackingEvent& rTEvt ) +{ + tools::Long nPos = GetThumbPos(); + if ( nPos != _nLastPos ) + { + OUString aTip = OUString::number(nPos) + "/"; + if ( !_pDataWin->GetRealRowCount().isEmpty() ) + aTip += _pDataWin->GetRealRowCount(); + else + aTip += OUString::number(GetRangeMax()); + + tools::Rectangle aRect(GetPointerPosPixel(), Size(GetTextWidth(aTip), GetTextHeight())); + Help::ShowQuickHelp(this, aRect, aTip); + _nLastPos = nPos; + } + + ScrollBar::Tracking( rTEvt ); +} + +void BrowserScrollBar::EndScroll() +{ + Help::HideBalloonAndQuickHelp(); + ScrollBar::EndScroll(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/brwbox/datwin.hxx b/svtools/source/brwbox/datwin.hxx new file mode 100644 index 000000000..789d96548 --- /dev/null +++ b/svtools/source/brwbox/datwin.hxx @@ -0,0 +1,107 @@ +/* -*- 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 <svtools/brwbox.hxx> +#include <tools/long.hxx> +#include <vcl/scrbar.hxx> + +#include <limits> + +#define MIN_COLUMNWIDTH 2 + +class ButtonFrame +{ + tools::Rectangle aRect; + tools::Rectangle aInnerRect; + OUString aText; + bool m_bDrawDisabled; + +public: + ButtonFrame( const Point& rPt, const Size& rSz, + const OUString &rText, + bool _bDrawDisabled) + :aRect( rPt, rSz ) + ,aInnerRect( Point( aRect.Left()+1, aRect.Top()+1 ), + Size( aRect.GetWidth()-2, aRect.GetHeight()-2 ) ) + ,aText(rText) + ,m_bDrawDisabled(_bDrawDisabled) + { + } + + void Draw( OutputDevice& rDev ); +}; + + +class BrowserColumn final +{ + sal_uInt16 _nId; + sal_uLong _nOriginalWidth; + sal_uLong _nWidth; + OUString _aTitle; + bool _bFrozen; + +public: + BrowserColumn( sal_uInt16 nItemId, + const OUString& rTitle, sal_uLong nWidthPixel, const Fraction& rCurrentZoom ); + ~BrowserColumn(); + + sal_uInt16 GetId() const { return _nId; } + + sal_uLong Width() const { return _nWidth; } + OUString& Title() { return _aTitle; } + + bool IsFrozen() const { return _bFrozen; } + void Freeze() { _bFrozen = true; } + + void Draw( BrowseBox const & rBox, OutputDevice& rDev, + const Point& rPos ); + + void SetWidth(sal_uLong nNewWidthPixel, const Fraction& rCurrentZoom); + void ZoomChanged(const Fraction& rNewZoom); +}; + + +class BrowserScrollBar: public ScrollBar +{ + tools::Long _nLastPos; + VclPtr<BrowserDataWin> _pDataWin; + +public: + BrowserScrollBar( vcl::Window* pParent, WinBits nStyle, + BrowserDataWin *pDataWin ) + : ScrollBar( pParent, nStyle ), + _nLastPos( std::numeric_limits<tools::Long>::max() ), + _pDataWin( pDataWin ) + {} + virtual ~BrowserScrollBar() override; + virtual void dispose() override; + //ScrollBar( vcl::Window* pParent, const ResId& rResId ); + + virtual void Tracking( const TrackingEvent& rTEvt ) override; + virtual void EndScroll() override; +}; + + +void InitSettings_Impl( vcl::Window *pWin ); + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/brwbox/ebbcontrols.cxx b/svtools/source/brwbox/ebbcontrols.cxx new file mode 100644 index 000000000..b0a2ad1bb --- /dev/null +++ b/svtools/source/brwbox/ebbcontrols.cxx @@ -0,0 +1,768 @@ +/* + * 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 <svtools/editbrowsebox.hxx> +#include <vcl/svapp.hxx> + +namespace svt +{ + //= ComboBoxControl + ComboBoxControl::ComboBoxControl(BrowserDataWin* pParent) + : ControlBase(pParent, "svt/ui/combocontrol.ui", "ComboControl") + , m_xWidget(m_xBuilder->weld_combo_box("combobox")) + { + InitControlBase(m_xWidget.get()); + m_xWidget->set_entry_width_chars(1); // so a smaller than default width can be used + m_xWidget->connect_changed(LINK(this, ComboBoxControl, SelectHdl)); + m_xWidget->connect_key_press(LINK(this, ControlBase, KeyInputHdl)); + m_xWidget->connect_key_release(LINK(this, ControlBase, KeyReleaseHdl)); + m_xWidget->connect_focus_in(LINK(this, ControlBase, FocusInHdl)); + m_xWidget->connect_focus_out(LINK(this, ControlBase, FocusOutHdl)); + m_xWidget->connect_mouse_press(LINK(this, ControlBase, MousePressHdl)); + m_xWidget->connect_mouse_release(LINK(this, ControlBase, MouseReleaseHdl)); + m_xWidget->connect_mouse_move(LINK(this, ControlBase, MouseMoveHdl)); + } + + void ComboBoxControl::SetPointFont(const vcl::Font& rFont) + { + m_xWidget->set_entry_font(rFont); + } + + void ComboBoxControl::dispose() + { + m_xWidget.reset(); + ControlBase::dispose(); + } + + IMPL_LINK_NOARG(ComboBoxControl, SelectHdl, weld::ComboBox&, void) + { + CallModifyHdls(); + } + + //= ComboBoxCellController + ComboBoxCellController::ComboBoxCellController(ComboBoxControl* pWin) + :CellController(pWin) + { + static_cast<ComboBoxControl&>(GetWindow()).SetModifyHdl(LINK(this, ComboBoxCellController, ModifyHdl)); + } + + IMPL_LINK_NOARG(ComboBoxCellController, ModifyHdl, LinkParamNone*, void) + { + callModifyHdl(); + } + + bool ComboBoxCellController::MoveAllowed(const KeyEvent& rEvt) const + { + weld::ComboBox& rBox = GetComboBox(); + switch (rEvt.GetKeyCode().GetCode()) + { + case KEY_END: + case KEY_RIGHT: + { + int nStartPos, nEndPos; + bool bNoSelection = rBox.get_entry_selection_bounds(nStartPos, nEndPos); + return bNoSelection && nEndPos == rBox.get_active_text().getLength(); + } + case KEY_HOME: + case KEY_LEFT: + { + int nStartPos, nEndPos; + bool bNoSelection = rBox.get_entry_selection_bounds(nStartPos, nEndPos); + return bNoSelection && nStartPos == 0; + } + case KEY_UP: + case KEY_DOWN: + if (rBox.get_popup_shown()) + return false; + if (!rEvt.GetKeyCode().IsShift() && + rEvt.GetKeyCode().IsMod1()) + return false; + // drop down the list box + else if (rEvt.GetKeyCode().IsMod2() && rEvt.GetKeyCode().GetCode() == KEY_DOWN) + return false; + [[fallthrough]]; + case KEY_PAGEUP: + case KEY_PAGEDOWN: + case KEY_RETURN: + if (rBox.get_popup_shown()) + return false; + [[fallthrough]]; + default: + return true; + } + } + + bool ComboBoxCellController::IsValueChangedFromSaved() const + { + return GetComboBox().get_value_changed_from_saved(); + } + + void ComboBoxCellController::SaveValue() + { + GetComboBox().save_value(); + } + + //= ListBoxControl + ListBoxControl::ListBoxControl(BrowserDataWin* pParent) + : ControlBase(pParent, "svt/ui/listcontrol.ui", "ListControl") + , m_xWidget(m_xBuilder->weld_combo_box("listbox")) + { + InitControlBase(m_xWidget.get()); + m_xWidget->set_size_request(42, -1); // so a later narrow size request can stick + m_xWidget->connect_changed(LINK(this, ListBoxControl, SelectHdl)); + m_xWidget->connect_key_press(LINK(this, ControlBase, KeyInputHdl)); + m_xWidget->connect_key_release(LINK(this, ControlBase, KeyReleaseHdl)); + m_xWidget->connect_focus_in(LINK(this, ControlBase, FocusInHdl)); + m_xWidget->connect_focus_out(LINK(this, ControlBase, FocusOutHdl)); + m_xWidget->connect_mouse_press(LINK(this, ControlBase, MousePressHdl)); + m_xWidget->connect_mouse_release(LINK(this, ControlBase, MouseReleaseHdl)); + m_xWidget->connect_mouse_move(LINK(this, ControlBase, MouseMoveHdl)); + } + + void ListBoxControl::SetPointFont(const vcl::Font& rFont) + { + m_xWidget->set_font(rFont); + } + + void ListBoxControl::dispose() + { + m_xWidget.reset(); + ControlBase::dispose(); + } + + IMPL_LINK_NOARG(ListBoxControl, SelectHdl, weld::ComboBox&, void) + { + CallModifyHdls(); + } + + //= ListBoxCellController + ListBoxCellController::ListBoxCellController(ListBoxControl* pWin) + :CellController(pWin) + { + static_cast<ListBoxControl&>(GetWindow()).SetModifyHdl(LINK(this, ListBoxCellController, ListBoxSelectHdl)); + } + + bool ListBoxCellController::MoveAllowed(const KeyEvent& rEvt) const + { + const weld::ComboBox& rBox = GetListBox(); + switch (rEvt.GetKeyCode().GetCode()) + { + case KEY_UP: + case KEY_DOWN: + if (!rEvt.GetKeyCode().IsShift() && + rEvt.GetKeyCode().IsMod1()) + return false; + // drop down the list box + else + if (rEvt.GetKeyCode().IsMod2() && rEvt.GetKeyCode().GetCode() == KEY_DOWN) + return false; + [[fallthrough]]; + case KEY_PAGEUP: + case KEY_PAGEDOWN: + if (rBox.get_popup_shown()) + return false; + [[fallthrough]]; + default: + return true; + } + } + + bool ListBoxCellController::IsValueChangedFromSaved() const + { + return GetListBox().get_value_changed_from_saved(); + } + + void ListBoxCellController::SaveValue() + { + GetListBox().save_value(); + } + + IMPL_LINK_NOARG(ListBoxCellController, ListBoxSelectHdl, LinkParamNone*, void) + { + callModifyHdl(); + } + + //= CheckBoxControl + CheckBoxControl::CheckBoxControl(BrowserDataWin* pParent) + : ControlBase(pParent, "svt/ui/checkboxcontrol.ui", "CheckBoxControl") + , m_xBox(m_xBuilder->weld_check_button("checkbox")) + { + m_aModeState.bTriStateEnabled = true; + InitControlBase(m_xBox.get()); + m_xBox->connect_key_press(LINK(this, ControlBase, KeyInputHdl)); + m_xBox->connect_key_release(LINK(this, ControlBase, KeyReleaseHdl)); + m_xBox->connect_focus_in(LINK(this, ControlBase, FocusInHdl)); + m_xBox->connect_focus_out(LINK(this, ControlBase, FocusOutHdl)); + m_xBox->connect_mouse_press(LINK(this, ControlBase, MousePressHdl)); + m_xBox->connect_mouse_release(LINK(this, ControlBase, MouseReleaseHdl)); + m_xBox->connect_mouse_move(LINK(this, ControlBase, MouseMoveHdl)); + m_xBox->connect_toggled(LINK(this, CheckBoxControl, OnToggle)); + } + + void CheckBoxControl::SetPointFont(const vcl::Font& /*rFont*/) + { + } + + void CheckBoxControl::EnableTriState( bool bTriState ) + { + if (m_aModeState.bTriStateEnabled != bTriState) + { + m_aModeState.bTriStateEnabled = bTriState; + + if (!m_aModeState.bTriStateEnabled && GetState() == TRISTATE_INDET) + SetState(TRISTATE_FALSE); + } + } + + void CheckBoxControl::SetState(TriState eState) + { + if (!m_aModeState.bTriStateEnabled && (eState == TRISTATE_INDET)) + eState = TRISTATE_FALSE; + m_aModeState.eState = eState; + m_xBox->set_state(eState); + } + + CheckBoxControl::~CheckBoxControl() + { + disposeOnce(); + } + + void CheckBoxControl::dispose() + { + m_xBox.reset(); + ControlBase::dispose(); + } + + void CheckBoxControl::Clicked() + { + // if tristate is enabled, m_aModeState will take care of setting the + // next state in the sequence via TriStateEnabled::ButtonToggled + if (!m_aModeState.bTriStateEnabled) + m_xBox->set_active(!m_xBox->get_active()); + OnToggle(*m_xBox); + } + + IMPL_LINK_NOARG(CheckBoxControl, OnToggle, weld::Toggleable&, void) + { + m_aModeState.ButtonToggled(*m_xBox); + m_aToggleLink.Call(*m_xBox); + CallModifyHdls(); + } + + //= CheckBoxCellController + CheckBoxCellController::CheckBoxCellController(CheckBoxControl* pWin) + : CellController(pWin) + { + static_cast<CheckBoxControl &>(GetWindow()).SetModifyHdl( LINK(this, CheckBoxCellController, ModifyHdl) ); + } + + void CheckBoxCellController::ActivatingMouseEvent(const BrowserMouseEvent& rEvt, bool /*bUp*/) + { + CheckBoxControl& rControl = static_cast<CheckBoxControl&>(GetWindow()); + rControl.GrabFocus(); + + // we have to adjust the position of the event relative to the controller's window + Point aPos = rEvt.GetPosPixel() - rEvt.GetRect().TopLeft(); + + Size aControlSize = rControl.GetSizePixel(); + Size aBoxSize = rControl.GetBox().get_preferred_size(); + tools::Rectangle aHotRect(Point((aControlSize.Width() - aBoxSize.Width()) / 2, + (aControlSize.Height() - aBoxSize.Height()) / 2), + aBoxSize); + + // we want the initial mouse event to act as if it was performed on the checkbox + if (aHotRect.Contains(aPos)) + rControl.Clicked(); + } + + weld::CheckButton& CheckBoxCellController::GetCheckBox() const + { + return static_cast<CheckBoxControl &>(GetWindow()).GetBox(); + } + + bool CheckBoxCellController::IsValueChangedFromSaved() const + { + return GetCheckBox().get_state_changed_from_saved(); + } + + void CheckBoxCellController::SaveValue() + { + GetCheckBox().save_state(); + } + + IMPL_LINK_NOARG(CheckBoxCellController, ModifyHdl, LinkParamNone*, void) + { + callModifyHdl(); + } + + //= MultiLineEditImplementation + OUString MultiLineEditImplementation::GetText(LineEnd eSeparator) const + { + weld::TextView& rEntry = m_rEdit.get_widget(); + return convertLineEnd(rEntry.get_text(), eSeparator); + } + + OUString MultiLineEditImplementation::GetSelected(LineEnd eSeparator) const + { + int nStartPos, nEndPos; + weld::TextView& rEntry = m_rEdit.get_widget(); + rEntry.get_selection_bounds(nStartPos, nEndPos); + return convertLineEnd(rEntry.get_text().copy(nStartPos, nEndPos - nStartPos), eSeparator); + } + + IMPL_LINK_NOARG(MultiLineEditImplementation, ModifyHdl, weld::TextView&, void) + { + CallModifyHdls(); + } + + EditCellController::EditCellController( IEditImplementation* _pImplementation ) + :CellController( &_pImplementation->GetControl() ) + ,m_pEditImplementation( _pImplementation ) + ,m_bOwnImplementation( false ) + { + m_pEditImplementation->SetModifyHdl( LINK(this, EditCellController, ModifyHdl) ); + } + + IMPL_LINK_NOARG(EntryImplementation, ModifyHdl, weld::Entry&, void) + { + CallModifyHdls(); + } + + ControlBase::ControlBase(BrowserDataWin* pParent, const OUString& rUIXMLDescription, const OString& rID) + : InterimItemWindow(pParent, rUIXMLDescription, rID) + { + } + + void ControlBase::SetEditableReadOnly(bool /*bReadOnly*/) + { + // expected to be overridden for Entry, TextView or the editable entry part of a ComboBox + } + + EditControlBase::EditControlBase(BrowserDataWin* pParent) + : ControlBase(pParent, "svt/ui/thineditcontrol.ui", "EditControl") // *thin*editcontrol has no frame/border + , m_pEntry(nullptr) // inheritors are expected to call InitEditControlBase + { + } + + void EditControlBase::InitEditControlBase(weld::Entry* pEntry) + { + InitControlBase(pEntry); + m_pEntry = pEntry; + m_pEntry->show(); + m_pEntry->set_width_chars(1); // so a smaller than default width can be used + connect_focus_in(LINK(this, ControlBase, FocusInHdl)); // need to chain with pattern handler + connect_focus_out(LINK(this, ControlBase, FocusOutHdl)); // need to chain with pattern handler + connect_key_press(LINK(this, ControlBase, KeyInputHdl)); // need to chain with pattern handler + m_pEntry->connect_key_release(LINK(this, ControlBase, KeyReleaseHdl)); + m_pEntry->connect_mouse_press(LINK(this, ControlBase, MousePressHdl)); + m_pEntry->connect_mouse_release(LINK(this, ControlBase, MouseReleaseHdl)); + m_pEntry->connect_mouse_move(LINK(this, ControlBase, MouseMoveHdl)); + } + + bool ControlBase::ProcessKey(const KeyEvent& rKEvt) + { + return static_cast<BrowserDataWin*>(GetParent())->GetParent()->ProcessKey(rKEvt); + } + + IMPL_LINK(ControlBase, KeyInputHdl, const KeyEvent&, rKEvt, bool) + { + m_aKeyInputHdl.Call(rKEvt); + return ProcessKey(rKEvt); + } + + IMPL_LINK(ControlBase, KeyReleaseHdl, const KeyEvent&, rKEvt, bool) + { + m_aKeyReleaseHdl.Call(rKEvt); + return false; + } + + IMPL_LINK_NOARG(ControlBase, FocusInHdl, weld::Widget&, void) + { + m_aFocusInHdl.Call(nullptr); + static_cast<BrowserDataWin*>(GetParent())->GetParent()->ChildFocusIn(); + } + + IMPL_LINK_NOARG(ControlBase, FocusOutHdl, weld::Widget&, void) + { + m_aFocusOutHdl.Call(nullptr); + static_cast<BrowserDataWin*>(GetParent())->GetParent()->ChildFocusOut(); + } + + IMPL_LINK(ControlBase, MousePressHdl, const MouseEvent&, rEvent, bool) + { + m_aMousePressHdl.Call(rEvent); + return false; + } + + IMPL_LINK(ControlBase, MouseReleaseHdl, const MouseEvent&, rEvent, bool) + { + m_aMouseReleaseHdl.Call(rEvent); + return false; + } + + IMPL_LINK(ControlBase, MouseMoveHdl, const MouseEvent&, rEvent, bool) + { + m_aMouseMoveHdl.Call(rEvent); + return false; + } + + void EditControlBase::dispose() + { + m_pEntry = nullptr; + ControlBase::dispose(); + } + + EditControl::EditControl(BrowserDataWin* pParent) + : EditControlBase(pParent) + , m_xWidget(m_xBuilder->weld_entry("entry")) + { + InitEditControlBase(m_xWidget.get()); + } + + void EditControl::dispose() + { + m_xWidget.reset(); + EditControlBase::dispose(); + } + + FormattedControlBase::FormattedControlBase(BrowserDataWin* pParent, bool bSpinVariant) + : EditControlBase(pParent) + , m_bSpinVariant(bSpinVariant) + , m_xEntry(m_xBuilder->weld_entry("entry")) + , m_xSpinButton(m_xBuilder->weld_formatted_spin_button("spinbutton")) + { + } + + void FormattedControlBase::InitFormattedControlBase() + { + InitEditControlBase(m_bSpinVariant ? m_xSpinButton.get() : m_xEntry.get()); + } + + void FormattedControlBase::connect_changed(const Link<weld::Entry&, void>& rLink) + { + get_formatter().connect_changed(rLink); + } + + void FormattedControlBase::connect_focus_in(const Link<weld::Widget&, void>& rLink) + { + get_widget().connect_focus_in(rLink); + } + + void FormattedControlBase::connect_focus_out(const Link<weld::Widget&, void>& rLink) + { + get_formatter().connect_focus_out(rLink); + } + + void FormattedControlBase::connect_key_press(const Link<const KeyEvent&, bool>& rLink) + { + get_widget().connect_key_press(rLink); + } + + weld::EntryFormatter& FormattedControlBase::get_formatter() + { + return *m_xEntryFormatter; + } + + void FormattedControlBase::dispose() + { + m_xEntryFormatter.reset(); + m_xSpinButton.reset(); + m_xEntry.reset(); + EditControlBase::dispose(); + } + + FormattedControl::FormattedControl(BrowserDataWin* pParent, bool bSpinVariant) + : FormattedControlBase(pParent, bSpinVariant) + { + if (bSpinVariant) + m_xEntryFormatter.reset(new weld::EntryFormatter(*m_xSpinButton)); + else + m_xEntryFormatter.reset(new weld::EntryFormatter(*m_xEntry)); + InitFormattedControlBase(); + } + + DoubleNumericControl::DoubleNumericControl(BrowserDataWin* pParent, bool bSpinVariant) + : FormattedControlBase(pParent, bSpinVariant) + { + if (bSpinVariant) + m_xEntryFormatter.reset(new weld::DoubleNumericFormatter(*m_xSpinButton)); + else + m_xEntryFormatter.reset(new weld::DoubleNumericFormatter(*m_xEntry)); + InitFormattedControlBase(); + } + + LongCurrencyControl::LongCurrencyControl(BrowserDataWin* pParent, bool bSpinVariant) + : FormattedControlBase(pParent, bSpinVariant) + { + if (bSpinVariant) + m_xEntryFormatter.reset(new weld::LongCurrencyFormatter(*m_xSpinButton)); + else + m_xEntryFormatter.reset(new weld::LongCurrencyFormatter(*m_xEntry)); + InitFormattedControlBase(); + } + + TimeControl::TimeControl(BrowserDataWin* pParent, bool bSpinVariant) + : FormattedControlBase(pParent, bSpinVariant) + { + if (bSpinVariant) + m_xEntryFormatter.reset(new weld::TimeFormatter(*m_xSpinButton)); + else + m_xEntryFormatter.reset(new weld::TimeFormatter(*m_xEntry)); + InitFormattedControlBase(); + } + + DateControl::DateControl(BrowserDataWin* pParent, bool bDropDown) + : FormattedControlBase(pParent, false) + , m_xMenuButton(m_xBuilder->weld_menu_button("button")) + , m_xCalendarBuilder(Application::CreateBuilder(m_xMenuButton.get(), "svt/ui/datewindow.ui")) + , m_xTopLevel(m_xCalendarBuilder->weld_widget("date_popup_window")) + , m_xCalendar(m_xCalendarBuilder->weld_calendar("date")) + , m_xExtras(m_xCalendarBuilder->weld_widget("extras")) + , m_xTodayBtn(m_xCalendarBuilder->weld_button("today")) + , m_xNoneBtn(m_xCalendarBuilder->weld_button("none")) + { + m_xEntryFormatter.reset(new weld::DateFormatter(*m_xEntry)); + InitFormattedControlBase(); + + m_xMenuButton->set_popover(m_xTopLevel.get()); + m_xMenuButton->set_visible(bDropDown); + m_xMenuButton->connect_toggled(LINK(this, DateControl, ToggleHdl)); + + m_xExtras->show(); + + m_xTodayBtn->connect_clicked(LINK(this, DateControl, ImplClickHdl)); + m_xNoneBtn->connect_clicked(LINK(this, DateControl, ImplClickHdl)); + + m_xCalendar->connect_activated(LINK(this, DateControl, ActivateHdl)); + } + + IMPL_LINK(DateControl, ImplClickHdl, weld::Button&, rBtn, void) + { + m_xMenuButton->set_active(false); + get_widget().grab_focus(); + + if (&rBtn == m_xTodayBtn.get()) + { + Date aToday(Date::SYSTEM); + SetDate(aToday); + } + else if (&rBtn == m_xNoneBtn.get()) + { + get_widget().set_text(OUString()); + } + } + + IMPL_LINK(DateControl, ToggleHdl, weld::Toggleable&, rButton, void) + { + if (rButton.get_active()) + m_xCalendar->set_date(static_cast<weld::DateFormatter&>(get_formatter()).GetDate()); + } + + IMPL_LINK_NOARG(DateControl, ActivateHdl, weld::Calendar&, void) + { + if (m_xMenuButton->get_active()) + m_xMenuButton->set_active(false); + static_cast<weld::DateFormatter&>(get_formatter()).SetDate(m_xCalendar->get_date()); + } + + void DateControl::SetDate(const Date& rDate) + { + static_cast<weld::DateFormatter&>(get_formatter()).SetDate(rDate); + m_xCalendar->set_date(rDate); + } + + void DateControl::dispose() + { + m_xTodayBtn.reset(); + m_xNoneBtn.reset(); + m_xExtras.reset(); + m_xCalendar.reset(); + m_xTopLevel.reset(); + m_xCalendarBuilder.reset(); + m_xMenuButton.reset(); + FormattedControlBase::dispose(); + } + + PatternControl::PatternControl(BrowserDataWin* pParent) + : EditControlBase(pParent) + , m_xWidget(m_xBuilder->weld_entry("entry")) + { + m_xEntryFormatter.reset(new weld::PatternFormatter(*m_xWidget)); + InitEditControlBase(m_xWidget.get()); + } + + void PatternControl::connect_changed(const Link<weld::Entry&, void>& rLink) + { + m_xEntryFormatter->connect_changed(rLink); + } + + void PatternControl::connect_focus_in(const Link<weld::Widget&, void>& rLink) + { + m_xEntryFormatter->connect_focus_in(rLink); + } + + void PatternControl::connect_focus_out(const Link<weld::Widget&, void>& rLink) + { + m_xEntryFormatter->connect_focus_out(rLink); + } + + void PatternControl::connect_key_press(const Link<const KeyEvent&, bool>& rLink) + { + m_xEntryFormatter->connect_key_press(rLink); + } + + void PatternControl::dispose() + { + m_xEntryFormatter.reset(); + m_xWidget.reset(); + EditControlBase::dispose(); + } + + EditCellController::EditCellController(EditControlBase* pEdit) + : CellController(pEdit) + , m_pEditImplementation(new EntryImplementation(*pEdit)) + , m_bOwnImplementation(true) + { + m_pEditImplementation->SetModifyHdl( LINK(this, EditCellController, ModifyHdl) ); + } + + EditCellController::~EditCellController( ) + { + if ( m_bOwnImplementation ) + delete m_pEditImplementation; + } + + void EditCellController::SaveValue() + { + m_pEditImplementation->SaveValue(); + } + + bool EditCellController::MoveAllowed(const KeyEvent& rEvt) const + { + bool bResult; + switch (rEvt.GetKeyCode().GetCode()) + { + case KEY_END: + case KEY_RIGHT: + { + Selection aSel = m_pEditImplementation->GetSelection(); + bResult = !aSel && aSel.Max() == m_pEditImplementation->GetText( LINEEND_LF ).getLength(); + break; + } + case KEY_HOME: + case KEY_LEFT: + { + Selection aSel = m_pEditImplementation->GetSelection(); + bResult = !aSel && aSel.Min() == 0; + break; + } + case KEY_DOWN: + { + bResult = !m_pEditImplementation->CanDown(); + break; + } + case KEY_UP: + { + bResult = !m_pEditImplementation->CanUp(); + break; + } + default: + bResult = true; + } + return bResult; + } + + bool EditCellController::IsValueChangedFromSaved() const + { + return m_pEditImplementation->IsValueChangedFromSaved(); + } + + IMPL_LINK_NOARG(EditCellController, ModifyHdl, LinkParamNone*, void) + { + callModifyHdl(); + } + + //= FormattedFieldCellController + FormattedFieldCellController::FormattedFieldCellController( FormattedControlBase* _pFormatted ) + : EditCellController(_pFormatted) + { + } + + void FormattedFieldCellController::CommitModifications() + { + static_cast<FormattedControl&>(GetWindow()).get_formatter().Commit(); + } + + MultiLineTextCell::MultiLineTextCell(BrowserDataWin* pParent) + : ControlBase(pParent, "svt/ui/textviewcontrol.ui", "TextViewControl") + , m_xWidget(m_xBuilder->weld_text_view("textview")) + { + InitControlBase(m_xWidget.get()); + m_xWidget->connect_key_press(LINK(this, ControlBase, KeyInputHdl)); + m_xWidget->connect_key_release(LINK(this, ControlBase, KeyReleaseHdl)); + m_xWidget->connect_focus_in(LINK(this, ControlBase, FocusInHdl)); + m_xWidget->connect_focus_out(LINK(this, ControlBase, FocusOutHdl)); + m_xWidget->connect_mouse_press(LINK(this, ControlBase, MousePressHdl)); + m_xWidget->connect_mouse_release(LINK(this, ControlBase, MouseReleaseHdl)); + m_xWidget->connect_mouse_move(LINK(this, ControlBase, MouseMoveHdl)); + // so any the natural size doesn't have an effect + m_xWidget->set_size_request(1, 1); + } + + void MultiLineTextCell::GetFocus() + { + if (m_xWidget) + m_xWidget->select_region(-1, 0); + ControlBase::GetFocus(); + } + + void MultiLineTextCell::dispose() + { + m_xWidget.reset(); + ControlBase::dispose(); + } + + bool MultiLineTextCell::ProcessKey(const KeyEvent& rKEvt) + { + bool bSendToDataWindow = true; + + sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode(); + bool bShift = rKEvt.GetKeyCode().IsShift(); + bool bCtrl = rKEvt.GetKeyCode().IsMod1(); + bool bAlt = rKEvt.GetKeyCode().IsMod2(); + + if (!bAlt && !bCtrl && !bShift) + { + switch (nCode) + { + case KEY_DOWN: + bSendToDataWindow = !m_xWidget->can_move_cursor_with_down(); + break; + case KEY_UP: + bSendToDataWindow = !m_xWidget->can_move_cursor_with_up(); + break; + } + } + + if (bSendToDataWindow) + return ControlBase::ProcessKey(rKEvt); + return false; + } +} // namespace svt + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/brwbox/editbrowsebox.cxx b/svtools/source/brwbox/editbrowsebox.cxx new file mode 100644 index 000000000..8cadf4748 --- /dev/null +++ b/svtools/source/brwbox/editbrowsebox.cxx @@ -0,0 +1,1284 @@ +/* -*- 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 <svtools/editbrowsebox.hxx> + +#include <tools/debug.hxx> +#include <vcl/image.hxx> +#include <vcl/settings.hxx> +#include <vcl/window.hxx> +#include <vcl/svapp.hxx> + +#include <bitmaps.hlst> + +#include <algorithm> +#include "editbrowseboximpl.hxx" +#include <com/sun/star/accessibility/AccessibleEventId.hpp> + + +namespace svt +{ + + namespace + { + + GetFocusFlags getRealGetFocusFlags( vcl::Window* _pWindow ) + { + GetFocusFlags nFlags = GetFocusFlags::NONE; + while ( _pWindow && nFlags == GetFocusFlags::NONE ) + { + nFlags = _pWindow->GetGetFocusFlags( ); + _pWindow = _pWindow->GetParent(); + } + return nFlags; + } + } + + using namespace ::com::sun::star::uno; + using namespace com::sun::star::accessibility::AccessibleEventId; + + + IEditImplementation::~IEditImplementation() + { + } + + //= EditBrowserHeader + + void EditBrowserHeader::DoubleClick() + { + sal_uInt16 nColId = GetCurItemId(); + if (nColId) + { + sal_uInt32 nAutoWidth = static_cast<EditBrowseBox*>(GetParent())->GetAutoColumnWidth(nColId); + if (nAutoWidth != static_cast<EditBrowseBox*>(GetParent())->GetColumnWidth(nColId)) + { + static_cast<EditBrowseBox*>(GetParent())->SetColumnWidth(nColId, nAutoWidth); + static_cast<EditBrowseBox*>(GetParent())->ColumnResized(nColId); + } + } + } + + //= EditBrowseBox + + void EditBrowseBox::BrowserMouseEventPtr::Clear() + { + pEvent.reset(); + } + + void EditBrowseBox::BrowserMouseEventPtr::Set(const BrowserMouseEvent* pEvt, bool bIsDown) + { + if (pEvt == pEvent.get()) + { + bDown = bIsDown; + return; + } + pEvent.reset(); + if (pEvt) + { + pEvent.reset(new BrowserMouseEvent(pEvt->GetWindow(), + *pEvt, + pEvt->GetRow(), + pEvt->GetColumn(), + pEvt->GetColumnId(), + pEvt->GetRect())); + bDown = bIsDown; + } + } + + EditBrowseBox::EditBrowseBox( vcl::Window* pParent, EditBrowseBoxFlags nBrowserFlags, WinBits nBits, BrowserMode _nMode ) + :BrowseBox( pParent, nBits, _nMode ) + ,nStartEvent(nullptr) + ,nEndEvent(nullptr) + ,nCellModifiedEvent(nullptr) + ,m_pFocusWhileRequest(nullptr) + ,nPaintRow(-1) + ,nEditRow(-1) + ,nEditCol(0) + ,bHasFocus(false) + ,bPaintStatus(true) + ,bActiveBeforeTracking( false ) + ,m_nBrowserFlags(nBrowserFlags) + ,pHeader(nullptr) + { + m_aImpl.reset(new EditBrowseBoxImpl); + + SetCompoundControl(true); + + ImplInitSettings(true, true, true); + + pCheckBoxPaint = VclPtr<CheckBoxControl>::Create(&GetDataWindow()); + pCheckBoxPaint->SetPaintTransparent( true ); + pCheckBoxPaint->SetBackground(); + } + + void EditBrowseBox::Init() + { + // late construction + } + + EditBrowseBox::~EditBrowseBox() + { + disposeOnce(); + } + + void EditBrowseBox::dispose() + { + if (nStartEvent) + Application::RemoveUserEvent(nStartEvent); + if (nEndEvent) + Application::RemoveUserEvent(nEndEvent); + if (nCellModifiedEvent) + Application::RemoveUserEvent(nCellModifiedEvent); + + pCheckBoxPaint.disposeAndClear(); + m_pFocusWhileRequest.clear(); + pHeader.clear(); + BrowseBox::dispose(); + } + + + void EditBrowseBox::RemoveRows() + { + BrowseBox::Clear(); + nEditRow = nPaintRow = -1; + nEditCol = 0; + } + + VclPtr<BrowserHeader> EditBrowseBox::CreateHeaderBar(BrowseBox* pParent) + { + pHeader = imp_CreateHeaderBar(pParent); + if (!IsUpdateMode()) + pHeader->SetUpdateMode(false); + return pHeader; + } + + VclPtr<BrowserHeader> EditBrowseBox::imp_CreateHeaderBar(BrowseBox* pParent) + { + return VclPtr<EditBrowserHeader>::Create(pParent); + } + + void EditBrowseBox::LoseFocus() + { + BrowseBox::LoseFocus(); + DetermineFocus(); + } + + void EditBrowseBox::GetFocus() + { + BrowseBox::GetFocus(); + + // This should handle the case that the BrowseBox (or one of its children) + // gets the focus from outside by pressing Tab + if (IsEditing() && Controller()->GetWindow().IsVisible()) + Controller()->GetWindow().GrabFocus(); + + DetermineFocus(getRealGetFocusFlags(this)); + } + + bool EditBrowseBox::SeekRow(sal_Int32 nRow) + { + nPaintRow = nRow; + return true; + } + + IMPL_LINK_NOARG(EditBrowseBox, StartEditHdl, void*, void) + { + nStartEvent = nullptr; + if (IsEditing()) + { + EnableAndShow(); + if (!ControlHasFocus() && (m_pFocusWhileRequest.get() == Application::GetFocusWindow())) + aController->GetWindow().GrabFocus(); + } + } + + void EditBrowseBox::PaintField( vcl::RenderContext& rDev, const tools::Rectangle& rRect, + sal_uInt16 nColumnId ) const + { + if (nColumnId == HandleColumnId) + { + if (bPaintStatus) + PaintStatusCell(rDev, rRect); + } + else + { + // don't paint the current cell + if (rDev.GetOwnerWindow() == &GetDataWindow()) + // but only if we're painting onto our data win (which is the usual painting) + if (nPaintRow == nEditRow) + { + if (IsEditing() && nEditCol == nColumnId && aController->GetWindow().IsVisible()) + return; + } + PaintCell(rDev, rRect, nColumnId); + } + } + + Image EditBrowseBox::GetImage(RowStatus eStatus) const + { + BitmapEx aBitmap; + bool bNeedMirror = IsRTLEnabled(); + switch (eStatus) + { + case CURRENT: + aBitmap = BitmapEx(BMP_CURRENT); + break; + case CURRENTNEW: + aBitmap = BitmapEx(BMP_CURRENTNEW); + break; + case MODIFIED: + aBitmap = BitmapEx(BMP_MODIFIED); + bNeedMirror = false; // the pen is not mirrored + break; + case NEW: + aBitmap = BitmapEx(BMP_NEW); + break; + case DELETED: + aBitmap = BitmapEx(BMP_DELETED); + break; + case PRIMARYKEY: + aBitmap = BitmapEx(BMP_PRIMARYKEY); + break; + case CURRENT_PRIMARYKEY: + aBitmap = BitmapEx(BMP_CURRENT_PRIMARYKEY); + break; + case FILTER: + aBitmap = BitmapEx(BMP_FILTER); + break; + case HEADERFOOTER: + aBitmap = BitmapEx(BMP_HEADERFOOTER); + break; + case CLEAN: + break; + } + if ( bNeedMirror ) + { + aBitmap.Mirror( BmpMirrorFlags::Horizontal ); + } + return Image(aBitmap); + } + + void EditBrowseBox::PaintStatusCell(OutputDevice& rDev, const tools::Rectangle& rRect) const + { + if (nPaintRow < 0) + return; + + RowStatus eStatus = GetRowStatus( nPaintRow ); + EditBrowseBoxFlags nBrowserFlags = GetBrowserFlags(); + + if (nBrowserFlags & EditBrowseBoxFlags::NO_HANDLE_COLUMN_CONTENT) + return; + + // draw the text of the header column + if (nBrowserFlags & EditBrowseBoxFlags::HANDLE_COLUMN_TEXT ) + { + rDev.DrawText( rRect, GetCellText( nPaintRow, 0 ), + DrawTextFlags::Center | DrawTextFlags::VCenter | DrawTextFlags::Clip ); + } + // draw an image + else if (eStatus != CLEAN && rDev.GetOutDevType() == OUTDEV_WINDOW) + { + Image aImage(GetImage(eStatus)); + // calc the image position + Size aImageSize(aImage.GetSizePixel()); + aImageSize.setWidth( CalcZoom(aImageSize.Width()) ); + aImageSize.setHeight( CalcZoom(aImageSize.Height()) ); + Point aPos( rRect.TopLeft() ); + + if ( ( aImageSize.Width() > rRect.GetWidth() ) || ( aImageSize.Height() > rRect.GetHeight() ) ) + rDev.SetClipRegion(vcl::Region(rRect)); + + if ( aImageSize.Width() < rRect.GetWidth() ) + aPos.AdjustX(( rRect.GetWidth() - aImageSize.Width() ) / 2 ); + + if ( aImageSize.Height() < rRect.GetHeight() ) + aPos.AdjustY(( rRect.GetHeight() - aImageSize.Height() ) / 2 ); + + if ( IsZoom() ) + rDev.DrawImage( aPos, aImageSize, aImage ); + else + rDev.DrawImage( aPos, aImage ); + + if (rDev.IsClipRegion()) + rDev.SetClipRegion(); + } + } + + + void EditBrowseBox::ImplStartTracking() + { + bActiveBeforeTracking = IsEditing(); + if ( bActiveBeforeTracking ) + { + DeactivateCell(); + PaintImmediately(); + } + + BrowseBox::ImplStartTracking(); + } + + + void EditBrowseBox::ImplEndTracking() + { + if ( bActiveBeforeTracking ) + ActivateCell(); + bActiveBeforeTracking = false; + + BrowseBox::ImplEndTracking(); + } + + + void EditBrowseBox::RowHeightChanged() + { + if ( IsEditing() ) + { + tools::Rectangle aRect( GetCellRect( nEditRow, nEditCol, false ) ); + CellControllerRef aCellController( Controller() ); + ResizeController( aCellController, aRect ); + aCellController->GetWindow().GrabFocus(); + } + + BrowseBox::RowHeightChanged(); + } + + + EditBrowseBox::RowStatus EditBrowseBox::GetRowStatus(sal_Int32) const + { + return CLEAN; + } + + + void EditBrowseBox::KeyInput( const KeyEvent& rEvt ) + { + sal_uInt16 nCode = rEvt.GetKeyCode().GetCode(); + bool bShift = rEvt.GetKeyCode().IsShift(); + bool bCtrl = rEvt.GetKeyCode().IsMod1(); + + switch (nCode) + { + case KEY_RETURN: + if (!bCtrl && !bShift && IsTabAllowed(true)) + { + Dispatch(BROWSER_CURSORRIGHT); + } + else + BrowseBox::KeyInput(rEvt); + return; + case KEY_TAB: + if (!bCtrl && !bShift) + { + if (IsTabAllowed(true)) + Dispatch(BROWSER_CURSORRIGHT); + else + // do NOT call BrowseBox::KeyInput : this would handle the tab, but we already now + // that tab isn't allowed here. So give the Control class a chance + Control::KeyInput(rEvt); + return; + } + else if (!bCtrl && bShift) + { + if (IsTabAllowed(false)) + Dispatch(BROWSER_CURSORLEFT); + else + // do NOT call BrowseBox::KeyInput : this would handle the tab, but we already now + // that tab isn't allowed here. So give the Control class a chance + Control::KeyInput(rEvt); + return; + } + [[fallthrough]]; + default: + BrowseBox::KeyInput(rEvt); + } + } + + void EditBrowseBox::ChildFocusIn() + { + DetermineFocus(getRealGetFocusFlags(this)); + } + + void EditBrowseBox::ChildFocusOut() + { + DetermineFocus(); + } + + void EditBrowseBox::MouseButtonDown(const BrowserMouseEvent& rEvt) + { + // absorb double clicks + if (rEvt.GetClicks() > 1 && rEvt.GetRow() >= 0) + return; + + // we are about to leave the current cell. If there is a "this cell has been modified" notification + // pending (asynchronously), this may be deadly -> do it synchronously + if ( nCellModifiedEvent ) + { + Application::RemoveUserEvent( nCellModifiedEvent ); + nCellModifiedEvent = nullptr; + LINK( this, EditBrowseBox, CellModifiedHdl ).Call( nullptr ); + } + + if (rEvt.GetColumnId() == HandleColumnId) + { // it was the handle column. save the current cell content if necessary + // (clicking on the handle column results in selecting the current row) + if (IsEditing() && aController->IsValueChangedFromSaved()) + SaveModified(); + } + + aMouseEvent.Set(&rEvt,true); + BrowseBox::MouseButtonDown(rEvt); + aMouseEvent.Clear(); + + if (m_nBrowserFlags & EditBrowseBoxFlags::ACTIVATE_ON_BUTTONDOWN) + { + // the base class does not travel upon MouseButtonDown, but implActivateCellOnMouseEvent assumes we traveled ... + GoToRowColumnId( rEvt.GetRow(), rEvt.GetColumnId() ); + if (rEvt.GetRow() >= 0) + implActivateCellOnMouseEvent(rEvt, false); + } + } + + void EditBrowseBox::MouseButtonUp( const BrowserMouseEvent& rEvt ) + { + // absorb double clicks + if (rEvt.GetClicks() > 1 && rEvt.GetRow() >= 0) + return; + + aMouseEvent.Set(&rEvt,false); + BrowseBox::MouseButtonUp(rEvt); + aMouseEvent.Clear(); + + if (!(m_nBrowserFlags & EditBrowseBoxFlags::ACTIVATE_ON_BUTTONDOWN)) + if (rEvt.GetRow() >= 0) + implActivateCellOnMouseEvent(rEvt, true); + } + + bool EditBrowseBox::ControlHasFocus() const + { + Window* pControlWindow = aController ? &aController->GetWindow() : nullptr; + if (ControlBase* pControlBase = dynamic_cast<ControlBase*>(pControlWindow)) + return pControlBase->ControlHasFocus(); + return pControlWindow && pControlWindow->HasChildPathFocus(); + } + + void EditBrowseBox::implActivateCellOnMouseEvent(const BrowserMouseEvent& _rEvt, bool _bUp) + { + if (!IsEditing()) + ActivateCell(); + else if (IsEditing() && !aController->GetWindow().IsEnabled()) + DeactivateCell(); + else if (IsEditing() && !ControlHasFocus()) + AsynchGetFocus(); + + if (!IsEditing() || !aController->GetWindow().IsEnabled()) + return; + + // forwards the event to the control + aController->ActivatingMouseEvent(_rEvt, _bUp); + } + + void EditBrowseBox::Dispatch( sal_uInt16 _nId ) + { + if ( _nId == BROWSER_ENHANCESELECTION ) + { // this is a workaround for the bug in the base class: + // if the row selection is to be extended (which is what BROWSER_ENHANCESELECTION tells us) + // then the base class does not revert any column selections, while, for doing a "simple" + // selection (BROWSER_SELECT), it does. In fact, it does not only revert the col selection then, + // but also any current row selections. + // This clearly tells me that the both ids are for row selection only - there this behaviour does + // make sense. + // But here, where we have column selection, too, we take care of this ourself. + if ( GetSelectColumnCount( ) ) + { + while ( GetSelectColumnCount( ) ) + SelectColumnPos( + sal::static_int_cast< sal_uInt16 >(FirstSelectedColumn()), + false ); + Select(); + } + } + BrowseBox::Dispatch( _nId ); + } + + bool EditBrowseBox::ProcessKey(const KeyEvent& rKeyEvent) + { + sal_uInt16 nCode = rKeyEvent.GetKeyCode().GetCode(); + bool bShift = rKeyEvent.GetKeyCode().IsShift(); + bool bCtrl = rKeyEvent.GetKeyCode().IsMod1(); + bool bAlt = rKeyEvent.GetKeyCode().IsMod2(); + bool bLocalSelect = false; + bool bNonEditOnly = false; + sal_uInt16 nId = BROWSER_NONE; + + if (!bAlt && !bCtrl && !bShift ) + switch ( nCode ) + { + case KEY_DOWN: nId = BROWSER_CURSORDOWN; break; + case KEY_UP: nId = BROWSER_CURSORUP; break; + case KEY_PAGEDOWN: nId = BROWSER_CURSORPAGEDOWN; break; + case KEY_PAGEUP: nId = BROWSER_CURSORPAGEUP; break; + case KEY_HOME: nId = BROWSER_CURSORHOME; break; + case KEY_END: nId = BROWSER_CURSOREND; break; + + case KEY_TAB: + // ask if traveling to the next cell is allowed + if (IsTabAllowed(true)) + nId = BROWSER_CURSORRIGHT; + break; + + case KEY_RETURN: + // save the cell content (if necessary) + if (IsEditing() && aController->IsValueChangedFromSaved() && !SaveModified()) + { + // maybe we're not visible ... + EnableAndShow(); + aController->GetWindow().GrabFocus(); + return true; + } + // ask if traveling to the next cell is allowed + if (IsTabAllowed(true)) + nId = BROWSER_CURSORRIGHT; + + break; + case KEY_RIGHT: nId = BROWSER_CURSORRIGHT; break; + case KEY_LEFT: nId = BROWSER_CURSORLEFT; break; + case KEY_SPACE: nId = BROWSER_SELECT; bNonEditOnly = bLocalSelect = true; break; + } + + if ( !bAlt && !bCtrl && bShift ) + switch ( nCode ) + { + case KEY_DOWN: nId = BROWSER_SELECTDOWN; bLocalSelect = true; break; + case KEY_UP: nId = BROWSER_SELECTUP; bLocalSelect = true; break; + case KEY_HOME: nId = BROWSER_SELECTHOME; bLocalSelect = true; break; + case KEY_END: nId = BROWSER_SELECTEND; bLocalSelect = true; break; + case KEY_TAB: + if (IsTabAllowed(false)) + nId = BROWSER_CURSORLEFT; + break; + } + + if ( !bAlt && bCtrl && bShift ) + switch ( nCode ) + { + case KEY_SPACE: nId = BROWSER_SELECTCOLUMN; bLocalSelect = true; break; + } + + + if ( !bAlt && bCtrl && !bShift ) + switch ( nCode ) + { + case KEY_DOWN: nId = BROWSER_SCROLLUP; break; + case KEY_UP: nId = BROWSER_SCROLLDOWN; break; + case KEY_PAGEDOWN: nId = BROWSER_CURSORENDOFFILE; break; + case KEY_PAGEUP: nId = BROWSER_CURSORTOPOFFILE; break; + case KEY_HOME: nId = BROWSER_CURSORTOPOFSCREEN; break; + case KEY_END: nId = BROWSER_CURSORENDOFSCREEN; break; + case KEY_SPACE: nId = BROWSER_ENHANCESELECTION; bLocalSelect = true; break; + } + + + if ( ( nId != BROWSER_NONE ) + && ( !IsEditing() + || ( !bNonEditOnly + && aController->MoveAllowed(rKeyEvent) + ) + ) + ) + { + if (nId == BROWSER_SELECT || BROWSER_SELECTCOLUMN == nId ) + { + // save the cell content (if necessary) + if (IsEditing() && aController->IsValueChangedFromSaved() && !SaveModified()) + { + // maybe we're not visible ... + EnableAndShow(); + aController->GetWindow().GrabFocus(); + return true; + } + } + + Dispatch(nId); + + if (bLocalSelect && (GetSelectRowCount() || GetSelection() != nullptr)) + DeactivateCell(); + return true; + } + return false; + } + + bool EditBrowseBox::PreNotify(NotifyEvent& rEvt) + { + if (rEvt.GetType() == MouseNotifyEvent::KEYINPUT) + { + if ( (IsEditing() && ControlHasFocus()) + || rEvt.GetWindow() == &GetDataWindow() + || (!IsEditing() && HasChildPathFocus()) + ) + { + if (ProcessKey(*rEvt.GetKeyEvent())) + return true; + } + } + return BrowseBox::PreNotify(rEvt); + } + + bool EditBrowseBox::IsTabAllowed(bool) const + { + return true; + } + + + bool EditBrowseBox::EventNotify(NotifyEvent& rEvt) + { + switch (rEvt.GetType()) + { + case MouseNotifyEvent::GETFOCUS: + DetermineFocus(getRealGetFocusFlags(this)); + break; + + case MouseNotifyEvent::LOSEFOCUS: + DetermineFocus(); + break; + + default: + break; + } + return BrowseBox::EventNotify(rEvt); + } + + + void EditBrowseBox::StateChanged( StateChangedType nType ) + { + BrowseBox::StateChanged( nType ); + + bool bNeedCellReActivation = false; + if ( nType == StateChangedType::Mirroring ) + { + bNeedCellReActivation = true; + } + else if ( nType == StateChangedType::Zoom ) + { + ImplInitSettings( true, false, false ); + bNeedCellReActivation = true; + } + else if ( 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(); + } + else if (nType == StateChangedType::Style) + { + WinBits nStyle = GetStyle(); + if (!(nStyle & WB_NOTABSTOP) ) + nStyle |= WB_TABSTOP; + + SetStyle(nStyle); + } + if ( bNeedCellReActivation ) + { + if ( IsEditing() ) + { + DeactivateCell(); + ActivateCell(); + } + } + } + + + void EditBrowseBox::DataChanged( const DataChangedEvent& rDCEvt ) + { + BrowseBox::DataChanged( rDCEvt ); + + if ((( rDCEvt.GetType() == DataChangedEventType::SETTINGS ) || + ( rDCEvt.GetType() == DataChangedEventType::DISPLAY )) && + ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE )) + { + ImplInitSettings( true, true, true ); + Invalidate(); + } + } + + void EditBrowseBox::ImplInitSettings( bool bFont, bool bForeground, bool bBackground ) + { + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + + if (bFont) + { + vcl::Font aFont = rStyleSettings.GetFieldFont(); + if (IsControlFont()) + { + GetDataWindow().SetControlFont(GetControlFont()); + aFont.Merge(GetControlFont()); + } + else + GetDataWindow().SetControlFont(); + + GetDataWindow().SetZoomedPointFont(*GetDataWindow().GetOutDev(), aFont); + } + + if (bFont || bForeground) + { + Color aTextColor = rStyleSettings.GetFieldTextColor(); + if (IsControlForeground()) + { + aTextColor = GetControlForeground(); + GetDataWindow().SetControlForeground(aTextColor); + } + else + GetDataWindow().SetControlForeground(); + + GetDataWindow().SetTextColor( aTextColor ); + } + + if (!bBackground) // FIXME: Outside of Paint Hierarchy + return; + + if (GetDataWindow().IsControlBackground()) + { + GetDataWindow().SetControlBackground(GetControlBackground()); + GetDataWindow().SetBackground(GetDataWindow().GetControlBackground()); + GetDataWindow().GetOutDev()->SetFillColor(GetDataWindow().GetControlBackground()); + } + else + { + GetDataWindow().SetControlBackground(); + GetDataWindow().SetBackground(rStyleSettings.GetFieldColor()); + GetDataWindow().GetOutDev()->SetFillColor(rStyleSettings.GetFieldColor()); + } + } + + + bool EditBrowseBox::IsCursorMoveAllowed(sal_Int32 nNewRow, sal_uInt16 nNewColId) const + { + sal_uInt16 nInfo = 0; + + if (GetSelectColumnCount() || (aMouseEvent.Is() && aMouseEvent->GetRow() < 0)) + nInfo |= COLSELECT; + if ((GetSelection() != nullptr && GetSelectRowCount()) || + (aMouseEvent.Is() && aMouseEvent->GetColumnId() == HandleColumnId)) + nInfo |= ROWSELECT; + if (!nInfo && nNewRow != nEditRow) + nInfo |= ROWCHANGE; + if (!nInfo && nNewColId != nEditCol) + nInfo |= COLCHANGE; + + if (nInfo == 0) // nothing happened + return true; + + // save the cell content + if (IsEditing() && aController->IsValueChangedFromSaved() && !const_cast<EditBrowseBox *>(this)->SaveModified()) + { + // maybe we're not visible ... + EnableAndShow(); + aController->GetWindow().GrabFocus(); + return false; + } + + EditBrowseBox * pTHIS = const_cast<EditBrowseBox *> (this); + + // save the cell content if + // a) a selection is being made + // b) the row is changing + if (IsModified() && (nInfo & (ROWCHANGE | COLSELECT | ROWSELECT)) && + !pTHIS->SaveRow()) + { + if (nInfo & COLSELECT || + nInfo & ROWSELECT) + { + // cancel selected + pTHIS->SetNoSelection(); + } + + if (IsEditing()) + { + if (!Controller()->GetWindow().IsVisible()) + { + EnableAndShow(); + } + aController->GetWindow().GrabFocus(); + } + return false; + } + + if (nNewRow != nEditRow) + { + vcl::Window& rWindow = GetDataWindow(); + if ((nEditRow >= 0) && !(GetBrowserFlags() & EditBrowseBoxFlags::NO_HANDLE_COLUMN_CONTENT)) + { + tools::Rectangle aRect = GetFieldRectPixel(nEditRow, 0, false ); + // status cell should be painted if and only if text is displayed + pTHIS->bPaintStatus = ( GetBrowserFlags() & EditBrowseBoxFlags::HANDLE_COLUMN_TEXT ) == EditBrowseBoxFlags::HANDLE_COLUMN_TEXT; + rWindow.Invalidate(aRect); + pTHIS->bPaintStatus = true; + } + + // don't paint during row change + rWindow.EnablePaint(false); + + // the last veto chance for derived classes + if (!pTHIS->CursorMoving(nNewRow, nNewColId)) + { + pTHIS->InvalidateStatusCell(nEditRow); + rWindow.EnablePaint(true); + return false; + } + else + { + rWindow.EnablePaint(true); + return true; + } + } + else + return pTHIS->CursorMoving(nNewRow, nNewColId); + } + + + void EditBrowseBox::ColumnMoved(sal_uInt16 nId) + { + BrowseBox::ColumnMoved(nId); + if (IsEditing()) + { + tools::Rectangle aRect( GetCellRect(nEditRow, nEditCol, false)); + CellControllerRef aControllerRef = Controller(); + ResizeController(aControllerRef, aRect); + Controller()->GetWindow().GrabFocus(); + } + } + + + bool EditBrowseBox::SaveRow() + { + return true; + } + + + bool EditBrowseBox::CursorMoving(sal_Int32, sal_uInt16) + { + DeactivateCell(false); + return true; + } + + + void EditBrowseBox::CursorMoved() + { + sal_Int32 nNewRow = GetCurRow(); + if (nEditRow != nNewRow) + { + if (!(GetBrowserFlags() & EditBrowseBoxFlags::NO_HANDLE_COLUMN_CONTENT)) + InvalidateStatusCell(nNewRow); + nEditRow = nNewRow; + } + ActivateCell(); + GetDataWindow().EnablePaint(true); + // should not be called here because the descant event is not needed here + //BrowseBox::CursorMoved(); + } + + + void EditBrowseBox::EndScroll() + { + if (IsEditing()) + { + tools::Rectangle aRect = GetCellRect(nEditRow, nEditCol, false); + ResizeController(aController,aRect); + AsynchGetFocus(); + } + BrowseBox::EndScroll(); + } + + + void EditBrowseBox::ActivateCell(sal_Int32 nRow, sal_uInt16 nCol, bool bCellFocus) + { + if (IsEditing()) + return; + + nEditCol = nCol; + + if ((GetSelectRowCount() && GetSelection() != nullptr) || GetSelectColumnCount() || + (aMouseEvent.Is() && (aMouseEvent.IsDown() || aMouseEvent->GetClicks() > 1))) // nothing happens on MouseDown + { + return; + } + + if (nEditRow < 0 || nEditCol <= HandleColumnId) + return; + + aController = GetController(nRow, nCol); + if (aController.is()) + { + tools::Rectangle aRect( GetCellRect(nEditRow, nEditCol, false)); + ResizeController(aController, aRect); + + InitController(aController, nEditRow, nEditCol); + + aController->SaveValue(); + aController->SetModifyHdl(LINK(this,EditBrowseBox,ModifyHdl)); + EnableAndShow(); + + if ( isAccessibleAlive() ) + implCreateActiveAccessible(); + + // activate the cell only of the browser has the focus + if ( bHasFocus && bCellFocus ) + AsynchGetFocus(); + } + else + { + // no controller -> we have a new "active descendant" + if ( isAccessibleAlive() && HasFocus() ) + { + commitTableEvent( + ACTIVE_DESCENDANT_CHANGED, + Any( CreateAccessibleCell( nRow, GetColumnPos( nCol -1) ) ), + Any() + ); + } + } + } + + + void EditBrowseBox::DeactivateCell(bool bUpdate) + { + if (!IsEditing()) + return; + + if ( isAccessibleAlive() ) + { + commitBrowseBoxEvent( CHILD, Any(), Any( m_aImpl->m_xActiveCell ) ); + m_aImpl->clearActiveCell(); + } + + aOldController = aController; + aController.clear(); + + // reset the modify handler + aOldController->SetModifyHdl(Link<LinkParamNone*,void>()); + + if (bHasFocus) + GrabFocus(); // ensure that we have (and keep) the focus + + aOldController->suspend(); + + // update if requested + if (bUpdate) + PaintImmediately(); + + // release the controller (asynchronously) + if (nEndEvent) + Application::RemoveUserEvent(nEndEvent); + nEndEvent = Application::PostUserEvent(LINK(this,EditBrowseBox,EndEditHdl), nullptr, true); + } + + + tools::Rectangle EditBrowseBox::GetCellRect(sal_Int32 nRow, sal_uInt16 nColId, bool bRel) const + { + tools::Rectangle aRect( GetFieldRectPixel(nRow, nColId, bRel)); + if ((GetMode() & BrowserMode::CURSOR_WO_FOCUS) == BrowserMode::CURSOR_WO_FOCUS) + { + aRect.AdjustTop(1 ); + aRect.AdjustBottom( -1 ); + } + return aRect; + } + + + IMPL_LINK_NOARG(EditBrowseBox, EndEditHdl, void*, void) + { + nEndEvent = nullptr; + + aOldController = CellControllerRef(); + } + + + IMPL_LINK_NOARG(EditBrowseBox, ModifyHdl, LinkParamNone*, void) + { + if (nCellModifiedEvent) + Application::RemoveUserEvent(nCellModifiedEvent); + nCellModifiedEvent = Application::PostUserEvent(LINK(this,EditBrowseBox,CellModifiedHdl), nullptr, true); + } + + + IMPL_LINK_NOARG(EditBrowseBox, CellModifiedHdl, void*, void) + { + nCellModifiedEvent = nullptr; + CellModified(); + } + + void EditBrowseBox::ColumnResized( sal_uInt16 ) + { + if (IsEditing()) + { + tools::Rectangle aRect( GetCellRect(nEditRow, nEditCol, false)); + CellControllerRef aControllerRef = Controller(); + ResizeController(aControllerRef, aRect); + // don't grab focus if Field Properties panel is being + // resized by split pane drag resizing + if (Application::IsUICaptured()) + return; + Controller()->GetWindow().GrabFocus(); + } + } + + sal_uInt16 EditBrowseBox::AppendColumn(const OUString& rName, sal_uInt16 nWidth, sal_uInt16 nPos, sal_uInt16 nId) + { + if (nId == BROWSER_INVALIDID) + { + // look for the next free id + for (nId = ColCount(); nId > 0 && GetColumnPos(nId) != BROWSER_INVALIDID; nId--) + ; + + if (!nId) + { + // if there is no handle column + // increment the id + if ( ColCount() == 0 || GetColumnId(0) != HandleColumnId ) + nId = ColCount() + 1; + } + } + + DBG_ASSERT(nId, "EditBrowseBox::AppendColumn: invalid id!"); + + tools::Long w = nWidth; + if (!w) + w = GetDefaultColumnWidth(rName); + + InsertDataColumn(nId, rName, w, (HeaderBarItemBits::CENTER | HeaderBarItemBits::CLICKABLE), nPos); + return nId; + } + + void EditBrowseBox::Resize() + { + BrowseBox::Resize(); + + // if the window is smaller than "title line height" + "control area", + // do nothing + if (GetOutputSizePixel().Height() < + (GetControlArea().GetHeight() + GetDataWindow().GetPosPixel().Y())) + return; + + // the size of the control area + Point aPoint(GetControlArea().TopLeft()); + sal_uInt16 nX = static_cast<sal_uInt16>(aPoint.X()); + + ArrangeControls(nX, static_cast<sal_uInt16>(aPoint.Y())); + + if (!nX) + nX = USHRT_MAX; + + bool bChanged = ReserveControlArea(nX); + + //tdf#97731 if the reserved area changed size, give the controls a + //chance to adapt to the new size + if (bChanged) + { + nX = static_cast<sal_uInt16>(aPoint.X()); + ArrangeControls(nX, static_cast<sal_uInt16>(aPoint.Y())); + } + } + + void EditBrowseBox::ArrangeControls(sal_uInt16&, sal_uInt16) + { + } + + CellController* EditBrowseBox::GetController(sal_Int32, sal_uInt16) + { + return nullptr; + } + + void EditBrowseBox::ResizeController(CellControllerRef const & rController, const tools::Rectangle& rRect) + { + Point aPoint(rRect.TopLeft()); + Size aSize(rRect.GetSize()); + Control& rControl = rController->GetWindow(); + auto nMinHeight = rControl.get_preferred_size().Height(); + if (nMinHeight > aSize.Height()) + { + auto nOffset = (nMinHeight - aSize.Height()) / 2; + aPoint.AdjustY(-nOffset); + aSize.setHeight(nMinHeight); + } + rControl.SetPosSizePixel(aPoint, aSize); + } + + void EditBrowseBox::InitController(CellControllerRef&, sal_Int32, sal_uInt16) + { + } + + + void EditBrowseBox::CellModified() + { + } + + + bool EditBrowseBox::SaveModified() + { + return true; + } + + + void EditBrowseBox::DoubleClick(const BrowserMouseEvent& rEvt) + { + // when double clicking on the column, the optimum size will be calculated + sal_uInt16 nColId = rEvt.GetColumnId(); + if (nColId != HandleColumnId) + SetColumnWidth(nColId, GetAutoColumnWidth(nColId)); + } + + + sal_uInt32 EditBrowseBox::GetAutoColumnWidth(sal_uInt16 nColId) + { + sal_uInt32 nCurColWidth = GetColumnWidth(nColId); + sal_uInt32 nMinColWidth = CalcZoom(20); // minimum + sal_uInt32 nNewColWidth = nMinColWidth; + sal_Int32 nMaxRows = std::min(sal_Int32(GetVisibleRows()), GetRowCount()); + sal_Int32 nLastVisRow = GetTopRow() + nMaxRows - 1; + + if (GetTopRow() <= nLastVisRow) // calc the column with using the cell contents + { + for (tools::Long i = GetTopRow(); i <= nLastVisRow; ++i) + nNewColWidth = std::max(nNewColWidth,GetTotalCellWidth(i,nColId) + 12); + + if (nNewColWidth == nCurColWidth) // size has not changed + nNewColWidth = GetDefaultColumnWidth(GetColumnTitle(nColId)); + } + else + nNewColWidth = GetDefaultColumnWidth(GetColumnTitle(nColId)); + return nNewColWidth; + } + + sal_uInt32 EditBrowseBox::GetTotalCellWidth(sal_Int32, sal_uInt16) + { + return 0; + } + + void EditBrowseBox::InvalidateHandleColumn() + { + tools::Rectangle aHdlFieldRect( GetFieldRectPixel( 0, 0 )); + tools::Rectangle aInvalidRect( Point(0,0), GetOutputSizePixel() ); + aInvalidRect.SetRight( aHdlFieldRect.Right() ); + Invalidate( aInvalidRect ); + } + + void EditBrowseBox::PaintTristate(const tools::Rectangle& rRect, const TriState& eState, bool _bEnabled) const + { + pCheckBoxPaint->SetState(eState); + + pCheckBoxPaint->GetBox().set_sensitive(_bEnabled); + + Size aBoxSize = pCheckBoxPaint->GetBox().get_preferred_size(); + tools::Rectangle aRect(Point(rRect.Left() + ((rRect.GetWidth() - aBoxSize.Width()) / 2), + rRect.Top() + ((rRect.GetHeight() - aBoxSize.Height()) / 2)), + aBoxSize); + pCheckBoxPaint->SetPosSizePixel(aRect.TopLeft(), aRect.GetSize()); + + pCheckBoxPaint->Draw(GetDataWindow().GetOutDev(), aRect.TopLeft(), SystemTextColorFlags::NONE); + } + + void EditBrowseBox::AsynchGetFocus() + { + if (nStartEvent) + Application::RemoveUserEvent(nStartEvent); + + m_pFocusWhileRequest = Application::GetFocusWindow(); + nStartEvent = Application::PostUserEvent(LINK(this,EditBrowseBox,StartEditHdl), nullptr, true); + } + + + void EditBrowseBox::SetBrowserFlags(EditBrowseBoxFlags nFlags) + { + if (m_nBrowserFlags == nFlags) + return; + + bool RowPicturesChanges = ((m_nBrowserFlags & EditBrowseBoxFlags::NO_HANDLE_COLUMN_CONTENT) != + (nFlags & EditBrowseBoxFlags::NO_HANDLE_COLUMN_CONTENT)); + m_nBrowserFlags = nFlags; + + if (RowPicturesChanges) + InvalidateStatusCell(GetCurRow()); + } + + inline void EditBrowseBox::EnableAndShow() const + { + Controller()->resume(); + } + + CellController::CellController(ControlBase* pW) + : pWindow(pW) + , bSuspended( true ) + { + + DBG_ASSERT(pWindow, "CellController::CellController: missing the window!"); + DBG_ASSERT(!pWindow->IsVisible(), "CellController::CellController: window should not be visible!"); + } + + CellController::~CellController() + { + } + + void CellController::suspend( ) + { + DBG_ASSERT( bSuspended == !GetWindow().IsVisible(), "CellController::suspend: inconsistence!" ); + if ( !isSuspended( ) ) + { + CommitModifications(); + GetWindow().Hide( ); + GetWindow().Disable( ); + bSuspended = true; + } + } + + void CellController::resume( ) + { + DBG_ASSERT( bSuspended == !GetWindow().IsVisible(), "CellController::resume: inconsistence!" ); + if ( isSuspended( ) ) + { + GetWindow().Enable( ); + GetWindow().Show( ); + bSuspended = false; + } + } + + void CellController::CommitModifications() + { + // nothing to do in this base class + } + + void CellController::ActivatingMouseEvent(const BrowserMouseEvent& /*rEvt*/, bool /*bUp*/) + { + // nothing to do in this base class + } + + bool CellController::MoveAllowed(const KeyEvent&) const + { + return true; + } + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/brwbox/editbrowsebox2.cxx b/svtools/source/brwbox/editbrowsebox2.cxx new file mode 100644 index 000000000..0c6adee04 --- /dev/null +++ b/svtools/source/brwbox/editbrowsebox2.cxx @@ -0,0 +1,200 @@ +/* -*- 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 <svtools/editbrowsebox.hxx> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include "editbrowseboximpl.hxx" +#include <comphelper/types.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/accessiblefactory.hxx> +#include <vcl/svapp.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> + +namespace svt +{ + using namespace com::sun::star::accessibility; + using namespace com::sun::star::uno; + using namespace ::com::sun::star::accessibility::AccessibleEventId; + + +Reference< XAccessible > EditBrowseBox::CreateAccessibleCheckBoxCell(sal_Int32 _nRow, sal_uInt16 _nColumnPos,const TriState& eState) +{ + Reference< XAccessible > xAccessible( GetAccessible() ); + Reference< XAccessibleContext > xAccContext; + if ( xAccessible.is() ) + xAccContext = xAccessible->getAccessibleContext(); + + Reference< XAccessible > xReturn; + if ( xAccContext.is() ) + { + xReturn = getAccessibleFactory().createAccessibleCheckBoxCell( + xAccContext->getAccessibleChild( ::vcl::BBINDEX_TABLE ), + *this, + nullptr, + _nRow, + _nColumnPos, + eState, + true + ); + } + return xReturn; +} + +sal_Int32 EditBrowseBox::GetAccessibleControlCount() const +{ + return IsEditing() ? 1 : 0; +} + +void EditBrowseBox::implCreateActiveAccessible( ) +{ + DBG_ASSERT( IsEditing(), "EditBrowseBox::implCreateActiveAccessible: not to be called if we're not editing currently!" ); + DBG_ASSERT( !m_aImpl->m_xActiveCell.is(), "EditBrowseBox::implCreateActiveAccessible: not to be called if the old one is still alive!" ); + + if ( m_aImpl->m_xActiveCell.is() || !IsEditing() ) + return; + + Reference< XAccessible > xCont = aController->GetWindow().GetAccessible(); + Reference< XAccessible > xMy = GetAccessible(); + if ( !(xMy.is() && xCont.is()) ) + return; + + m_aImpl->m_xActiveCell = getAccessibleFactory().createEditBrowseBoxTableCellAccess( + xMy, // parent accessible + xCont, // control accessible + VCLUnoHelper::GetInterface( &aController->GetWindow() ), // focus window (for notifications) + *this, // the browse box + GetCurRow(), + GetColumnPos( GetCurColumnId() ) + ); + + commitBrowseBoxEvent( CHILD, Any( m_aImpl->m_xActiveCell ), Any() ); +} + + +Reference< XAccessible > EditBrowseBox::CreateAccessibleControl( sal_Int32 _nIndex ) +{ + DBG_ASSERT( 0 == _nIndex, "EditBrowseBox::CreateAccessibleControl: invalid index!" ); + + if ( isAccessibleAlive() ) + { + if ( !m_aImpl->m_xActiveCell.is() ) + implCreateActiveAccessible(); + } + + return m_aImpl->m_xActiveCell; +} + +void EditBrowseBoxImpl::clearActiveCell() +{ + try + { + ::comphelper::disposeComponent(m_xActiveCell); + } + catch(const Exception&) + { + TOOLS_WARN_EXCEPTION( "svtools", "EditBrowseBoxImpl::clearActiveCell: caught an exception while disposing the AccessibleCell!" ); + } + + m_xActiveCell = nullptr; +} + +void EditBrowseBox::GrabTableFocus() +{ + if ( aController.is() ) + aController->GetWindow().GrabFocus(); +} + +void EditBrowseBox::DetermineFocus( const GetFocusFlags _nGetFocusFlags ) +{ + bool bFocus = ControlHasFocus(); + for (vcl::Window* pWindow = Application::GetFocusWindow(); + pWindow && !bFocus; + pWindow = pWindow->GetParent()) + bFocus = pWindow == this; + + if (bFocus == bHasFocus) + return; + + bHasFocus = bFocus; + + if ( !(GetBrowserFlags( ) & EditBrowseBoxFlags::SMART_TAB_TRAVEL) ) + return; + + if ( !(bHasFocus // we got the focus + && ( _nGetFocusFlags & GetFocusFlags::Tab )) // using the TAB key + ) + return; + + sal_Int32 nRows = GetRowCount(); + sal_uInt16 nCols = ColCount(); + + if (( nRows <= 0 ) || ( nCols <= 0 )) + return; + + if ( _nGetFocusFlags & GetFocusFlags::Forward ) + { + if ( GetColumnId( 0 ) != HandleColumnId ) + { + GoToRowColumnId( 0, GetColumnId( 0 ) ); + } + else + { // the first column is the handle column -> not focussable + if ( nCols > 1 ) + GoToRowColumnId( 0, GetColumnId( 1 ) ); + } + } + else if ( _nGetFocusFlags & GetFocusFlags::Backward ) + { + GoToRowColumnId( nRows - 1, GetColumnId( nCols -1 ) ); + } +} + +tools::Rectangle EditBrowseBox::GetFieldCharacterBounds(sal_Int32 _nRow,sal_Int32 _nColumnPos,sal_Int32 _nIndex) +{ + tools::Rectangle aRect; + if ( SeekRow(_nRow) ) + { + CellController* pController = GetController( + _nRow, GetColumnId( sal::static_int_cast< sal_uInt16 >(_nColumnPos) ) ); + if ( pController ) + aRect = pController->GetWindow().GetCharacterBounds(_nIndex); + } + return aRect; +} + +sal_Int32 EditBrowseBox::GetFieldIndexAtPoint(sal_Int32 _nRow,sal_Int32 _nColumnPos,const Point& _rPoint) +{ + sal_Int32 nRet = -1; + if ( SeekRow(_nRow) ) + { + CellController* pController = GetController( + _nRow, GetColumnId( sal::static_int_cast< sal_uInt16 >(_nColumnPos) ) ); + if ( pController ) + nRet = pController->GetWindow().GetIndexForPoint(_rPoint); + } + return nRet; +} + + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/brwbox/editbrowseboximpl.hxx b/svtools/source/brwbox/editbrowseboximpl.hxx new file mode 100644 index 000000000..f71aab9b7 --- /dev/null +++ b/svtools/source/brwbox/editbrowseboximpl.hxx @@ -0,0 +1,35 @@ +/* -*- 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 <com/sun/star/accessibility/XAccessible.hpp> + +namespace svt +{ + // impl class for the class EditBrowseBox + class EditBrowseBoxImpl + { + public: + css::uno::Reference< css::accessibility::XAccessible > m_xActiveCell; + + void clearActiveCell(); + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/brwbox/recorditemwindow.cxx b/svtools/source/brwbox/recorditemwindow.cxx new file mode 100644 index 000000000..98ff79a27 --- /dev/null +++ b/svtools/source/brwbox/recorditemwindow.cxx @@ -0,0 +1,113 @@ +/* -*- 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 <svtools/recorditemwindow.hxx> +#include <vcl/event.hxx> + +RecordItemWindowBase::RecordItemWindowBase(std::unique_ptr<weld::Entry> xEntry) + : m_xWidget(std::move(xEntry)) +{ + m_xWidget->connect_key_press(LINK(this, RecordItemWindowBase, KeyInputHdl)); + m_xWidget->connect_activate(LINK(this, RecordItemWindowBase, ActivatedHdl)); + m_xWidget->connect_focus_out(LINK(this, RecordItemWindowBase, FocusOutHdl)); + + m_xWidget->show(); +} + +RecordItemWindowBase::~RecordItemWindowBase() {} + +RecordItemWindow::RecordItemWindow(vcl::Window* pParent) + : InterimItemWindow(pParent, "svx/ui/absrecbox.ui", "AbsRecBox") + , RecordItemWindowBase(m_xBuilder->weld_entry("entry-frame")) +{ + InitControlBase(m_xWidget.get()); + + auto aPrefSize(m_xWidget->get_preferred_size()); + + m_xWidget->set_width_chars(1); // so a smaller than default width can be used later + + SetSizePixel(aPrefSize); +} + +void RecordItemWindow::dispose() +{ + m_xWidget.reset(); + InterimItemWindow::dispose(); +} + +RecordItemWindow::~RecordItemWindow() { disposeOnce(); } + +void RecordItemWindowBase::FirePosition(bool _bForce) +{ + if (!_bForce && !m_xWidget->get_value_changed_from_saved()) + return; + + sal_Int64 nRecord = m_xWidget->get_text().toInt64(); + if (nRecord < 1) + nRecord = 1; + + PositionFired(nRecord); + + m_xWidget->save_value(); +} + +IMPL_LINK_NOARG(RecordItemWindowBase, FocusOutHdl, weld::Widget&, void) { FirePosition(false); } + +bool RecordItemWindowBase::DoKeyInput(const KeyEvent& rKEvt) +{ + vcl::KeyCode aCode = rKEvt.GetKeyCode(); + bool bUp = (aCode.GetCode() == KEY_UP); + bool bDown = (aCode.GetCode() == KEY_DOWN); + + if (!aCode.IsShift() && !aCode.IsMod1() && !aCode.IsMod2() && (bUp || bDown)) + { + sal_Int64 nRecord = m_xWidget->get_text().toInt64(); + if (bUp) + ++nRecord; + else + --nRecord; + if (nRecord < 1) + nRecord = 1; + m_xWidget->set_text(OUString::number(nRecord)); + return true; + } + + return false; +} + +bool RecordItemWindow::DoKeyInput(const KeyEvent& rKEvt) +{ + return RecordItemWindowBase::DoKeyInput(rKEvt) || ChildKeyInput(rKEvt); +} + +void RecordItemWindowBase::PositionFired(sal_Int64 /*nRecord*/) {} + +IMPL_LINK(RecordItemWindowBase, KeyInputHdl, const KeyEvent&, rKEvt, bool) +{ + return DoKeyInput(rKEvt); +} + +IMPL_LINK_NOARG(RecordItemWindowBase, ActivatedHdl, weld::Entry&, bool) +{ + if (!m_xWidget->get_text().isEmpty()) + FirePosition(true); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/config/accessibilityoptions.cxx b/svtools/source/config/accessibilityoptions.cxx new file mode 100644 index 000000000..4922326fa --- /dev/null +++ b/svtools/source/config/accessibilityoptions.cxx @@ -0,0 +1,390 @@ +/* -*- 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 <svtools/accessibilityoptions.hxx> + +#include <unotools/configmgr.hxx> +#include <com/sun/star/uno/Any.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <comphelper/configurationhelper.hxx> +#include <comphelper/processfactory.hxx> + +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <tools/diagnose_ex.h> +#include <mutex> + +#include "itemholder2.hxx" + +using namespace utl; +using namespace com::sun::star::uno; + +#define HELP_TIP_TIMEOUT 0xffff // max. timeout setting to pretend a non-timeout + +// class SvtAccessibilityOptions_Impl --------------------------------------------- + +class SvtAccessibilityOptions_Impl +{ +private: + css::uno::Reference< css::container::XNameAccess > m_xCfg; + css::uno::Reference< css::beans::XPropertySet > m_xNode; + +public: + SvtAccessibilityOptions_Impl(); + + void SetVCLSettings(); + bool GetIsHelpTipsDisappear() const; + bool GetIsAllowAnimatedGraphics() const; + bool GetIsAllowAnimatedText() const; + bool GetIsAutomaticFontColor() const; + sal_Int16 GetHelpTipSeconds() const; + bool IsSelectionInReadonly() const; + sal_Int16 GetEdgeBlending() const; + sal_Int16 GetListBoxMaximumLineCount() const; + sal_Int16 GetColorValueSetColumnCount() const; + bool GetPreviewUsesCheckeredBackground() const; +}; + +// initialization of static members -------------------------------------- + +SvtAccessibilityOptions_Impl* SvtAccessibilityOptions::sm_pSingleImplConfig =nullptr; +sal_Int32 SvtAccessibilityOptions::sm_nAccessibilityRefCount(0); + +namespace +{ + std::mutex& SingletonMutex() + { + static std::mutex SINGLETON; + return SINGLETON; + } +} + + +// class SvtAccessibilityOptions_Impl --------------------------------------------- + +SvtAccessibilityOptions_Impl::SvtAccessibilityOptions_Impl() +{ + try + { + m_xCfg.set( + ::comphelper::ConfigurationHelper::openConfig( + comphelper::getProcessComponentContext(), + "org.openoffice.Office.Common/Accessibility", + ::comphelper::EConfigurationModes::Standard ), + css::uno::UNO_QUERY); + m_xNode.set(m_xCfg, css::uno::UNO_QUERY); + } + catch(const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.config"); + m_xCfg.clear(); + } +} + +bool SvtAccessibilityOptions_Impl::GetIsHelpTipsDisappear() const +{ + bool bRet = true; + + try + { + if(m_xNode.is()) + m_xNode->getPropertyValue("IsHelpTipsDisappear") >>= bRet; + } + catch(const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.config"); + } + + return bRet; +} + +bool SvtAccessibilityOptions_Impl::GetIsAllowAnimatedGraphics() const +{ + bool bRet = true; + + try + { + if(m_xNode.is()) + m_xNode->getPropertyValue("IsAllowAnimatedGraphics") >>= bRet; + } + catch(const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.config"); + } + + return bRet; +} + +bool SvtAccessibilityOptions_Impl::GetIsAllowAnimatedText() const +{ + bool bRet = true; + + try + { + static constexpr OUStringLiteral PROPNAME = u"IsAllowAnimatedText"; + if(m_xNode.is()) + m_xNode->getPropertyValue(PROPNAME) >>= bRet; + } + catch(const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.config"); + } + + return bRet; +} + +bool SvtAccessibilityOptions_Impl::GetIsAutomaticFontColor() const +{ + bool bRet = false; + + try + { + if(m_xNode.is()) + m_xNode->getPropertyValue("IsAutomaticFontColor") >>= bRet; + } + catch(const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.config"); + } + + return bRet; +} + +sal_Int16 SvtAccessibilityOptions_Impl::GetHelpTipSeconds() const +{ + sal_Int16 nRet = 4; + + try + { + if(m_xNode.is()) + m_xNode->getPropertyValue("HelpTipSeconds") >>= nRet; + } + catch(const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.config"); + } + + return nRet; +} + +bool SvtAccessibilityOptions_Impl::IsSelectionInReadonly() const +{ + bool bRet = false; + + try + { + if(m_xNode.is()) + m_xNode->getPropertyValue("IsSelectionInReadonly") >>= bRet; + } + catch(const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.config"); + } + + return bRet; +} + +sal_Int16 SvtAccessibilityOptions_Impl::GetEdgeBlending() const +{ + sal_Int16 nRet = 35; + + try + { + if(m_xNode.is()) + m_xNode->getPropertyValue("EdgeBlending") >>= nRet; + } + catch(const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.config"); + } + + return nRet; +} + +sal_Int16 SvtAccessibilityOptions_Impl::GetListBoxMaximumLineCount() const +{ + sal_Int16 nRet = 25; + + try + { + if(m_xNode.is()) + m_xNode->getPropertyValue("ListBoxMaximumLineCount") >>= nRet; + } + catch(const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.config"); + } + + return nRet; +} + +sal_Int16 SvtAccessibilityOptions_Impl::GetColorValueSetColumnCount() const +{ +#ifdef IOS + return 4; +#else + sal_Int16 nRet = 12; + + try + { + if(m_xNode.is()) + m_xNode->getPropertyValue("ColorValueSetColumnCount") >>= nRet; + } + catch(const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.config"); + } + + return nRet; +#endif +} + +bool SvtAccessibilityOptions_Impl::GetPreviewUsesCheckeredBackground() const +{ + bool bRet = false; + + try + { + if(m_xNode.is()) + m_xNode->getPropertyValue("PreviewUsesCheckeredBackground") >>= bRet; + } + catch(const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.config"); + } + + return bRet; +} + +void SvtAccessibilityOptions_Impl::SetVCLSettings() +{ + AllSettings aAllSettings(Application::GetSettings()); + StyleSettings aStyleSettings(aAllSettings.GetStyleSettings()); + HelpSettings aHelpSettings(aAllSettings.GetHelpSettings()); + bool StyleSettingsChanged(false); + + aHelpSettings.SetTipTimeout( GetIsHelpTipsDisappear() ? GetHelpTipSeconds() * 1000 : HELP_TIP_TIMEOUT); + aAllSettings.SetHelpSettings(aHelpSettings); + + const sal_Int16 nEdgeBlendingCountA(GetEdgeBlending()); + OSL_ENSURE(nEdgeBlendingCountA >= 0, "OOps, negative values for EdgeBlending are not allowed (!)"); + const sal_uInt16 nEdgeBlendingCountB(static_cast< sal_uInt16 >(nEdgeBlendingCountA >= 0 ? nEdgeBlendingCountA : 0)); + + if(aStyleSettings.GetEdgeBlending() != nEdgeBlendingCountB) + { + aStyleSettings.SetEdgeBlending(nEdgeBlendingCountB); + StyleSettingsChanged = true; + } + + const sal_Int16 nMaxLineCountA(GetListBoxMaximumLineCount()); + OSL_ENSURE(nMaxLineCountA >= 0, "OOps, negative values for ListBoxMaximumLineCount are not allowed (!)"); + const sal_uInt16 nMaxLineCountB(static_cast< sal_uInt16 >(nMaxLineCountA >= 0 ? nMaxLineCountA : 0)); + + if(aStyleSettings.GetListBoxMaximumLineCount() != nMaxLineCountB) + { + aStyleSettings.SetListBoxMaximumLineCount(nMaxLineCountB); + StyleSettingsChanged = true; + } + + const sal_Int16 nMaxColumnCountA(GetColorValueSetColumnCount()); + OSL_ENSURE(nMaxColumnCountA >= 0, "OOps, negative values for ColorValueSetColumnCount are not allowed (!)"); + const sal_uInt16 nMaxColumnCountB(static_cast< sal_uInt16 >(nMaxColumnCountA >= 0 ? nMaxColumnCountA : 0)); + + if(aStyleSettings.GetColorValueSetColumnCount() != nMaxColumnCountB) + { + aStyleSettings.SetColorValueSetColumnCount(nMaxColumnCountB); + StyleSettingsChanged = true; + } + + const bool bPreviewUsesCheckeredBackground(GetPreviewUsesCheckeredBackground()); + + if(aStyleSettings.GetPreviewUsesCheckeredBackground() != bPreviewUsesCheckeredBackground) + { + aStyleSettings.SetPreviewUsesCheckeredBackground(bPreviewUsesCheckeredBackground); + StyleSettingsChanged = true; + } + + if(StyleSettingsChanged) + { + aAllSettings.SetStyleSettings(aStyleSettings); + Application::MergeSystemSettings(aAllSettings); + } + + Application::SetSettings(aAllSettings); +} + +// class SvtAccessibilityOptions -------------------------------------------------- + +SvtAccessibilityOptions::SvtAccessibilityOptions() +{ + if (!utl::ConfigManager::IsFuzzing()) + { + std::unique_lock aGuard( SingletonMutex() ); + if(!sm_pSingleImplConfig) + { + sm_pSingleImplConfig = new SvtAccessibilityOptions_Impl; + aGuard.unlock(); // because holdConfigItem will call this constructor + svtools::ItemHolder2::holdConfigItem(EItem::AccessibilityOptions); + } + ++sm_nAccessibilityRefCount; + } + //StartListening( *sm_pSingleImplConfig, sal_True ); +} + +SvtAccessibilityOptions::~SvtAccessibilityOptions() +{ + //EndListening( *sm_pSingleImplConfig, sal_True ); + std::unique_lock aGuard( SingletonMutex() ); + if( !--sm_nAccessibilityRefCount ) + { + //if( sm_pSingleImplConfig->IsModified() ) + // sm_pSingleImplConfig->Commit(); + delete sm_pSingleImplConfig; + sm_pSingleImplConfig = nullptr; + } +} + +bool SvtAccessibilityOptions::GetIsAllowAnimatedGraphics() const +{ + return sm_pSingleImplConfig->GetIsAllowAnimatedGraphics(); +} +bool SvtAccessibilityOptions::GetIsAllowAnimatedText() const +{ + return sm_pSingleImplConfig->GetIsAllowAnimatedText(); +} +bool SvtAccessibilityOptions::GetIsAutomaticFontColor() const +{ + return sm_pSingleImplConfig->GetIsAutomaticFontColor(); +} +bool SvtAccessibilityOptions::IsSelectionInReadonly() const +{ + return sm_pSingleImplConfig->IsSelectionInReadonly(); +} + + +void SvtAccessibilityOptions::SetVCLSettings() +{ + sm_pSingleImplConfig->SetVCLSettings(); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/config/apearcfg.cxx b/svtools/source/config/apearcfg.cxx new file mode 100644 index 000000000..35670657e --- /dev/null +++ b/svtools/source/config/apearcfg.cxx @@ -0,0 +1,198 @@ +/* -*- 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 <svtools/apearcfg.hxx> + +#include <o3tl/any.hxx> +#include <tools/debug.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> + +#include <com/sun/star/uno/Sequence.hxx> + +#define DEFAULT_DRAGMODE DragMode::SystemDep +#define DEFAULT_SNAPMODE SnapType::ToButton +#define DEFAULT_AAMINHEIGHT 8 + +using namespace ::com::sun::star::uno; + +bool SvtTabAppearanceCfg::bInitialized = false; + +SvtTabAppearanceCfg::SvtTabAppearanceCfg() + :ConfigItem("Office.Common/View") + ,nDragMode ( DEFAULT_DRAGMODE ) + ,nSnapMode ( DEFAULT_SNAPMODE ) + ,nMiddleMouse ( MouseMiddleButtonAction::AutoScroll ) + ,nAAMinPixelHeight ( DEFAULT_AAMINHEIGHT ) + ,bFontAntialiasing ( true ) + ,bMenuMouseFollow ( false ) +{ + const Sequence<OUString>& rNames = GetPropertyNames(); + Sequence<Any> aValues = GetProperties(rNames); + const Any* pValues = aValues.getConstArray(); + DBG_ASSERT(aValues.getLength() == rNames.getLength(), "GetProperties failed"); + + if(aValues.getLength() != rNames.getLength()) + return; + + for(int nProp = 0; nProp < rNames.getLength(); ++nProp, ++pValues) + { + if(pValues->hasValue()) + { + switch(nProp) + { + case 0: //"Window/Drag" + { + short nTmp; + if (*pValues >>= nTmp) + nDragMode = static_cast<DragMode>(nTmp); + break; + } + case 1: bMenuMouseFollow = *o3tl::doAccess<bool>(*pValues); break; //"Menu/FollowMouse", + case 2: + { + short nTmp; + if (*pValues >>= nTmp) + nSnapMode = static_cast<SnapType>(nTmp); //"Dialog/MousePositioning", + break; + } + case 3: { short nTmp = 0; *pValues >>= nTmp; nMiddleMouse = static_cast<MouseMiddleButtonAction>(nTmp); break; } //"Dialog/MiddleMouseButton", + case 4: bFontAntialiasing = *o3tl::doAccess<bool>(*pValues); break; // "FontAntialiasing/Enabled", + case 5: *pValues >>= nAAMinPixelHeight; break; // "FontAntialiasing/MinPixelHeight", + } + } + } +} + +SvtTabAppearanceCfg::~SvtTabAppearanceCfg( ) +{ +} + +const Sequence<OUString>& SvtTabAppearanceCfg::GetPropertyNames() +{ + static Sequence<OUString> const aNames + { + "Window/Drag" // 0 + ,"Menu/FollowMouse" // 1 + ,"Dialog/MousePositioning" // 2 + ,"Dialog/MiddleMouseButton" // 3 + ,"FontAntiAliasing/Enabled" // 4 + ,"FontAntiAliasing/MinPixelHeight" // 5 + }; + return aNames; +} + +void SvtTabAppearanceCfg::ImplCommit() +{ + const Sequence<OUString>& rNames = GetPropertyNames(); + Sequence<Any> aValues(rNames.getLength()); + Any* pValues = aValues.getArray(); + + for(int nProp = 0; nProp < rNames.getLength(); nProp++) + { + switch(nProp) + { + case 0: pValues[nProp] <<= static_cast<short>(nDragMode); break; // "Window/Drag", + case 1: pValues[nProp] <<= bMenuMouseFollow; break; // "Menu/FollowMouse", + case 2: pValues[nProp] <<= static_cast<short>(nSnapMode); break; // "Dialog/MousePositioning", + case 3: pValues[nProp] <<= static_cast<short>(nMiddleMouse); break; // "Dialog/MiddleMouseButton", + case 4: pValues[nProp] <<= bFontAntialiasing; break; // "FontAntialiasing/Enabled", + case 5: pValues[nProp] <<= nAAMinPixelHeight; break; // "FontAntialiasing/MinPixelHeight", + } + } + PutProperties(rNames, aValues); +} + +void SvtTabAppearanceCfg::Notify( const css::uno::Sequence< OUString >& ) +{ +} + +void SvtTabAppearanceCfg::SetSnapMode ( SnapType nSet ) +{ + nSnapMode = nSet; + SetModified(); +} + +void SvtTabAppearanceCfg::SetMiddleMouseButton ( MouseMiddleButtonAction nSet ) +{ + nMiddleMouse = nSet; + SetModified(); +} + +void SvtTabAppearanceCfg::SetApplicationDefaults ( Application* pApp ) +{ + AllSettings hAppSettings = Application::GetSettings(); + StyleSettings hAppStyle = hAppSettings.GetStyleSettings(); + + // Look & Feel + + // SetStandard...Styles() resets the UseSystemUIFonts flag, + // but we don't want to change it now, so save the flag before ... + bool bUseSystemUIFonts = hAppStyle.GetUseSystemUIFonts(); + hAppStyle.SetStandardStyles(); + // and set it here + hAppStyle.SetUseSystemUIFonts( bUseSystemUIFonts ); + + // font anti aliasing + hAppStyle.SetAntialiasingMinPixelHeight( nAAMinPixelHeight ); + hAppStyle.SetDisplayOptions( bFontAntialiasing ? DisplayOptions::NONE : DisplayOptions::AADisable ); + + // Mouse Snap + + MouseSettings hMouseSettings = hAppSettings.GetMouseSettings(); + MouseSettingsOptions nMouseOptions = hMouseSettings.GetOptions(); + + nMouseOptions &= ~ MouseSettingsOptions(MouseSettingsOptions::AutoCenterPos | MouseSettingsOptions::AutoDefBtnPos); + + switch ( nSnapMode ) + { + case SnapType::ToButton: + nMouseOptions |= MouseSettingsOptions::AutoDefBtnPos; + break; + case SnapType::ToMiddle: + nMouseOptions |= MouseSettingsOptions::AutoCenterPos; + break; + case SnapType::NONE: + default: + break; + } + hMouseSettings.SetOptions(nMouseOptions); + hMouseSettings.SetMiddleButtonAction(nMiddleMouse); + + // Merge and Publish Settings + + MouseFollowFlags nFollow = hMouseSettings.GetFollow(); + if(bMenuMouseFollow) + nFollow |= MouseFollowFlags::Menu; + else + nFollow &= ~MouseFollowFlags::Menu; + hMouseSettings.SetFollow( nFollow ); + + hAppSettings.SetMouseSettings( hMouseSettings ); + + hAppSettings.SetStyleSettings( hAppStyle ); + Application::MergeSystemSettings ( hAppSettings ); // Allow system-settings to apply + pApp->OverrideSystemSettings ( hAppSettings ); + + Application::SetSettings ( hAppSettings ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/config/colorcfg.cxx b/svtools/source/config/colorcfg.cxx new file mode 100644 index 000000000..72556dd10 --- /dev/null +++ b/svtools/source/config/colorcfg.cxx @@ -0,0 +1,607 @@ +/* -*- 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 <string_view> + +#include <svtools/colorcfg.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <comphelper/processfactory.hxx> +#include <unotools/configitem.hxx> +#include <unotools/confignode.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/configpaths.hxx> +#include <com/sun/star/uno/Sequence.h> +#include <svl/poolitem.hxx> +#include <mutex> + +#include "itemholder2.hxx" + +#include <vcl/svapp.hxx> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> + + +using namespace utl; +using namespace com::sun::star; + +const char g_sIsVisible[] = "/IsVisible"; + + +namespace svtools +{ + +static sal_Int32 nColorRefCount_Impl = 0; +namespace +{ + std::mutex& ColorMutex_Impl() + { + static std::mutex SINGLETON; + return SINGLETON; + } +} + +ColorConfig_Impl* ColorConfig::m_pImpl = nullptr; + +class ColorConfig_Impl : public utl::ConfigItem +{ + ColorConfigValue m_aConfigValues[ColorConfigEntryCount]; + OUString m_sLoadedScheme; + bool m_bAutoDetectSystemHC; + + virtual void ImplCommit() override; + +public: + explicit ColorConfig_Impl(); + virtual ~ColorConfig_Impl() override; + + void Load(const OUString& rScheme); + void CommitCurrentSchemeName(); + //changes the name of the current scheme but doesn't load it! + void SetCurrentSchemeName(const OUString& rSchemeName) {m_sLoadedScheme = rSchemeName;} + virtual void Notify( const uno::Sequence<OUString>& aPropertyNames) override; + + const ColorConfigValue& GetColorConfigValue(ColorConfigEntry eValue) const + {return m_aConfigValues[eValue];} + void SetColorConfigValue(ColorConfigEntry eValue, + const ColorConfigValue& rValue ); + + const OUString& GetLoadedScheme() const {return m_sLoadedScheme;} + + uno::Sequence< OUString> GetSchemeNames(); + + void AddScheme(const OUString& rNode); + void RemoveScheme(const OUString& rNode); + using ConfigItem::SetModified; + using ConfigItem::ClearModified; + void SettingsChanged(); + bool GetAutoDetectSystemHC() const {return m_bAutoDetectSystemHC;} + + DECL_LINK( DataChangedEventListener, VclSimpleEvent&, void ); + + void ImplUpdateApplicationSettings(); +}; + +namespace { + +uno::Sequence< OUString> GetPropertyNames(std::u16string_view rScheme) +{ + struct ColorConfigEntryData_Impl + { + std::u16string_view cName; + bool bCanBeVisible; + }; + static const ColorConfigEntryData_Impl cNames[] = + { + { std::u16string_view(u"/DocColor") ,false }, + { std::u16string_view(u"/DocBoundaries") ,true }, + { std::u16string_view(u"/AppBackground") ,false }, + { std::u16string_view(u"/ObjectBoundaries"),true }, + { std::u16string_view(u"/TableBoundaries") ,true }, + { std::u16string_view(u"/FontColor") ,false }, + { std::u16string_view(u"/Links") ,true }, + { std::u16string_view(u"/LinksVisited") ,true }, + { std::u16string_view(u"/Spell") ,false }, + { std::u16string_view(u"/SmartTags") ,false }, + { std::u16string_view(u"/Shadow") , true }, + { std::u16string_view(u"/WriterTextGrid") ,false }, + { std::u16string_view(u"/WriterFieldShadings"),true }, + { std::u16string_view(u"/WriterIdxShadings") ,true }, + { std::u16string_view(u"/WriterDirectCursor") ,true }, + { std::u16string_view(u"/WriterScriptIndicator") ,false }, + { std::u16string_view(u"/WriterSectionBoundaries") ,true }, + { std::u16string_view(u"/WriterHeaderFooterMark") ,false }, + { std::u16string_view(u"/WriterPageBreaks") ,false }, + { std::u16string_view(u"/HTMLSGML") ,false }, + { std::u16string_view(u"/HTMLComment") ,false }, + { std::u16string_view(u"/HTMLKeyword") ,false }, + { std::u16string_view(u"/HTMLUnknown") ,false }, + { std::u16string_view(u"/CalcGrid") ,false }, + { std::u16string_view(u"/CalcPageBreak"), false }, + { std::u16string_view(u"/CalcPageBreakManual"), false }, + { std::u16string_view(u"/CalcPageBreakAutomatic"), false }, + { std::u16string_view(u"/CalcHiddenColRow"), true }, + { std::u16string_view(u"/CalcDetective") ,false }, + { std::u16string_view(u"/CalcDetectiveError") ,false }, + { std::u16string_view(u"/CalcReference") ,false }, + { std::u16string_view(u"/CalcNotesBackground") ,false }, + { std::u16string_view(u"/CalcValue") ,false }, + { std::u16string_view(u"/CalcFormula") ,false }, + { std::u16string_view(u"/CalcText") ,false }, + { std::u16string_view(u"/CalcProtectedBackground") ,false }, + { std::u16string_view(u"/DrawGrid") ,true }, + { std::u16string_view(u"/BASICIdentifier"), false }, + { std::u16string_view(u"/BASICComment") , false }, + { std::u16string_view(u"/BASICNumber") , false }, + { std::u16string_view(u"/BASICString") , false }, + { std::u16string_view(u"/BASICOperator") , false }, + { std::u16string_view(u"/BASICKeyword") , false }, + { std::u16string_view(u"/BASICError"), false }, + { std::u16string_view(u"/SQLIdentifier"), false }, + { std::u16string_view(u"/SQLNumber"), false }, + { std::u16string_view(u"/SQLString"), false }, + { std::u16string_view(u"/SQLOperator"), false }, + { std::u16string_view(u"/SQLKeyword"), false }, + { std::u16string_view(u"/SQLParameter"), false }, + { std::u16string_view(u"/SQLComment"), false } + }; + + uno::Sequence<OUString> aNames(2 * ColorConfigEntryCount); + OUString* pNames = aNames.getArray(); + int nIndex = 0; + OUString sBase = "ColorSchemes/" + + utl::wrapConfigurationElementName(rScheme); + for(sal_Int32 i = 0; i < ColorConfigEntryCount; ++i) + { + OUString sBaseName = sBase + cNames[i].cName; + pNames[nIndex++] = sBaseName + "/Color"; + if(cNames[i].bCanBeVisible) + { + pNames[nIndex++] = sBaseName + g_sIsVisible; + } + } + aNames.realloc(nIndex); + return aNames; +} + +} + +ColorConfig_Impl::ColorConfig_Impl() : + ConfigItem("Office.UI/ColorScheme"), + m_bAutoDetectSystemHC(true) +{ + //try to register on the root node - if possible + uno::Sequence < OUString > aNames(1); + EnableNotification( aNames ); + + if (!utl::ConfigManager::IsFuzzing()) + Load(OUString()); + + ImplUpdateApplicationSettings(); + + ::Application::AddEventListener( LINK(this, ColorConfig_Impl, DataChangedEventListener) ); + +} + +ColorConfig_Impl::~ColorConfig_Impl() +{ + ::Application::RemoveEventListener( LINK(this, ColorConfig_Impl, DataChangedEventListener) ); +} + +void ColorConfig_Impl::Load(const OUString& rScheme) +{ + OUString sScheme(rScheme); + if(sScheme.isEmpty()) + { + //detect current scheme name + uno::Sequence < OUString > aCurrent { "CurrentColorScheme" }; + uno::Sequence< uno::Any > aCurrentVal = GetProperties( aCurrent ); + aCurrentVal.getConstArray()[0] >>= sScheme; + } + m_sLoadedScheme = sScheme; + + uno::Sequence < OUString > aColorNames = GetPropertyNames(sScheme); + uno::Sequence< uno::Any > aColors = GetProperties( aColorNames ); + const uno::Any* pColors = aColors.getConstArray(); + const OUString* pColorNames = aColorNames.getConstArray(); + sal_Int32 nIndex = 0; + for(int i = 0; i < ColorConfigEntryCount && aColors.getLength() > nIndex; ++i) + { + if(pColors[nIndex].hasValue()) + { + Color nTmp; + pColors[nIndex] >>= nTmp; + m_aConfigValues[i].nColor = nTmp; + } + else + m_aConfigValues[i].nColor = COL_AUTO; + nIndex++; + if(nIndex >= aColors.getLength()) + break; + //test for visibility property + if(pColorNames[nIndex].endsWith(g_sIsVisible)) + m_aConfigValues[i].bIsVisible = Any2Bool(pColors[nIndex++]); + } + // fdo#71511: check if we are running in a11y autodetect + { + utl::OConfigurationNode aNode = utl::OConfigurationTreeRoot::tryCreateWithComponentContext(comphelper::getProcessComponentContext(),"org.openoffice.Office.Common/Accessibility" ); + if(aNode.isValid()) + { + uno::Any aValue = aNode.getNodeValue(OUString("AutoDetectSystemHC")); + aValue >>= m_bAutoDetectSystemHC; + } + } +} + +void ColorConfig_Impl::Notify( const uno::Sequence<OUString>& ) +{ + //loading via notification always uses the default setting + Load(OUString()); + NotifyListeners(ConfigurationHints::NONE); +} + +void ColorConfig_Impl::ImplCommit() +{ + uno::Sequence < OUString > aColorNames = GetPropertyNames(m_sLoadedScheme); + uno::Sequence < beans::PropertyValue > aPropValues(aColorNames.getLength()); + beans::PropertyValue* pPropValues = aPropValues.getArray(); + const OUString* pColorNames = aColorNames.getConstArray(); + sal_Int32 nIndex = 0; + for(int i = 0; i < ColorConfigEntryCount && aColorNames.getLength() > nIndex; ++i) + { + pPropValues[nIndex].Name = pColorNames[nIndex]; + //save automatic colors as void value + if(m_aConfigValues[i].nColor != COL_AUTO) + pPropValues[nIndex].Value <<= m_aConfigValues[i].nColor; + + nIndex++; + if(nIndex >= aColorNames.getLength()) + break; + //test for visibility property + if(pColorNames[nIndex].endsWith(g_sIsVisible)) + { + pPropValues[nIndex].Name = pColorNames[nIndex]; + pPropValues[nIndex].Value <<= m_aConfigValues[i].bIsVisible; + nIndex++; + } + } + SetSetProperties("ColorSchemes", aPropValues); + + CommitCurrentSchemeName(); +} + +void ColorConfig_Impl::CommitCurrentSchemeName() +{ + //save current scheme name + uno::Sequence < OUString > aCurrent { "CurrentColorScheme" }; + uno::Sequence< uno::Any > aCurrentVal(1); + aCurrentVal.getArray()[0] <<= m_sLoadedScheme; + PutProperties(aCurrent, aCurrentVal); +} + +void ColorConfig_Impl::SetColorConfigValue(ColorConfigEntry eValue, const ColorConfigValue& rValue ) +{ + if(rValue != m_aConfigValues[eValue]) + { + m_aConfigValues[eValue] = rValue; + SetModified(); + } +} + +uno::Sequence< OUString> ColorConfig_Impl::GetSchemeNames() +{ + return GetNodeNames("ColorSchemes"); +} + +void ColorConfig_Impl::AddScheme(const OUString& rScheme) +{ + if(ConfigItem::AddNode("ColorSchemes", rScheme)) + { + m_sLoadedScheme = rScheme; + Commit(); + } +} + +void ColorConfig_Impl::RemoveScheme(const OUString& rScheme) +{ + uno::Sequence< OUString > aElements { rScheme }; + ClearNodeElements("ColorSchemes", aElements); +} + +void ColorConfig_Impl::SettingsChanged() +{ + SolarMutexGuard aVclGuard; + + ImplUpdateApplicationSettings(); + + NotifyListeners(ConfigurationHints::NONE); +} + +IMPL_LINK( ColorConfig_Impl, DataChangedEventListener, VclSimpleEvent&, rEvent, void ) +{ + if ( rEvent.GetId() == VclEventId::ApplicationDataChanged ) + { + DataChangedEvent* pData = static_cast<DataChangedEvent*>(static_cast<VclWindowEvent&>(rEvent).GetData()); + if ( (pData->GetType() == DataChangedEventType::SETTINGS) && + (pData->GetFlags() & AllSettingsFlags::STYLE) ) + { + SettingsChanged(); + } + } +} + + +/** updates the font color in the vcl window settings */ +void ColorConfig_Impl::ImplUpdateApplicationSettings() +{ + Application* pApp = GetpApp(); + if( !pApp ) + return; + + AllSettings aSettings = Application::GetSettings(); + StyleSettings aStyleSettings( aSettings.GetStyleSettings() ); + + ColorConfigValue aRet = GetColorConfigValue(svtools::FONTCOLOR); + if(COL_AUTO == aRet.nColor) + aRet.nColor = ColorConfig::GetDefaultColor(svtools::FONTCOLOR); + + Color aFontColor(aRet.nColor); + + if( aStyleSettings.GetFontColor() != aFontColor ) + { + aStyleSettings.SetFontColor( aFontColor ); + + aSettings.SetStyleSettings( aStyleSettings ); + Application::SetSettings( aSettings ); + } +} + +ColorConfig::ColorConfig() +{ + if (utl::ConfigManager::IsFuzzing()) + return; + std::unique_lock aGuard( ColorMutex_Impl() ); + if ( !m_pImpl ) + { + m_pImpl = new ColorConfig_Impl; + aGuard.unlock(); // because holdConfigItem will call this constructor + svtools::ItemHolder2::holdConfigItem(EItem::ColorConfig); + } + ++nColorRefCount_Impl; + m_pImpl->AddListener(this); +} + +ColorConfig::~ColorConfig() +{ + if (utl::ConfigManager::IsFuzzing()) + return; + std::unique_lock aGuard( ColorMutex_Impl() ); + m_pImpl->RemoveListener(this); + if(!--nColorRefCount_Impl) + { + delete m_pImpl; + m_pImpl = nullptr; + } +} + +Color ColorConfig::GetDefaultColor(ColorConfigEntry eEntry) +{ + static const Color aAutoColors[] = + { + COL_WHITE, // DOCCOLOR + COL_LIGHTGRAY, // DOCBOUNDARIES + Color(0xDFDFDE), // APPBACKGROUND + COL_LIGHTGRAY, // OBJECTBOUNDARIES + COL_LIGHTGRAY, // TABLEBOUNDARIES + COL_BLACK, // FONTCOLOR + COL_BLUE, // LINKS + Color(0x0000cc), // LINKSVISITED + COL_LIGHTRED, // SPELL + COL_LIGHTMAGENTA, // SMARTTAGS + COL_GRAY, // SHADOWCOLOR + COL_LIGHTGRAY, // WRITERTEXTGRID + COL_LIGHTGRAY, // WRITERFIELDSHADIN + COL_LIGHTGRAY, // WRITERIDXSHADINGS + COL_BLACK, // WRITERDIRECTCURSOR + COL_GREEN, //WRITERSCRIPTINDICATOR + COL_LIGHTGRAY, //WRITERSECTIONBOUNDARIES + Color(0x0369a3), //WRITERHEADERFOOTERMARK, + COL_BLUE, //WRITERPAGEBREAKS, + COL_LIGHTBLUE, // HTMLSGML + COL_LIGHTGREEN, // HTMLCOMMENT + COL_LIGHTRED, // HTMLKEYWORD + COL_GRAY, // HTMLUNKNOWN + COL_GRAY3, // CALCGRID + COL_BLUE, //CALCPAGEBREAK + Color(0x2300dc), //CALCPAGEBREAKMANUAL + COL_GRAY7, //CALCPAGEBREAKAUTOMATIC + Color(0x2300dc), //CALCHIDDENCOLROW + COL_LIGHTBLUE, // CALCDETECTIVE + COL_LIGHTRED, // CALCDETECTIVEERROR + Color(0xef0fff), // CALCREFERENCE + Color(0xffffc0), // CALCNOTESBACKGROUND + COL_LIGHTBLUE, // CALCVALUE + COL_GREEN, // CALCFORMULA + COL_BLACK, // CALCTEXT + COL_LIGHTGRAY, // CALCPROTECTEDBACKGROUND + COL_GRAY7, // DRAWGRID + COL_GREEN, // BASICIDENTIFIER, + COL_GRAY, // BASICCOMMENT, + COL_LIGHTRED, // BASICNUMBER, + COL_LIGHTRED, // BASICSTRING, + COL_BLUE, // BASICOPERATOR, + COL_BLUE, // BASICKEYWORD, + COL_RED, //BASICERROR + Color(0x009900), // SQLIDENTIFIER + COL_BLACK, // SQLNUMBER + Color(0xCE7B00), // SQLSTRING + COL_BLACK, // SQLOPERATOR + Color(0x0000E6), // SQLKEYWORD + Color(0x259D9D), // SQLPARAMETER + COL_GRAY, // SQLCOMMENT + }; + Color aRet; + switch(eEntry) + { + case APPBACKGROUND : + aRet = Application::GetSettings().GetStyleSettings().GetWorkspaceColor(); + break; + + case LINKS : + aRet = Application::GetSettings().GetStyleSettings().GetLinkColor(); + break; + + case LINKSVISITED : + aRet = Application::GetSettings().GetStyleSettings().GetVisitedLinkColor(); + break; + + default: + aRet = aAutoColors[eEntry]; + } + // fdo#71511: if in autodetected a11y HC mode, do pull background color from theme + if(m_pImpl && m_pImpl->GetAutoDetectSystemHC()) + { + switch(eEntry) + { + case DOCCOLOR : + aRet = Application::GetSettings().GetStyleSettings().GetWindowColor(); + break; + case FONTCOLOR : + aRet = Application::GetSettings().GetStyleSettings().GetWindowTextColor(); + break; + default: + break; + } + } + return aRet; +} + +ColorConfigValue ColorConfig::GetColorValue(ColorConfigEntry eEntry, bool bSmart) const +{ + ColorConfigValue aRet; + + if (m_pImpl) + aRet = m_pImpl->GetColorConfigValue(eEntry); + + if (bSmart && aRet.nColor == COL_AUTO) + aRet.nColor = ColorConfig::GetDefaultColor(eEntry); + + return aRet; +} + +EditableColorConfig::EditableColorConfig() : + m_pImpl(new ColorConfig_Impl), + m_bModified(false) +{ + m_pImpl->BlockBroadcasts(true); +} + +EditableColorConfig::~EditableColorConfig() +{ + m_pImpl->BlockBroadcasts(false); + if(m_bModified) + m_pImpl->SetModified(); + if(m_pImpl->IsModified()) + m_pImpl->Commit(); +} + +uno::Sequence< OUString > EditableColorConfig::GetSchemeNames() const +{ + return m_pImpl->GetSchemeNames(); +} + +void EditableColorConfig::DeleteScheme(const OUString& rScheme ) +{ + m_pImpl->RemoveScheme(rScheme); +} + +void EditableColorConfig::AddScheme(const OUString& rScheme ) +{ + m_pImpl->AddScheme(rScheme); +} + +void EditableColorConfig::LoadScheme(const OUString& rScheme ) +{ + if(m_bModified) + m_pImpl->SetModified(); + if(m_pImpl->IsModified()) + m_pImpl->Commit(); + m_bModified = false; + m_pImpl->Load(rScheme); + //the name of the loaded scheme has to be committed separately + m_pImpl->CommitCurrentSchemeName(); +} + +const OUString& EditableColorConfig::GetCurrentSchemeName()const +{ + return m_pImpl->GetLoadedScheme(); +} + +// Changes the name of the current scheme but doesn't load it! +void EditableColorConfig::SetCurrentSchemeName(const OUString& rScheme) +{ + m_pImpl->SetCurrentSchemeName(rScheme); + m_pImpl->CommitCurrentSchemeName(); +} + +const ColorConfigValue& EditableColorConfig::GetColorValue( + ColorConfigEntry eEntry)const +{ + return m_pImpl->GetColorConfigValue(eEntry); +} + +void EditableColorConfig::SetColorValue( + ColorConfigEntry eEntry, const ColorConfigValue& rValue) +{ + m_pImpl->SetColorConfigValue(eEntry, rValue); + m_pImpl->ClearModified(); + m_bModified = true; +} + +void EditableColorConfig::SetModified() +{ + m_bModified = true; +} + +void EditableColorConfig::Commit() +{ + if(m_bModified) + m_pImpl->SetModified(); + if(m_pImpl->IsModified()) + m_pImpl->Commit(); + m_bModified = false; +} + +void EditableColorConfig::DisableBroadcast() +{ + m_pImpl->BlockBroadcasts(true); +} + +void EditableColorConfig::EnableBroadcast() +{ + m_pImpl->BlockBroadcasts(false); +} + + +}//namespace svtools + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/config/extcolorcfg.cxx b/svtools/source/config/extcolorcfg.cxx new file mode 100644 index 000000000..5a5e2c6c0 --- /dev/null +++ b/svtools/source/config/extcolorcfg.cxx @@ -0,0 +1,665 @@ +/* -*- 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 <map> +#include <string_view> + +#include <svtools/extcolorcfg.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <tools/color.hxx> +#include <unotools/configitem.hxx> +#include <com/sun/star/uno/Sequence.h> +#include <comphelper/sequence.hxx> +#include <svl/hint.hxx> +#include <mutex> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <o3tl/string_view.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/event.hxx> + + +using namespace utl; +using namespace com::sun::star; + + +namespace svtools +{ + +static sal_Int32 nExtendedColorRefCount_Impl = 0; +namespace +{ + std::mutex& ColorMutex_Impl() + { + static std::mutex SINGLETON; + return SINGLETON; + } +} + +ExtendedColorConfig_Impl* ExtendedColorConfig::m_pImpl = nullptr; + +class ExtendedColorConfig_Impl : public utl::ConfigItem, public SfxBroadcaster +{ + typedef std::map<OUString, OUString> TDisplayNames; + typedef std::map<OUString, ExtendedColorConfigValue> TConfigValues; + typedef ::std::vector<TConfigValues::iterator> TMapPos; + typedef ::std::pair< TConfigValues, TMapPos > TComponentMapping; + typedef std::map<OUString, TComponentMapping> TComponents; + TComponents m_aConfigValues; + TDisplayNames m_aComponentDisplayNames; + ::std::vector<TComponents::iterator> m_aConfigValuesPos; + + OUString m_sLoadedScheme; + bool m_bIsBroadcastEnabled; + static bool m_bLockBroadcast; + static bool m_bBroadcastWhenUnlocked; + + uno::Sequence< OUString> GetPropertyNames(const OUString& rScheme); + void FillComponentColors(const uno::Sequence < OUString >& _rComponents,const TDisplayNames& _rDisplayNames); + + virtual void ImplCommit() override; + +public: + explicit ExtendedColorConfig_Impl(); + virtual ~ExtendedColorConfig_Impl() override; + + void Load(const OUString& rScheme); + void CommitCurrentSchemeName(); + //changes the name of the current scheme but doesn't load it! + void SetCurrentSchemeName(const OUString& rSchemeName) {m_sLoadedScheme = rSchemeName;} + bool ExistsScheme(std::u16string_view _sSchemeName); + virtual void Notify( const uno::Sequence<OUString>& aPropertyNames) override; + + sal_Int32 GetComponentCount() const; + OUString GetComponentName(sal_uInt32 _nPos) const; + OUString GetComponentDisplayName(const OUString& _sComponentName) const; + sal_Int32 GetComponentColorCount(const OUString& _sName) const; + ExtendedColorConfigValue GetComponentColorConfigValue(const OUString& _sName,sal_uInt32 _nPos) const; + + ExtendedColorConfigValue GetColorConfigValue(const OUString& _sComponentName,const OUString& _sName) + { + TComponents::iterator aFind = m_aConfigValues.find(_sComponentName); + if ( aFind != m_aConfigValues.end() ) + { + TConfigValues::iterator aFind2 = aFind->second.first.find(_sName); + if ( aFind2 != aFind->second.first.end() ) + return aFind2->second; + } +#if OSL_DEBUG_LEVEL > 0 + SAL_WARN( "svtools", "Could find the required config:\n" + "component: " << _sComponentName + << "\nname: " << _sName ); +#endif + return ExtendedColorConfigValue(); + } + void SetColorConfigValue(const OUString& _sName, + const ExtendedColorConfigValue& rValue ); + + void AddScheme(const OUString& rNode); + void RemoveScheme(const OUString& rNode); + using ConfigItem::SetModified; + using ConfigItem::ClearModified; + void SettingsChanged(); + + static void DisableBroadcast(); + static void EnableBroadcast(); + + static void LockBroadcast(); + static void UnlockBroadcast(); + + DECL_LINK( DataChangedEventListener, VclSimpleEvent&, void ); +}; + +uno::Sequence< OUString> ExtendedColorConfig_Impl::GetPropertyNames(const OUString& rScheme) +{ + uno::Sequence< OUString> aNames(GetNodeNames(rScheme)); + for(OUString & i : asNonConstRange(aNames)) + { + i = rScheme + "/" + i; + } + return aNames; +} + +sal_Int32 ExtendedColorConfig_Impl::GetComponentCount() const +{ + return m_aConfigValues.size(); +} + +sal_Int32 ExtendedColorConfig_Impl::GetComponentColorCount(const OUString& _sName) const +{ + sal_Int32 nSize = 0; + TComponents::const_iterator aFind = m_aConfigValues.find(_sName); + if ( aFind != m_aConfigValues.end() ) + { + nSize = aFind->second.first.size(); + } + return nSize; +} + +ExtendedColorConfigValue ExtendedColorConfig_Impl::GetComponentColorConfigValue(const OUString& _sName,sal_uInt32 _nPos) const +{ + TComponents::const_iterator aFind = m_aConfigValues.find(_sName); + if ( aFind != m_aConfigValues.end() ) + { + if ( _nPos < aFind->second.second.size() ) + { + return aFind->second.second[_nPos]->second; + } + } + return ExtendedColorConfigValue(); +} + +OUString ExtendedColorConfig_Impl::GetComponentDisplayName(const OUString& _sComponentName) const +{ + OUString sRet; + TDisplayNames::const_iterator aFind = m_aComponentDisplayNames.find(_sComponentName); + if ( aFind != m_aComponentDisplayNames.end() ) + sRet = aFind->second; + return sRet; +} + +OUString ExtendedColorConfig_Impl::GetComponentName(sal_uInt32 _nPos) const +{ + OUString sRet; + if ( _nPos < m_aConfigValuesPos.size() ) + sRet = m_aConfigValuesPos[_nPos]->first; + return sRet; +} + +bool ExtendedColorConfig_Impl::m_bLockBroadcast = false; +bool ExtendedColorConfig_Impl::m_bBroadcastWhenUnlocked = false; +ExtendedColorConfig_Impl::ExtendedColorConfig_Impl() : + ConfigItem("Office.ExtendedColorScheme"), + m_bIsBroadcastEnabled(true) +{ + //try to register on the root node - if possible + uno::Sequence < OUString > aNames(1); + EnableNotification( aNames ); + Load(OUString()); + + ::Application::AddEventListener( LINK(this, ExtendedColorConfig_Impl, DataChangedEventListener) ); + +} + +ExtendedColorConfig_Impl::~ExtendedColorConfig_Impl() +{ + ::Application::RemoveEventListener( LINK(this, ExtendedColorConfig_Impl, DataChangedEventListener) ); +} + +void ExtendedColorConfig_Impl::DisableBroadcast() +{ + if ( ExtendedColorConfig::m_pImpl ) + ExtendedColorConfig::m_pImpl->m_bIsBroadcastEnabled = false; +} + +void ExtendedColorConfig_Impl::EnableBroadcast() +{ + if ( ExtendedColorConfig::m_pImpl ) + ExtendedColorConfig::m_pImpl->m_bIsBroadcastEnabled = true; +} + +static void lcl_addString(uno::Sequence < OUString >& _rSeq,std::u16string_view _sAdd) +{ + for(OUString & i : asNonConstRange(_rSeq)) + i += _sAdd; +} + +void ExtendedColorConfig_Impl::Load(const OUString& rScheme) +{ + m_aComponentDisplayNames.clear(); + m_aConfigValuesPos.clear(); + m_aConfigValues.clear(); + + // fill display names + TDisplayNames aDisplayNameMap; + uno::Sequence < OUString > aComponentNames = GetPropertyNames("EntryNames"); + OUString sDisplayName("/DisplayName"); + for(OUString & componentName : asNonConstRange(aComponentNames)) + { + uno::Sequence< uno::Any > aComponentDisplayNamesValue = GetProperties( { componentName + sDisplayName } ); + OUString sComponentDisplayName; + if ( aComponentDisplayNamesValue.hasElements() && (aComponentDisplayNamesValue[0] >>= sComponentDisplayName) ) + { + m_aComponentDisplayNames.emplace(componentName.getToken(1, '/'),sComponentDisplayName); + } + + componentName += "/Entries"; + uno::Sequence < OUString > aDisplayNames = GetPropertyNames(componentName); + lcl_addString(aDisplayNames,sDisplayName); + + uno::Sequence< uno::Any > aDisplayNamesValue = GetProperties( aDisplayNames ); + + const OUString* pDispIter = aDisplayNames.getConstArray(); + const OUString* pDispEnd = pDispIter + aDisplayNames.getLength(); + for(sal_Int32 j = 0;pDispIter != pDispEnd;++pDispIter,++j) + { + sal_Int32 nIndex = 0; + o3tl::getToken(*pDispIter, 0, '/', nIndex); + std::u16string_view sName = pDispIter->subView(nIndex); + sName = sName.substr(0, sName.rfind(sDisplayName)); + OUString sCurrentDisplayName; + aDisplayNamesValue[j] >>= sCurrentDisplayName; + aDisplayNameMap.emplace(OUString(sName),sCurrentDisplayName); + } + } + + // load color settings + OUString sScheme(rScheme); + + if(sScheme.isEmpty()) + { + //detect current scheme name + uno::Sequence < OUString > aCurrent { "ExtendedColorScheme/CurrentColorScheme" }; + uno::Sequence< uno::Any > aCurrentVal = GetProperties( aCurrent ); + aCurrentVal.getConstArray()[0] >>= sScheme; + } // if(!sScheme.getLength()) + + m_sLoadedScheme = sScheme; + OUString sBase = "ExtendedColorScheme/ColorSchemes/" + + sScheme; + + bool bFound = ExistsScheme(sScheme); + if ( bFound ) + { + aComponentNames = GetPropertyNames(sBase); + FillComponentColors(aComponentNames,aDisplayNameMap); + } + + if ( m_sLoadedScheme.isEmpty() ) + m_sLoadedScheme = "default"; + + if ( sScheme != "default" ) + { + if ( ExistsScheme(u"default") ) + { + aComponentNames = GetPropertyNames("ExtendedColorScheme/ColorSchemes/default"); + FillComponentColors(aComponentNames,aDisplayNameMap); + } + } + if ( !bFound && !sScheme.isEmpty() ) + { + AddScheme(sScheme); + CommitCurrentSchemeName(); + } +} + +void ExtendedColorConfig_Impl::FillComponentColors(const uno::Sequence < OUString >& _rComponents,const TDisplayNames& _rDisplayNames) +{ + static const OUStringLiteral sColorEntries(u"/Entries"); + for(OUString const & component : _rComponents) + { + OUString sComponentName = component.copy(component.lastIndexOf('/')+1); + if ( m_aConfigValues.find(sComponentName) == m_aConfigValues.end() ) + { + OUString sEntry = component + sColorEntries; + + uno::Sequence < OUString > aColorNames = GetPropertyNames(sEntry); + uno::Sequence < OUString > aDefaultColorNames = aColorNames; + + static const OUStringLiteral sColor(u"/Color"); + lcl_addString(aColorNames,sColor); + lcl_addString(aDefaultColorNames,u"/DefaultColor"); + uno::Sequence< uno::Any > aColors = GetProperties( aColorNames ); + const uno::Any* pColors = aColors.getConstArray(); + + uno::Sequence< uno::Any > aDefaultColors = GetProperties( aDefaultColorNames ); + bool bDefaultColorFound = aDefaultColors.hasElements(); + const uno::Any* pDefaultColors = aDefaultColors.getConstArray(); + + OUString* pColorIter = aColorNames.getArray(); + OUString* pColorEnd = pColorIter + aColorNames.getLength(); + + m_aConfigValuesPos.push_back(m_aConfigValues.emplace(sComponentName,TComponentMapping(TConfigValues(),TMapPos())).first); + TConfigValues& aConfigValues = (*m_aConfigValuesPos.rbegin())->second.first; + TMapPos& aConfigValuesPos = (*m_aConfigValuesPos.rbegin())->second.second; + for(int i = 0; pColorIter != pColorEnd; ++pColorIter ,++i) + { + if ( aConfigValues.find(*pColorIter) == aConfigValues.end() ) + { + sal_Int32 nIndex = 0; + o3tl::getToken(*pColorIter, 2, '/', nIndex); + OUString sName(pColorIter->copy(nIndex)),sDisplayName; + OUString sTemp = sName.copy(0,sName.lastIndexOf(sColor)); + + TDisplayNames::const_iterator aFind = _rDisplayNames.find(sTemp); + sName = sName.getToken(2, '/'); + OSL_ENSURE(aFind != _rDisplayNames.end(),"DisplayName is not in EntryNames config list!"); + if ( aFind != _rDisplayNames.end() ) + sDisplayName = aFind->second; + + OSL_ENSURE(pColors[i].hasValue(),"Color config entry has NIL as color value set!"); + OSL_ENSURE(pDefaultColors[i].hasValue(),"Color config entry has NIL as color value set!"); + Color nColor, nDefaultColor; + pColors[i] >>= nColor; + if ( bDefaultColorFound ) + pDefaultColors[i] >>= nDefaultColor; + else + nDefaultColor = nColor; + ExtendedColorConfigValue aValue(sName,sDisplayName,nColor,nDefaultColor); + aConfigValuesPos.push_back(aConfigValues.emplace(sName,aValue).first); + } + } // for(int i = 0; pColorIter != pColorEnd; ++pColorIter ,++i) + } + } +} + +void ExtendedColorConfig_Impl::Notify( const uno::Sequence<OUString>& /*rPropertyNames*/) +{ + //loading via notification always uses the default setting + Load(OUString()); + + SolarMutexGuard aVclGuard; + + if(m_bLockBroadcast) + { + m_bBroadcastWhenUnlocked = true; + } + else + Broadcast(SfxHint(SfxHintId::ColorsChanged)); +} + +void ExtendedColorConfig_Impl::ImplCommit() +{ + if ( m_sLoadedScheme.isEmpty() ) + return; + static const OUStringLiteral sColorEntries(u"Entries"); + static const OUStringLiteral sColor(u"/Color"); + OUString sBase = "ExtendedColorScheme/ColorSchemes/" + + m_sLoadedScheme; + static const OUStringLiteral s_sSep(u"/"); + + for (auto const& configValue : m_aConfigValues) + { + if ( ConfigItem::AddNode(sBase, configValue.first) ) + { + OUString sNode = sBase + + s_sSep + + configValue.first + //ConfigItem::AddNode(sNode, sColorEntries); + + s_sSep + + sColorEntries; + + uno::Sequence < beans::PropertyValue > aPropValues(configValue.second.first.size()); + beans::PropertyValue* pPropValues = aPropValues.getArray(); + for (auto const& elem : configValue.second.first) + { + pPropValues->Name = sNode + s_sSep + elem.first; + ConfigItem::AddNode(sNode, elem.first); + pPropValues->Name += sColor; + pPropValues->Value <<= elem.second.getColor(); + // the default color will never be changed + ++pPropValues; + } + SetSetProperties("ExtendedColorScheme/ColorSchemes", aPropValues); + } + } + + CommitCurrentSchemeName(); +} + +void ExtendedColorConfig_Impl::CommitCurrentSchemeName() +{ + //save current scheme name + uno::Sequence < OUString > aCurrent { "ExtendedColorScheme/CurrentColorScheme" }; + uno::Sequence< uno::Any > aCurrentVal(1); + aCurrentVal.getArray()[0] <<= m_sLoadedScheme; + PutProperties(aCurrent, aCurrentVal); +} + +bool ExtendedColorConfig_Impl::ExistsScheme(std::u16string_view _sSchemeName) +{ + OUString sBase("ExtendedColorScheme/ColorSchemes"); + + uno::Sequence < OUString > aComponentNames = GetPropertyNames(sBase); + sBase += OUString::Concat("/") + _sSchemeName; + return comphelper::findValue(aComponentNames, sBase) != -1; +} + +void ExtendedColorConfig_Impl::SetColorConfigValue(const OUString& _sName, const ExtendedColorConfigValue& rValue ) +{ + TComponents::iterator aFind = m_aConfigValues.find(_sName); + if ( aFind != m_aConfigValues.end() ) + { + TConfigValues::iterator aFind2 = aFind->second.first.find(rValue.getName()); + if ( aFind2 != aFind->second.first.end() ) + aFind2->second = rValue; + SetModified(); + } +} + +void ExtendedColorConfig_Impl::AddScheme(const OUString& rScheme) +{ + if(ConfigItem::AddNode("ExtendedColorScheme/ColorSchemes", rScheme)) + { + m_sLoadedScheme = rScheme; + Commit(); + } +} + +void ExtendedColorConfig_Impl::RemoveScheme(const OUString& rScheme) +{ + uno::Sequence< OUString > aElements { rScheme }; + ClearNodeElements("ExtendedColorScheme/ColorSchemes", aElements); +} + +void ExtendedColorConfig_Impl::SettingsChanged() +{ + SolarMutexGuard aVclGuard; + + Broadcast( SfxHint( SfxHintId::ColorsChanged ) ); +} + +void ExtendedColorConfig_Impl::LockBroadcast() +{ + m_bLockBroadcast = true; +} + +void ExtendedColorConfig_Impl::UnlockBroadcast() +{ + if ( m_bBroadcastWhenUnlocked ) + { + m_bBroadcastWhenUnlocked = ExtendedColorConfig::m_pImpl != nullptr; + if ( m_bBroadcastWhenUnlocked ) + { + if (ExtendedColorConfig::m_pImpl->m_bIsBroadcastEnabled) + { + m_bBroadcastWhenUnlocked = false; + ExtendedColorConfig::m_pImpl->Broadcast(SfxHint(SfxHintId::ColorsChanged)); + } + } + } + m_bLockBroadcast = false; +} + +IMPL_LINK( ExtendedColorConfig_Impl, DataChangedEventListener, VclSimpleEvent&, rEvent, void ) +{ + if ( rEvent.GetId() == VclEventId::ApplicationDataChanged ) + { + DataChangedEvent* pData = static_cast<DataChangedEvent*>(static_cast<VclWindowEvent&>(rEvent).GetData()); + if ( (pData->GetType() == DataChangedEventType::SETTINGS) && + (pData->GetFlags() & AllSettingsFlags::STYLE) ) + { + SettingsChanged(); + } + } +} + + +ExtendedColorConfig::ExtendedColorConfig() +{ + std::unique_lock aGuard( ColorMutex_Impl() ); + if ( !m_pImpl ) + m_pImpl = new ExtendedColorConfig_Impl; + ++nExtendedColorRefCount_Impl; + StartListening( *m_pImpl); +} + +ExtendedColorConfig::~ExtendedColorConfig() +{ + std::unique_lock aGuard( ColorMutex_Impl() ); + EndListening( *m_pImpl); + if(!--nExtendedColorRefCount_Impl) + { + delete m_pImpl; + m_pImpl = nullptr; + } +} + +ExtendedColorConfigValue ExtendedColorConfig::GetColorValue(const OUString& _sComponentName,const OUString& _sName)const +{ + return m_pImpl->GetColorConfigValue(_sComponentName,_sName); +} + +sal_Int32 ExtendedColorConfig::GetComponentCount() const +{ + return m_pImpl->GetComponentCount(); +} + +sal_Int32 ExtendedColorConfig::GetComponentColorCount(const OUString& _sName) const +{ + return m_pImpl->GetComponentColorCount(_sName); +} + +ExtendedColorConfigValue ExtendedColorConfig::GetComponentColorConfigValue(const OUString& _sName,sal_uInt32 _nPos) const +{ + return m_pImpl->GetComponentColorConfigValue(_sName,_nPos); +} + +OUString ExtendedColorConfig::GetComponentName(sal_uInt32 _nPos) const +{ + return m_pImpl->GetComponentName(_nPos); +} + +OUString ExtendedColorConfig::GetComponentDisplayName(const OUString& _sComponentName) const +{ + return m_pImpl->GetComponentDisplayName(_sComponentName); +} + +void ExtendedColorConfig::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) +{ + SolarMutexGuard aVclGuard; + + Broadcast( rHint ); +} + +EditableExtendedColorConfig::EditableExtendedColorConfig() : + m_pImpl(new ExtendedColorConfig_Impl), + m_bModified(false) +{ + ExtendedColorConfig_Impl::LockBroadcast(); +} + +EditableExtendedColorConfig::~EditableExtendedColorConfig() +{ + ExtendedColorConfig_Impl::UnlockBroadcast(); + if(m_bModified) + m_pImpl->SetModified(); + if(m_pImpl->IsModified()) + m_pImpl->Commit(); +} + +void EditableExtendedColorConfig::DeleteScheme(const OUString& rScheme ) +{ + m_pImpl->RemoveScheme(rScheme); +} + +void EditableExtendedColorConfig::AddScheme(const OUString& rScheme ) +{ + m_pImpl->AddScheme(rScheme); +} + +void EditableExtendedColorConfig::LoadScheme(const OUString& rScheme ) +{ + if(m_bModified) + m_pImpl->SetModified(); + if(m_pImpl->IsModified()) + m_pImpl->Commit(); + m_bModified = false; + m_pImpl->Load(rScheme); + //the name of the loaded scheme has to be committed separately + m_pImpl->CommitCurrentSchemeName(); +} + +// Changes the name of the current scheme but doesn't load it! +void EditableExtendedColorConfig::SetCurrentSchemeName(const OUString& rScheme) +{ + m_pImpl->SetCurrentSchemeName(rScheme); + m_pImpl->CommitCurrentSchemeName(); +} + +void EditableExtendedColorConfig::SetColorValue( + const OUString& _sName, const ExtendedColorConfigValue& rValue) +{ + m_pImpl->SetColorConfigValue(_sName, rValue); + m_pImpl->ClearModified(); + m_bModified = true; +} + +void EditableExtendedColorConfig::SetModified() +{ + m_bModified = true; +} + +void EditableExtendedColorConfig::Commit() +{ + if(m_bModified) + m_pImpl->SetModified(); + if(m_pImpl->IsModified()) + m_pImpl->Commit(); + m_bModified = false; +} + +void EditableExtendedColorConfig::DisableBroadcast() +{ + ExtendedColorConfig_Impl::DisableBroadcast(); +} + +void EditableExtendedColorConfig::EnableBroadcast() +{ + ExtendedColorConfig_Impl::EnableBroadcast(); +} + +sal_Int32 EditableExtendedColorConfig::GetComponentCount() const +{ + return m_pImpl->GetComponentCount(); +} + +sal_Int32 EditableExtendedColorConfig::GetComponentColorCount(const OUString& _sName) const +{ + return m_pImpl->GetComponentColorCount(_sName); +} + +ExtendedColorConfigValue EditableExtendedColorConfig::GetComponentColorConfigValue(const OUString& _sName,sal_uInt32 _nPos) const +{ + return m_pImpl->GetComponentColorConfigValue(_sName,_nPos); +} + +OUString EditableExtendedColorConfig::GetComponentName(sal_uInt32 _nPos) const +{ + return m_pImpl->GetComponentName(_nPos); +} +}//namespace svtools + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/config/fontsubstconfig.cxx b/svtools/source/config/fontsubstconfig.cxx new file mode 100644 index 000000000..6d7323bb0 --- /dev/null +++ b/svtools/source/config/fontsubstconfig.cxx @@ -0,0 +1,155 @@ +/* -*- 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 <svtools/fontsubstconfig.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <o3tl/any.hxx> +#include <tools/debug.hxx> +#include <vcl/outdev.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/configitem.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; + + +constexpr OUStringLiteral cReplacement = u"Replacement"; +constexpr OUStringLiteral cFontPairs = u"FontPairs"; + +constexpr OUStringLiteral cReplaceFont = u"ReplaceFont"; +constexpr OUStringLiteral cSubstituteFont= u"SubstituteFont"; +constexpr OUStringLiteral cOnScreenOnly = u"OnScreenOnly"; +constexpr OUStringLiteral cAlways = u"Always"; + +namespace svtools +{ + +bool IsFontSubstitutionsEnabled() +{ + bool bIsEnabled = false; + Reference<css::container::XHierarchicalNameAccess> xHierarchyAccess = utl::ConfigManager::acquireTree(u"Office.Common/Font/Substitution"); + Any aVal = xHierarchyAccess->getByHierarchicalName(cReplacement); + + DBG_ASSERT(aVal.hasValue(), "no value available"); + if(aVal.hasValue()) + bIsEnabled = *o3tl::doAccess<bool>(aVal); + return bIsEnabled; +} + +std::vector<SubstitutionStruct> GetFontSubstitutions() +{ + Reference<css::container::XHierarchicalNameAccess> xHierarchyAccess = utl::ConfigManager::acquireTree(u"Office.Common/Font/Substitution"); + + const Sequence<OUString> aNodeNames = utl::ConfigItem::GetNodeNames(xHierarchyAccess, cFontPairs, utl::ConfigNameFormat::LocalPath); + Sequence<OUString> aPropNames(aNodeNames.getLength() * 4); + OUString* pNames = aPropNames.getArray(); + sal_Int32 nName = 0; + for(const OUString& rNodeName : aNodeNames) + { + OUString sStart = cFontPairs + "/" + rNodeName + "/"; + pNames[nName++] = sStart + cReplaceFont; + pNames[nName++] = sStart + cSubstituteFont; + pNames[nName++] = sStart + cAlways; + pNames[nName++] = sStart + cOnScreenOnly; + } + Sequence<Any> aNodeValues = utl::ConfigItem::GetProperties(xHierarchyAccess, aPropNames, /*bAllLocales*/false); + const Any* pNodeValues = aNodeValues.getConstArray(); + nName = 0; + std::vector<SubstitutionStruct> aSubstArr; + for(sal_Int32 nNode = 0; nNode < aNodeNames.getLength(); nNode++) + { + SubstitutionStruct aInsert; + pNodeValues[nName++] >>= aInsert.sFont; + pNodeValues[nName++] >>= aInsert.sReplaceBy; + aInsert.bReplaceAlways = *o3tl::doAccess<bool>(pNodeValues[nName++]); + aInsert.bReplaceOnScreenOnly = *o3tl::doAccess<bool>(pNodeValues[nName++]); + aSubstArr.push_back(aInsert); + } + return aSubstArr; +} + +void SetFontSubstitutions(bool bIsEnabled, std::vector<SubstitutionStruct> const & aSubstArr) +{ + Reference<css::container::XHierarchicalNameAccess> xHierarchyAccess = utl::ConfigManager::acquireTree(u"Office.Common/Font/Substitution"); + utl::ConfigItem::PutProperties(xHierarchyAccess, {cReplacement}, {css::uno::Any(bIsEnabled)}, /*bAllLocales*/false); + + OUString sNode(cFontPairs); + if(aSubstArr.empty()) + { + utl::ConfigItem::ClearNodeSet(xHierarchyAccess, sNode); + return; + } + + Sequence<PropertyValue> aSetValues(4 * aSubstArr.size()); + PropertyValue* pSetValues = aSetValues.getArray(); + sal_Int32 nSetValue = 0; + + const OUString sReplaceFont(cReplaceFont); + const OUString sSubstituteFont(cSubstituteFont); + const OUString sAlways(cAlways); + const OUString sOnScreenOnly(cOnScreenOnly); + + for(size_t i = 0; i < aSubstArr.size(); i++) + { + OUString sPrefix = sNode + "/_" + OUString::number(i) + "/"; + + const SubstitutionStruct& rSubst = aSubstArr[i]; + pSetValues[nSetValue].Name = sPrefix; pSetValues[nSetValue].Name += sReplaceFont; + pSetValues[nSetValue++].Value <<= rSubst.sFont; + pSetValues[nSetValue].Name = sPrefix; pSetValues[nSetValue].Name += sSubstituteFont; + pSetValues[nSetValue++].Value <<= rSubst.sReplaceBy; + pSetValues[nSetValue].Name = sPrefix; pSetValues[nSetValue].Name += sAlways; + pSetValues[nSetValue++].Value <<= rSubst.bReplaceAlways; + pSetValues[nSetValue].Name = sPrefix; pSetValues[nSetValue].Name += sOnScreenOnly; + pSetValues[nSetValue++].Value <<= rSubst.bReplaceOnScreenOnly; + } + utl::ConfigItem::ReplaceSetProperties(xHierarchyAccess, sNode, aSetValues, /*bAllLocales*/false); +} + +void ApplyFontSubstitutionsToVcl() +{ + OutputDevice::BeginFontSubstitution(); + + // remove old substitutions + OutputDevice::RemoveFontsSubstitute(); + + const bool bIsEnabled = IsFontSubstitutionsEnabled(); + std::vector<SubstitutionStruct> aSubst = GetFontSubstitutions(); + + // read new substitutions + if (bIsEnabled) + for (const SubstitutionStruct & rSub : aSubst) + { + AddFontSubstituteFlags nFlags = AddFontSubstituteFlags::NONE; + if(rSub.bReplaceAlways) + nFlags |= AddFontSubstituteFlags::ALWAYS; + if(rSub.bReplaceOnScreenOnly) + nFlags |= AddFontSubstituteFlags::ScreenOnly; + OutputDevice::AddFontSubstitute( rSub.sFont, rSub.sReplaceBy, nFlags ); + } + + OutputDevice::EndFontSubstitution(); +} + +} // namespace svtools + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/config/htmlcfg.cxx b/svtools/source/config/htmlcfg.cxx new file mode 100644 index 000000000..983d85e4b --- /dev/null +++ b/svtools/source/config/htmlcfg.cxx @@ -0,0 +1,89 @@ +/* -*- 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 <svtools/htmlcfg.hxx> +#include <unotools/syslocale.hxx> +#include <officecfg/Office/Common.hxx> + +namespace SvxHtmlOptions +{ +sal_uInt16 GetFontSize(sal_uInt16 nPos) +{ + switch (nPos) + { + case 0: + return officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_1::get(); + case 1: + return officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_2::get(); + case 2: + return officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_3::get(); + case 3: + return officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_4::get(); + case 4: + return officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_5::get(); + case 5: + return officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_6::get(); + case 6: + return officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_7::get(); + } + assert(false); + return 0; +} + +sal_uInt16 GetExportMode() +{ + sal_Int32 nExpMode = officecfg::Office::Common::Filter::HTML::Export::Browser::get(); + switch (nExpMode) + { + case 1: + nExpMode = HTML_CFG_MSIE; + break; + case 3: + nExpMode = HTML_CFG_WRITER; + break; + case 4: + nExpMode = HTML_CFG_NS40; + break; + default: + nExpMode = HTML_CFG_NS40; + break; + } + return nExpMode; +} + +bool IsPrintLayoutExtension() +{ + bool bRet = officecfg::Office::Common::Filter::HTML::Export::PrintLayout::get(); + switch (GetExportMode()) + { + case HTML_CFG_MSIE: + case HTML_CFG_NS40: + case HTML_CFG_WRITER: + break; + default: + bRet = false; + } + return bRet; +} + +} // namespace SvxHtmlOptions + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/config/itemholder2.cxx b/svtools/source/config/itemholder2.cxx new file mode 100644 index 000000000..8880655d3 --- /dev/null +++ b/svtools/source/config/itemholder2.cxx @@ -0,0 +1,140 @@ +/* -*- 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 "itemholder2.hxx" + +#include <osl/diagnose.h> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> + +#include <svtools/accessibilityoptions.hxx> +#include <svtools/colorcfg.hxx> +#include <unotools/options.hxx> +#include <svtools/miscopt.hxx> +#include <tools/diagnose_ex.h> +#include <rtl/ref.hxx> + +namespace svtools { + +ItemHolder2::ItemHolder2() +{ + try + { + css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + css::uno::Reference< css::lang::XComponent > xCfg( + css::configuration::theDefaultProvider::get( xContext ), + css::uno::UNO_QUERY_THROW ); + xCfg->addEventListener(static_cast< css::lang::XEventListener* >(this)); + } + catch(const css::uno::RuntimeException&) + { + throw; + } +#ifdef DBG_UTIL + catch(const css::uno::Exception&) + { + static bool bMessage = true; + if(bMessage) + { + bMessage = false; + TOOLS_WARN_EXCEPTION( "svtools", "CreateInstance with arguments" ); + } + } +#else + catch(css::uno::Exception&){} +#endif +} + + +ItemHolder2::~ItemHolder2() +{ + impl_releaseAllItems(); +} + + +void ItemHolder2::holdConfigItem(EItem eItem) +{ + static rtl::Reference<ItemHolder2> pHolder = new ItemHolder2(); + pHolder->impl_addItem(eItem); +} + + +void SAL_CALL ItemHolder2::disposing(const css::lang::EventObject&) +{ + impl_releaseAllItems(); +} + + +void ItemHolder2::impl_addItem(EItem eItem) +{ + std::scoped_lock aLock(m_aLock); + + for ( auto const & rInfo : m_lItems ) + { + if (rInfo.eItem == eItem) + return; + } + + TItemInfo aNewItem; + aNewItem.eItem = eItem; + impl_newItem(aNewItem); + if (aNewItem.pItem) + m_lItems.emplace_back(std::move(aNewItem)); +} + + +void ItemHolder2::impl_releaseAllItems() +{ + std::vector<TItemInfo> items; + { + std::scoped_lock aLock(m_aLock); + items.swap(m_lItems); + } + + // items will be freed when the block exits +} + + +void ItemHolder2::impl_newItem(TItemInfo& rItem) +{ + switch(rItem.eItem) + { + case EItem::AccessibilityOptions : + rItem.pItem.reset( new SvtAccessibilityOptions() ); + break; + + case EItem::ColorConfig : + rItem.pItem.reset( new ::svtools::ColorConfig() ); + break; + + case EItem::MiscOptions : + rItem.pItem.reset( new SvtMiscOptions() ); + break; + + default: + OSL_ASSERT(false); + break; + } +} + +} // namespace svtools + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/config/itemholder2.hxx b/svtools/source/config/itemholder2.hxx new file mode 100644 index 000000000..3bb1a6871 --- /dev/null +++ b/svtools/source/config/itemholder2.hxx @@ -0,0 +1,64 @@ +/* -*- 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 <unotools/itemholderbase.hxx> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/lang/XEventListener.hpp> +#include <mutex> + +namespace svtools { + + +class ItemHolder2 : public ::cppu::WeakImplHelper< css::lang::XEventListener > +{ + + // member + private: + std::mutex m_aLock; + std::vector<TItemInfo> m_lItems; + + + // c++ interface + public: + + ItemHolder2(); + virtual ~ItemHolder2() override; + static void holdConfigItem(EItem eItem); + + + // uno interface + public: + + virtual void SAL_CALL disposing(const css::lang::EventObject& aEvent) override; + + + // helper + private: + + void impl_addItem(EItem eItem); + void impl_releaseAllItems(); + static void impl_newItem(TItemInfo& rItem); +}; + +} // namespace svtools + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/config/languagetoolcfg.cxx b/svtools/source/config/languagetoolcfg.cxx new file mode 100644 index 000000000..9f81c8e78 --- /dev/null +++ b/svtools/source/config/languagetoolcfg.cxx @@ -0,0 +1,164 @@ +/* -*- 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/log.hxx> +#include <sal/config.h> +#include <svtools/languagetoolcfg.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <tools/debug.hxx> + +using namespace utl; +using namespace com::sun::star::uno; + +struct LanguageToolOptions_Impl +{ + OUString sBaseURL; + OUString sUsername; + OUString sApiKey; + bool bEnabled; +}; + +const Sequence<OUString>& SvxLanguageToolOptions::GetPropertyNames() +{ + static Sequence<OUString> const aNames{ + "LanguageTool/BaseURL", + "LanguageTool/Username", + "LanguageTool/ApiKey", + "LanguageTool/IsEnabled", + }; + return aNames; +} + +const OUString& SvxLanguageToolOptions::getBaseURL() const { return pImpl->sBaseURL; } + +void SvxLanguageToolOptions::setBaseURL(const OUString& rVal) +{ + pImpl->sBaseURL = rVal; + SetModified(); +} + +const OUString& SvxLanguageToolOptions::getUsername() const { return pImpl->sUsername; } + +void SvxLanguageToolOptions::setUsername(const OUString& rVal) +{ + pImpl->sUsername = rVal; + SetModified(); +} + +OUString SvxLanguageToolOptions::getLocaleListURL() const { return pImpl->sBaseURL + "/languages"; } + +OUString SvxLanguageToolOptions::getCheckerURL() const { return pImpl->sBaseURL + "/check"; } + +const OUString& SvxLanguageToolOptions::getApiKey() const { return pImpl->sApiKey; } + +void SvxLanguageToolOptions::setApiKey(const OUString& rVal) +{ + pImpl->sApiKey = rVal; + SetModified(); +} + +bool SvxLanguageToolOptions::getEnabled() const { return pImpl->bEnabled; } + +void SvxLanguageToolOptions::setEnabled(bool bEnabled) +{ + pImpl->bEnabled = bEnabled; + SetModified(); +} + +namespace +{ +class theSvxLanguageToolOptions + : public rtl::Static<SvxLanguageToolOptions, theSvxLanguageToolOptions> +{ +}; +} + +SvxLanguageToolOptions& SvxLanguageToolOptions::Get() { return theSvxLanguageToolOptions::get(); } + +SvxLanguageToolOptions::SvxLanguageToolOptions() + : ConfigItem("Office.Linguistic/GrammarChecking") + , pImpl(new LanguageToolOptions_Impl) +{ + Load(GetPropertyNames()); +} + +SvxLanguageToolOptions::~SvxLanguageToolOptions() {} +void SvxLanguageToolOptions::Notify(const css::uno::Sequence<OUString>&) +{ + Load(GetPropertyNames()); +} + +void SvxLanguageToolOptions::Load(const css::uno::Sequence<OUString>& aNames) +{ + Sequence<Any> aValues = GetProperties(aNames); + const Any* pValues = aValues.getConstArray(); + DBG_ASSERT(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if (aValues.getLength() != aNames.getLength()) + return; + for (int nProp = 0; nProp < aNames.getLength(); nProp++) + { + if (!pValues[nProp].hasValue()) + continue; + switch (nProp) + { + case 0: + pValues[nProp] >>= pImpl->sBaseURL; + break; + case 1: + pValues[nProp] >>= pImpl->sUsername; + break; + case 2: + pValues[nProp] >>= pImpl->sApiKey; + break; + case 3: + pValues[nProp] >>= pImpl->bEnabled; + break; + default: + break; + } + } +} + +void SvxLanguageToolOptions::ImplCommit() +{ + const Sequence<OUString>& aNames = GetPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + for (int nProp = 0; nProp < aNames.getLength(); nProp++) + { + switch (nProp) + { + case 0: + pValues[nProp] <<= pImpl->sBaseURL; + break; + case 1: + pValues[nProp] <<= pImpl->sUsername; + break; + case 2: + pValues[nProp] <<= pImpl->sApiKey; + break; + case 3: + pValues[nProp] <<= pImpl->bEnabled; + break; + default: + break; + } + } + PutProperties(aNames, aValues); +}
\ No newline at end of file diff --git a/svtools/source/config/miscopt.cxx b/svtools/source/config/miscopt.cxx new file mode 100644 index 000000000..2462a5804 --- /dev/null +++ b/svtools/source/config/miscopt.cxx @@ -0,0 +1,456 @@ +/* -*- 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 <svtools/miscopt.hxx> +#include <unotools/configitem.hxx> +#include <tools/debug.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <comphelper/sequence.hxx> +#include <tools/link.hxx> +#include <osl/diagnose.h> + +#include "itemholder2.hxx" + +#include <svtools/imgdef.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> + +#include <mutex> +#include <vector> + +using namespace ::utl ; +using namespace ::osl ; +using namespace ::com::sun::star::uno ; +using namespace ::com::sun::star; + +constexpr OUStringLiteral ROOTNODE_MISC = u"Office.Common/Misc"; + +// PROPERTYHANDLE defines must be sequential from zero for Commit/Load +constexpr OUStringLiteral PROPERTYNAME_SYMBOLSET = u"SymbolSet"; +#define PROPERTYHANDLE_SYMBOLSET 0 +constexpr OUStringLiteral PROPERTYNAME_ICONTHEME = u"SymbolStyle"; +#define PROPERTYHANDLE_SYMBOLSTYLE 1 + +static std::mutex & GetInitMutex() +{ + static std::mutex theSvtMiscOptionsMutex; + return theSvtMiscOptionsMutex; +} + + +class SvtMiscOptions_Impl : public ConfigItem +{ +private: + ::std::vector<Link<LinkParamNone*,void>> aList; + sal_Int16 m_nSymbolsSize; + bool m_bIsSymbolsSizeRO; + bool m_bIsSymbolsStyleRO; + bool m_bIconThemeWasSetAutomatically; + + virtual void ImplCommit() override; + +public: + + SvtMiscOptions_Impl(); + virtual ~SvtMiscOptions_Impl() override; + + /*-**************************************************************************************************** + @short called for notify of configmanager + @descr This method is called from the ConfigManager before the application ends or from the + PropertyChangeListener if the sub tree broadcasts changes. You must update your + internal values. + + @seealso baseclass ConfigItem + + @param "seqPropertyNames" is the list of properties which should be updated. + *//*-*****************************************************************************************************/ + + virtual void Notify( const Sequence< OUString >& seqPropertyNames ) override; + + /** loads required data from the configuration. It's called in the constructor to + read all entries and form ::Notify to re-read changed settings + + */ + void Load( const Sequence< OUString >& rPropertyNames ); + + // public interface + + sal_Int16 GetSymbolsSize() const + { return m_nSymbolsSize; } + + void SetSymbolsSize( sal_Int16 nSet ); + + static OUString GetIconTheme(); + + enum class SetModifiedFlag { SET, DONT_SET }; + + /** Set the icon theme + * + * @param theme + * The name of the icon theme to use. + * + * @param setModified + * Whether to call SetModified() and CallListeners(). + * + * @internal + * The @p setModified flag was introduced because the unittests fail if we call SetModified() + * during initialization in the constructor. + */ + void + SetIconTheme(const OUString &theme, SetModifiedFlag setModified ); + + bool IconThemeWasSetAutomatically() const + {return m_bIconThemeWasSetAutomatically;} + + void AddListenerLink( const Link<LinkParamNone*,void>& rLink ); + void RemoveListenerLink( const Link<LinkParamNone*,void>& rLink ); + void CallListeners(); + + + // private methods + + +private: + + /*-**************************************************************************************************** + @short return list of key names of our configuration management which represent our module tree + @descr These methods return a static const list of key names. We need it to get needed values from our + configuration management. + @return A list of needed configuration keys is returned. + *//*-*****************************************************************************************************/ + + static Sequence< OUString > GetPropertyNames(); +}; + + +// constructor + +SvtMiscOptions_Impl::SvtMiscOptions_Impl() + // Init baseclasses first + : ConfigItem( ROOTNODE_MISC ) + + , m_nSymbolsSize( 0 ) + , m_bIsSymbolsSizeRO( false ) + , m_bIsSymbolsStyleRO( false ) + , m_bIconThemeWasSetAutomatically( false ) +{ + // Use our static list of configuration keys to get his values. + Sequence< OUString > seqNames = GetPropertyNames ( ); + Load( seqNames ); + Sequence< Any > seqValues = GetProperties ( seqNames ); + Sequence< sal_Bool > seqRO = GetReadOnlyStates ( seqNames ); + + // Safe impossible cases. + // We need values from ALL configuration keys. + // Follow assignment use order of values in relation to our list of key names! + DBG_ASSERT( !(seqNames.getLength()!=seqValues.getLength()), "SvtMiscOptions_Impl::SvtMiscOptions_Impl()\nI miss some values of configuration keys!\n" ); + + // Copy values from list in right order to our internal member. + sal_Int32 nPropertyCount = seqValues.getLength(); + for( sal_Int32 nProperty=0; nProperty<nPropertyCount; ++nProperty ) + { + if (!seqValues[nProperty].hasValue()) + continue; + switch( nProperty ) + { + case PROPERTYHANDLE_SYMBOLSET : + { + if( !(seqValues[nProperty] >>= m_nSymbolsSize) ) + { + OSL_FAIL("Wrong type of \"Misc\\SymbolSet\"!" ); + } + m_bIsSymbolsSizeRO = seqRO[nProperty]; + break; + } + + case PROPERTYHANDLE_SYMBOLSTYLE : + { + OUString aIconTheme; + if (seqValues[nProperty] >>= aIconTheme) + SetIconTheme(aIconTheme, SetModifiedFlag::DONT_SET); + else + OSL_FAIL("Wrong type of \"Misc\\SymbolStyle\"!" ); + + m_bIsSymbolsStyleRO = seqRO[nProperty]; + break; + } + + } + } + + // Enable notification mechanism of our baseclass. + // We need it to get information about changes outside these class on our used configuration keys! + EnableNotification( seqNames ); +} + + +// destructor + +SvtMiscOptions_Impl::~SvtMiscOptions_Impl() +{ + assert(!IsModified()); // should have been committed +} + +void SvtMiscOptions_Impl::Load( const Sequence< OUString >& rPropertyNames ) +{ + const uno::Sequence< OUString> aInternalPropertyNames( GetPropertyNames()); + Sequence< Any > seqValues = GetProperties( rPropertyNames ); + + // Safe impossible cases. + // We need values from ALL configuration keys. + // Follow assignment use order of values in relation to our list of key names! + DBG_ASSERT( !(rPropertyNames.getLength()!=seqValues.getLength()), "SvtSecurityOptions_Impl::SvtSecurityOptions_Impl()\nI miss some values of configuration keys!\n" ); + + // Copy values from list in right order to our internal member. + sal_Int32 nPropertyCount = seqValues.getLength(); + for( sal_Int32 nProperty=0; nProperty<nPropertyCount; ++nProperty ) + { + if (!seqValues[nProperty].hasValue()) + continue; + switch( comphelper::findValue(aInternalPropertyNames, rPropertyNames[nProperty]) ) + { + case PROPERTYHANDLE_SYMBOLSET : { + if( !(seqValues[nProperty] >>= m_nSymbolsSize) ) + { + OSL_FAIL("Wrong type of \"Misc\\SymbolSet\"!" ); + } + } + break; + case PROPERTYHANDLE_SYMBOLSTYLE : { + OUString aIconTheme; + if (seqValues[nProperty] >>= aIconTheme) + SetIconTheme(aIconTheme, SetModifiedFlag::DONT_SET); + else + OSL_FAIL("Wrong type of \"Misc\\SymbolStyle\"!" ); + } + break; + } + } +} + +void SvtMiscOptions_Impl::AddListenerLink( const Link<LinkParamNone*,void>& rLink ) +{ + aList.push_back( rLink ); +} + +void SvtMiscOptions_Impl::RemoveListenerLink( const Link<LinkParamNone*,void>& rLink ) +{ + aList.erase(std::remove(aList.begin(), aList.end(), rLink), aList.end()); +} + +void SvtMiscOptions_Impl::CallListeners() +{ + for (auto const& elem : aList) + elem.Call( nullptr ); +} + +void SvtMiscOptions_Impl::SetSymbolsSize( sal_Int16 nSet ) +{ + m_nSymbolsSize = nSet; + SetModified(); + CallListeners(); +} + +OUString SvtMiscOptions_Impl::GetIconTheme() +{ + return Application::GetSettings().GetStyleSettings().DetermineIconTheme(); +} + +void +SvtMiscOptions_Impl::SetIconTheme(const OUString &rName, SetModifiedFlag setModified) +{ + OUString aTheme(rName); + if (aTheme.isEmpty() || aTheme == "auto") + { + aTheme = Application::GetSettings().GetStyleSettings().GetAutomaticallyChosenIconTheme(); + m_bIconThemeWasSetAutomatically = true; + } + else + m_bIconThemeWasSetAutomatically = false; + + AllSettings aAllSettings = Application::GetSettings(); + StyleSettings aStyleSettings = aAllSettings.GetStyleSettings(); + aStyleSettings.SetIconTheme(aTheme); + + aAllSettings.SetStyleSettings(aStyleSettings); + Application::MergeSystemSettings( aAllSettings ); + Application::SetSettings(aAllSettings); + + if (setModified == SetModifiedFlag::SET) { + SetModified(); + } + CallListeners(); +} + + +// public method + +void SvtMiscOptions_Impl::Notify( const Sequence< OUString >& rPropertyNames ) +{ + Load( rPropertyNames ); + CallListeners(); +} + + +// public method + +void SvtMiscOptions_Impl::ImplCommit() +{ + // Get names of supported properties, create a list for values and copy current values to it. + Sequence< OUString > seqNames = GetPropertyNames (); + sal_Int32 nCount = seqNames.getLength(); + Sequence< Any > seqValues ( nCount ); + auto seqValuesRange = asNonConstRange(seqValues); + for( sal_Int32 nProperty=0; nProperty<nCount; ++nProperty ) + { + switch( nProperty ) + { + case PROPERTYHANDLE_SYMBOLSET : + { + if ( !m_bIsSymbolsSizeRO ) + seqValuesRange[nProperty] <<= m_nSymbolsSize; + break; + } + + case PROPERTYHANDLE_SYMBOLSTYLE : + { + if ( !m_bIsSymbolsStyleRO ) { + OUString value; + if (m_bIconThemeWasSetAutomatically) { + value = "auto"; + } + else { + value = GetIconTheme(); + } + seqValuesRange[nProperty] <<= value; + } + break; + } + + } + } + // Set properties in configuration. + PutProperties( seqNames, seqValues ); +} + + +// private method + +Sequence< OUString > SvtMiscOptions_Impl::GetPropertyNames() +{ + return Sequence<OUString> + { + PROPERTYNAME_SYMBOLSET, + PROPERTYNAME_ICONTHEME, + }; +} + +namespace { + +std::weak_ptr<SvtMiscOptions_Impl> g_pMiscOptions; + +} + +SvtMiscOptions::SvtMiscOptions() +{ + // Global access, must be guarded (multithreading!). + std::unique_lock aGuard( GetInitMutex() ); + + m_pImpl = g_pMiscOptions.lock(); + if( !m_pImpl ) + { + m_pImpl = std::make_shared<SvtMiscOptions_Impl>(); + g_pMiscOptions = m_pImpl; + aGuard.unlock(); // because holdConfigItem will call this constructor + svtools::ItemHolder2::holdConfigItem(EItem::MiscOptions); + } +} + +SvtMiscOptions::~SvtMiscOptions() +{ + // Global access, must be guarded (multithreading!) + std::unique_lock aGuard( GetInitMutex() ); + + m_pImpl.reset(); +} + + +sal_Int16 SvtMiscOptions::GetSymbolsSize() const +{ + return m_pImpl->GetSymbolsSize(); +} + +void SvtMiscOptions::SetSymbolsSize( sal_Int16 nSet ) +{ + m_pImpl->SetSymbolsSize( nSet ); +} + +sal_Int16 SvtMiscOptions::GetCurrentSymbolsSize() const +{ + sal_Int16 eOptSymbolsSize = m_pImpl->GetSymbolsSize(); + + if ( eOptSymbolsSize == SFX_SYMBOLS_SIZE_AUTO ) + { + // Use system settings, we have to retrieve the toolbar icon size from the + // Application class + ToolbarIconSize nStyleIconSize = Application::GetSettings().GetStyleSettings().GetToolbarIconSize(); + if (nStyleIconSize == ToolbarIconSize::Size32) + eOptSymbolsSize = SFX_SYMBOLS_SIZE_32; + else if (nStyleIconSize == ToolbarIconSize::Large) + eOptSymbolsSize = SFX_SYMBOLS_SIZE_LARGE; + else + eOptSymbolsSize = SFX_SYMBOLS_SIZE_SMALL; + } + + return eOptSymbolsSize; +} + +bool SvtMiscOptions::AreCurrentSymbolsLarge() const +{ + return ( GetCurrentSymbolsSize() == SFX_SYMBOLS_SIZE_LARGE || GetCurrentSymbolsSize() == SFX_SYMBOLS_SIZE_32); +} + +OUString SvtMiscOptions::GetIconTheme() const +{ + return SvtMiscOptions_Impl::GetIconTheme(); +} + +void SvtMiscOptions::SetIconTheme(const OUString& iconTheme) +{ + m_pImpl->SetIconTheme(iconTheme, SvtMiscOptions_Impl::SetModifiedFlag::SET); +} + +void SvtMiscOptions::AddListenerLink( const Link<LinkParamNone*,void>& rLink ) +{ + m_pImpl->AddListenerLink( rLink ); +} + +void SvtMiscOptions::RemoveListenerLink( const Link<LinkParamNone*,void>& rLink ) +{ + m_pImpl->RemoveListenerLink( rLink ); +} + +bool +SvtMiscOptions::IconThemeWasSetAutomatically() const +{ + return m_pImpl->IconThemeWasSetAutomatically(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/config/optionsdrawinglayer.cxx b/svtools/source/config/optionsdrawinglayer.cxx new file mode 100644 index 000000000..54efe4a66 --- /dev/null +++ b/svtools/source/config/optionsdrawinglayer.cxx @@ -0,0 +1,263 @@ +/* -*- 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 <svtools/optionsdrawinglayer.hxx> +#include <vcl/svapp.hxx> +#include <vcl/outdev.hxx> +#include <vcl/settings.hxx> +#include <officecfg/Office/Common.hxx> +#include <mutex> + +// #i73602# +// #i74769#, #i75172# +// #i4219# + +namespace SvtOptionsDrawinglayer +{ + +bool IsOverlayBuffer() +{ + return officecfg::Office::Common::Drawinglayer::OverlayBuffer::get(); +} + +bool IsPaintBuffer() +{ + return officecfg::Office::Common::Drawinglayer::PaintBuffer::get(); +} + +Color GetStripeColorA() +{ + return Color(ColorTransparency, officecfg::Office::Common::Drawinglayer::StripeColorA::get()); +} + +Color GetStripeColorB() +{ + return Color(ColorTransparency, officecfg::Office::Common::Drawinglayer::StripeColorB::get()); +} + +sal_uInt16 GetStripeLength() +{ + return officecfg::Office::Common::Drawinglayer::StripeLength::get(); +} + +bool IsOverlayBuffer_Calc() +{ + return officecfg::Office::Common::Drawinglayer::OverlayBuffer_Calc::get(); +} + +bool IsOverlayBuffer_Writer() +{ + return officecfg::Office::Common::Drawinglayer::OverlayBuffer_Writer::get(); +} + +bool IsOverlayBuffer_DrawImpress() +{ + return officecfg::Office::Common::Drawinglayer::OverlayBuffer_DrawImpress::get(); +} + +// #i74769#, #i75172# +bool IsPaintBuffer_Calc() +{ + return officecfg::Office::Common::Drawinglayer::PaintBuffer_Calc::get(); +} + +bool IsPaintBuffer_Writer() +{ + return officecfg::Office::Common::Drawinglayer::PaintBuffer_Writer::get(); +} + +bool IsPaintBuffer_DrawImpress() +{ + return officecfg::Office::Common::Drawinglayer::PaintBuffer_DrawImpress::get(); +} + +// #i4219# +sal_uInt32 GetMaximumPaperWidth() +{ + return officecfg::Office::Common::Drawinglayer::MaximumPaperWidth::get(); +} + +sal_uInt32 GetMaximumPaperHeight() +{ + return officecfg::Office::Common::Drawinglayer::MaximumPaperHeight::get(); +} + +sal_uInt32 GetMaximumPaperLeftMargin() +{ + return officecfg::Office::Common::Drawinglayer::MaximumPaperLeftMargin::get(); +} + +sal_uInt32 GetMaximumPaperRightMargin() +{ + return officecfg::Office::Common::Drawinglayer::MaximumPaperRightMargin::get(); +} + +sal_uInt32 GetMaximumPaperTopMargin() +{ + return officecfg::Office::Common::Drawinglayer::MaximumPaperTopMargin::get(); +} + +sal_uInt32 GetMaximumPaperBottomMargin() +{ + return officecfg::Office::Common::Drawinglayer::MaximumPaperBottomMargin::get(); +} + +static std::mutex gaAntiAliasMutex; +static bool gbAntiAliasingInit = false; +static bool gbAntiAliasing = false; +static bool gbAllowAAInit = false; +static bool gbAllowAA = false; + +bool IsAAPossibleOnThisSystem() +{ + std::scoped_lock aGuard(gaAntiAliasMutex); + if (!gbAllowAAInit) + { + gbAllowAAInit = true; + gbAllowAA = Application::GetDefaultDevice()->SupportsOperation( OutDevSupportType::TransparentRect ); + } + return gbAllowAA; +} + + +bool IsAntiAliasing() +{ + bool bAntiAliasing; + { + std::scoped_lock aGuard(gaAntiAliasMutex); + if (!gbAntiAliasingInit) + { + gbAntiAliasingInit = true; + gbAntiAliasing = officecfg::Office::Common::Drawinglayer::AntiAliasing::get(); + } + bAntiAliasing = gbAntiAliasing; + } + return bAntiAliasing && IsAAPossibleOnThisSystem(); +} + +/** + * Some code like to turn this stuff on and off during a drawing operation + * so it can "tunnel" information down through several layers, + * so we don't want to actually do a config write all the time. + */ +void SetAntiAliasing( bool bOn, bool bTemporary ) +{ + std::scoped_lock aGuard(gaAntiAliasMutex); + if (!bTemporary) + { + std::shared_ptr<comphelper::ConfigurationChanges> batch = + comphelper::ConfigurationChanges::create(); + officecfg::Office::Common::Drawinglayer::AntiAliasing::set(bOn, batch); + batch->commit(); + } + gbAntiAliasing = bOn; +} + + +bool IsSnapHorVerLinesToDiscrete() +{ + return IsAntiAliasing() && officecfg::Office::Common::Drawinglayer::SnapHorVerLinesToDiscrete::get(); +} + +bool IsSolidDragCreate() +{ + return officecfg::Office::Common::Drawinglayer::SolidDragCreate::get(); +} + +bool IsRenderDecoratedTextDirect() +{ + return officecfg::Office::Common::Drawinglayer::RenderDecoratedTextDirect::get(); +} + +bool IsRenderSimpleTextDirect() +{ + return officecfg::Office::Common::Drawinglayer::RenderSimpleTextDirect::get(); +} + +sal_uInt32 GetQuadratic3DRenderLimit() +{ + return officecfg::Office::Common::Drawinglayer::Quadratic3DRenderLimit::get(); +} + +sal_uInt32 GetQuadraticFormControlRenderLimit() +{ + return officecfg::Office::Common::Drawinglayer::QuadraticFormControlRenderLimit::get(); +} + +// #i97672# selection settings +bool IsTransparentSelection() +{ + return officecfg::Office::Common::Drawinglayer::TransparentSelection::get(); +} + +sal_uInt16 GetTransparentSelectionPercent() +{ + sal_uInt16 aRetval = officecfg::Office::Common::Drawinglayer::TransparentSelectionPercent::get(); + + // crop to range [10% .. 90%] + if(aRetval < 10) + { + aRetval = 10; + } + + if(aRetval > 90) + { + aRetval = 90; + } + + return aRetval; +} + +sal_uInt16 GetSelectionMaximumLuminancePercent() +{ + sal_uInt16 aRetval = officecfg::Office::Common::Drawinglayer::SelectionMaximumLuminancePercent::get(); + + // crop to range [0% .. 100%] + if(aRetval > 90) + { + aRetval = 90; + } + + return aRetval; +} + +Color getHilightColor() +{ + Color aRetval(Application::GetSettings().GetStyleSettings().GetHighlightColor()); + const basegfx::BColor aSelection(aRetval.getBColor()); + const double fLuminance(aSelection.luminance()); + const double fMaxLum(GetSelectionMaximumLuminancePercent() / 100.0); + + if(fLuminance > fMaxLum) + { + const double fFactor(fMaxLum / fLuminance); + const basegfx::BColor aNewSelection( + aSelection.getRed() * fFactor, + aSelection.getGreen() * fFactor, + aSelection.getBlue() * fFactor); + + aRetval = Color(aNewSelection); + } + + return aRetval; +} + +} // namespace SvtOptionsDrawinglayer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/config/printoptions.cxx b/svtools/source/config/printoptions.cxx new file mode 100644 index 000000000..ca9484356 --- /dev/null +++ b/svtools/source/config/printoptions.cxx @@ -0,0 +1,171 @@ +/* -*- 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 <svtools/printoptions.hxx> +#include <vcl/printer/Options.hxx> +#include <officecfg/Office/Common.hxx> +#include <sal/macros.h> +#include <tools/long.hxx> + +const sal_uInt16 aDPIArray[] = { 72, 96, 150, 200, 300, 600 }; + +#define DPI_COUNT (SAL_N_ELEMENTS(aDPIArray)) + +using namespace ::osl; +using namespace ::com::sun::star::uno; + +namespace svtools +{ + +void GetPrinterOptions( vcl::printer::Options& rOptions, bool bFile ) +{ + if (bFile) + { + rOptions.SetReduceTransparency( officecfg::Office::Common::Print::Option::File::ReduceTransparency::get() ); + rOptions.SetReducedTransparencyMode( static_cast<vcl::printer::TransparencyMode>( + officecfg::Office::Common::Print::Option::File::ReducedTransparencyMode::get() ) ); + rOptions.SetReduceGradients( officecfg::Office::Common::Print::Option::File::ReduceGradients::get() ); + rOptions.SetReducedGradientMode( static_cast<vcl::printer::GradientMode>( + officecfg::Office::Common::Print::Option::File::ReducedGradientMode::get()) ); + rOptions.SetReducedGradientStepCount( officecfg::Office::Common::Print::Option::File::ReducedGradientStepCount::get() ); + rOptions.SetReduceBitmaps( officecfg::Office::Common::Print::Option::File::ReduceBitmaps::get() ); + rOptions.SetReducedBitmapMode( static_cast<vcl::printer::BitmapMode>( + officecfg::Office::Common::Print::Option::File::ReducedBitmapMode::get()) ); + rOptions.SetReducedBitmapResolution( aDPIArray[ std::min( static_cast<sal_uInt16>( + officecfg::Office::Common::Print::Option::File::ReducedBitmapResolution::get()), sal_uInt16( DPI_COUNT - 1 ) ) ] ); + rOptions.SetReducedBitmapIncludesTransparency( + officecfg::Office::Common::Print::Option::File::ReducedBitmapIncludesTransparency::get() ); + rOptions.SetConvertToGreyscales( officecfg::Office::Common::Print::Option::File::ConvertToGreyscales::get() ); + rOptions.SetPDFAsStandardPrintJobFormat( officecfg::Office::Common::Print::Option::File::PDFAsStandardPrintJobFormat::get() ); + } + else + { + rOptions.SetReduceTransparency( officecfg::Office::Common::Print::Option::Printer::ReduceTransparency::get() ); + rOptions.SetReducedTransparencyMode( static_cast<vcl::printer::TransparencyMode>( + officecfg::Office::Common::Print::Option::Printer::ReducedTransparencyMode::get() ) ); + rOptions.SetReduceGradients( officecfg::Office::Common::Print::Option::Printer::ReduceGradients::get() ); + rOptions.SetReducedGradientMode( static_cast<vcl::printer::GradientMode>( + officecfg::Office::Common::Print::Option::Printer::ReducedGradientMode::get()) ); + rOptions.SetReducedGradientStepCount( officecfg::Office::Common::Print::Option::Printer::ReducedGradientStepCount::get() ); + rOptions.SetReduceBitmaps( officecfg::Office::Common::Print::Option::Printer::ReduceBitmaps::get() ); + rOptions.SetReducedBitmapMode( static_cast<vcl::printer::BitmapMode>( + officecfg::Office::Common::Print::Option::Printer::ReducedBitmapMode::get()) ); + rOptions.SetReducedBitmapResolution( aDPIArray[ std::min( static_cast<sal_uInt16>( + officecfg::Office::Common::Print::Option::Printer::ReducedBitmapResolution::get()), sal_uInt16( DPI_COUNT - 1 ) ) ] ); + rOptions.SetReducedBitmapIncludesTransparency( + officecfg::Office::Common::Print::Option::Printer::ReducedBitmapIncludesTransparency::get() ); + rOptions.SetConvertToGreyscales( officecfg::Office::Common::Print::Option::Printer::ConvertToGreyscales::get() ); + rOptions.SetPDFAsStandardPrintJobFormat( officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::get() ); + } +} + +void SetPrinterOptions( const vcl::printer::Options& rOptions, bool bFile ) +{ + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + if (bFile) + { + officecfg::Office::Common::Print::Option::File::ReduceTransparency::set( + rOptions.IsReduceTransparency(), batch ); + officecfg::Office::Common::Print::Option::File::ReducedTransparencyMode::set( + static_cast<sal_Int16>(rOptions.GetReducedTransparencyMode()), batch ); + officecfg::Office::Common::Print::Option::File::ReduceGradients::set( + rOptions.IsReduceGradients(), batch ); + officecfg::Office::Common::Print::Option::File::ReducedGradientMode::set( + static_cast<sal_Int16>(rOptions.GetReducedGradientMode()), batch ); + officecfg::Office::Common::Print::Option::File::ReducedGradientStepCount::set( + rOptions.GetReducedGradientStepCount(), batch ); + officecfg::Office::Common::Print::Option::File::ReduceBitmaps::set( + rOptions.IsReduceBitmaps(), batch ); + officecfg::Office::Common::Print::Option::File::ReducedBitmapMode::set( + static_cast<sal_Int16>(rOptions.GetReducedBitmapMode()), batch ); + officecfg::Office::Common::Print::Option::File::ReducedBitmapIncludesTransparency::set( + rOptions.IsReducedBitmapIncludesTransparency(), batch ); + officecfg::Office::Common::Print::Option::File::ConvertToGreyscales::set( + rOptions.IsConvertToGreyscales(), batch ); + officecfg::Office::Common::Print::Option::File::PDFAsStandardPrintJobFormat::set( + rOptions.IsPDFAsStandardPrintJobFormat(), batch ); + + const sal_uInt16 nDPI = rOptions.GetReducedBitmapResolution(); + + if( nDPI < aDPIArray[ 0 ] ) + officecfg::Office::Common::Print::Option::File::ReducedBitmapResolution::set( 0, batch ); + else + { + for( tools::Long i = DPI_COUNT - 1; i >= 0; i-- ) + { + if( nDPI >= aDPIArray[ i ] ) + { + officecfg::Office::Common::Print::Option::File::ReducedBitmapResolution::set( + static_cast<sal_Int16>(i), batch ); + i = -1; + } + } + } + } + else + { + officecfg::Office::Common::Print::Option::Printer::ReduceTransparency::set( + rOptions.IsReduceTransparency(), batch ); + officecfg::Office::Common::Print::Option::Printer::ReducedTransparencyMode::set( + static_cast<sal_Int16>(rOptions.GetReducedTransparencyMode()), batch ); + officecfg::Office::Common::Print::Option::Printer::ReduceGradients::set( + rOptions.IsReduceGradients(), batch ); + officecfg::Office::Common::Print::Option::Printer::ReducedGradientMode::set( + static_cast<sal_Int16>(rOptions.GetReducedGradientMode()), batch ); + officecfg::Office::Common::Print::Option::Printer::ReducedGradientStepCount::set( + rOptions.GetReducedGradientStepCount(), batch ); + officecfg::Office::Common::Print::Option::Printer::ReduceBitmaps::set( + rOptions.IsReduceBitmaps(), batch ); + officecfg::Office::Common::Print::Option::Printer::ReducedBitmapMode::set( + static_cast<sal_Int16>(rOptions.GetReducedBitmapMode()), batch ); + officecfg::Office::Common::Print::Option::Printer::ReducedBitmapIncludesTransparency::set( + rOptions.IsReducedBitmapIncludesTransparency(), batch ); + officecfg::Office::Common::Print::Option::Printer::ConvertToGreyscales::set( + rOptions.IsConvertToGreyscales(), batch ); + officecfg::Office::Common::Print::Option::Printer::PDFAsStandardPrintJobFormat::set( + rOptions.IsPDFAsStandardPrintJobFormat(), batch ); + + const sal_uInt16 nDPI = rOptions.GetReducedBitmapResolution(); + + if( nDPI < aDPIArray[ 0 ] ) + officecfg::Office::Common::Print::Option::Printer::ReducedBitmapResolution::set( 0, batch ); + else + { + for( tools::Long i = DPI_COUNT - 1; i >= 0; i-- ) + { + if( nDPI >= aDPIArray[ i ] ) + { + officecfg::Office::Common::Print::Option::Printer::ReducedBitmapResolution::set( + static_cast<sal_Int16>(i), batch ); + i = -1; + } + } + } + } + batch->commit(); +} + +} // namespace svtools + + + + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/config/slidesorterbaropt.cxx b/svtools/source/config/slidesorterbaropt.cxx new file mode 100644 index 000000000..784e0894e --- /dev/null +++ b/svtools/source/config/slidesorterbaropt.cxx @@ -0,0 +1,425 @@ +/* -*- 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 <svtools/slidesorterbaropt.hxx> +#include <unotools/configitem.hxx> +#include <tools/debug.hxx> +#include <osl/diagnose.h> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +#include <comphelper/lok.hxx> +#include <comphelper/sequence.hxx> +#include <mutex> + +using namespace ::utl; +using namespace ::osl; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star; + +constexpr OUStringLiteral ROOTNODE_SLIDESORTERBAR = u"Office.Impress/MultiPaneGUI/SlideSorterBar/Visible"; + +constexpr OUStringLiteral PROPERTYNAME_VISIBLE_IMPRESSVIEW = u"ImpressView"; +#define PROPERTYHANDLE_VISIBLE_IMPRESSVIEW 0 +constexpr OUStringLiteral PROPERTYNAME_VISIBLE_OUTLINEVIEW = u"OutlineView"; +#define PROPERTYHANDLE_VISIBLE_OUTLINEVIEW 1 +constexpr OUStringLiteral PROPERTYNAME_VISIBLE_NOTESVIEW = u"NotesView"; +#define PROPERTYHANDLE_VISIBLE_NOTESVIEW 2 +constexpr OUStringLiteral PROPERTYNAME_VISIBLE_HANDOUTVIEW = u"HandoutView"; +#define PROPERTYHANDLE_VISIBLE_HANDOUTVIEW 3 +constexpr OUStringLiteral PROPERTYNAME_VISIBLE_SLIDESORTERVIEW = u"SlideSorterView"; +#define PROPERTYHANDLE_VISIBLE_SLIDESORTERVIEW 4 +constexpr OUStringLiteral PROPERTYNAME_VISIBLE_DRAWVIEW = u"DrawView"; +#define PROPERTYHANDLE_VISIBLE_DRAWVIEW 5 + +static std::mutex & GetInitMutex() +{ + static std::mutex theSvtSlideSorterBarOptionsMutex; + return theSvtSlideSorterBarOptionsMutex; +} + + +class SvtSlideSorterBarOptions_Impl : public ConfigItem +{ + Sequence< OUString > m_seqPropertyNames; + + public: + + SvtSlideSorterBarOptions_Impl(); + ~SvtSlideSorterBarOptions_Impl() override; + + /** called for notify of configmanager + + This method is called from the ConfigManager before the application ends or from the + PropertyChangeListener if the sub tree broadcasts changes. You must update your + internal values. + + \sa baseclass ConfigItem + \param[in,out] seqPropertyNames is the list of properties which should be updated. + */ + virtual void Notify( const Sequence< OUString >& seqPropertyNames ) override; + + /** + loads required data from the configuration. It's called in the constructor to + read all entries and form ::Notify to re-read changed setting + */ + void Load( const Sequence< OUString >& rPropertyNames ); + + // public interface + bool m_bVisibleImpressView; + bool m_bVisibleOutlineView; + bool m_bVisibleNotesView; + bool m_bVisibleHandoutView; + bool m_bVisibleSlideSorterView; + bool m_bVisibleDrawView; + + private: + virtual void ImplCommit() final override; + + /** return list of key names of our configuration management which represent our module tree + + This method returns a static const list of key names. We need it to get needed values from + configuration management. + + \return A list of needed configuration keys is returned. + */ + static Sequence< OUString > GetPropertyNames(); + + void SetVisibleViewImpl( bool& bVisibleView, bool bVisible ); + + public: + void SetVisibleImpressView( bool bVisible) + { SetVisibleViewImpl( m_bVisibleImpressView, bVisible ); } + + void SetVisibleOutlineView( bool bVisible) + { SetVisibleViewImpl( m_bVisibleOutlineView, bVisible ); } + + void SetVisibleNotesView( bool bVisible) + { SetVisibleViewImpl( m_bVisibleNotesView, bVisible ); } + + void SetVisibleHandoutView( bool bVisible) + { SetVisibleViewImpl( m_bVisibleHandoutView, bVisible ); } + + void SetVisibleSlideSorterView( bool bVisible) + { SetVisibleViewImpl( m_bVisibleSlideSorterView, bVisible ); } + + void SetVisibleDrawView( bool bVisible) + { SetVisibleViewImpl( m_bVisibleDrawView, bVisible ); } + +}; + +SvtSlideSorterBarOptions_Impl::SvtSlideSorterBarOptions_Impl() + // Init baseclasses first + : ConfigItem( ROOTNODE_SLIDESORTERBAR ) + , m_seqPropertyNames(GetPropertyNames()) + , m_bVisibleImpressView( false ) + , m_bVisibleOutlineView( false ) + , m_bVisibleNotesView( false ) + , m_bVisibleHandoutView( false ) + , m_bVisibleSlideSorterView( false ) + , m_bVisibleDrawView( false ) + +{ + // Use our static list of configuration keys to get his values. + Sequence< Any > seqValues = GetProperties( m_seqPropertyNames ); + + // Safe impossible cases. + // We need values from ALL configuration keys. + // Follow assignment use order of values in relation to our list of key names! + DBG_ASSERT( !(m_seqPropertyNames.getLength()!=seqValues.getLength()), + "SvtSlideSorterBarOptions_Impl::SvtSlideSorterBarOptions_Impl()\nI miss some values of configuration keys!\n" ); + + // Copy values from list in right order to our internal member. + for( sal_Int32 nProperty=0; nProperty<seqValues.getLength(); ++nProperty ) + { + if (!seqValues[nProperty].hasValue()) + continue; + switch( nProperty ) + { + case PROPERTYHANDLE_VISIBLE_IMPRESSVIEW : + { + if( !(seqValues[nProperty] >>= m_bVisibleImpressView) ) + OSL_FAIL("Wrong type of \"SlideSorterBar\\VisibleImpressView\"!" ); + break; + } + case PROPERTYHANDLE_VISIBLE_OUTLINEVIEW : + { + if( !(seqValues[nProperty] >>= m_bVisibleOutlineView) ) + OSL_FAIL("Wrong type of \"SlideSorterBar\\VisibleOutlineView\"!" ); + break; + } + case PROPERTYHANDLE_VISIBLE_NOTESVIEW : + { + if( !(seqValues[nProperty] >>= m_bVisibleNotesView) ) + OSL_FAIL("Wrong type of \"SlideSorterBar\\VisibleNotesView\"!" ); + break; + } + case PROPERTYHANDLE_VISIBLE_HANDOUTVIEW : + { + if( !(seqValues[nProperty] >>= m_bVisibleHandoutView) ) + OSL_FAIL("Wrong type of \"SlideSorterBar\\VisibleHandoutView\"!" ); + break; + } + case PROPERTYHANDLE_VISIBLE_SLIDESORTERVIEW : + { + if( !(seqValues[nProperty] >>= m_bVisibleSlideSorterView) ) + OSL_FAIL("Wrong type of \"SlideSorterBar\\VisibleSlideSorterView\"!" ); + break; + } + case PROPERTYHANDLE_VISIBLE_DRAWVIEW : + { + if( !(seqValues[nProperty] >>= m_bVisibleDrawView) ) + OSL_FAIL("Wrong type of \"SlideSorterBar\\VisibleDrawView\"!" ); + break; + } + } + } + + // Enable notification mechanism of our baseclass. + // We need it to get information about changes outside these class on our used configuration keys! + EnableNotification( m_seqPropertyNames ); +} + +SvtSlideSorterBarOptions_Impl::~SvtSlideSorterBarOptions_Impl() +{ + if (IsModified()) + Commit(); +} + +void SvtSlideSorterBarOptions_Impl::Load( const Sequence< OUString >& rPropertyNames ) +{ + const uno::Sequence< OUString> aInternalPropertyNames( GetPropertyNames()); + Sequence< Any > seqValues = GetProperties( rPropertyNames ); + + // Safe impossible cases. + // We need values from ALL configuration keys. + // Follow assignment use order of values in relation to our list of key names! + DBG_ASSERT( !(rPropertyNames.getLength()!=seqValues.getLength()), + "SvtSlideSorterBarOptions_Impl::SvtSlideSorterBarOptions_Impl()\nI miss some values of configuration keys!\n" ); + + // Copy values from list in right order to our internal member. + for( sal_Int32 nProperty=0; nProperty<seqValues.getLength(); ++nProperty ) + { + if (!seqValues[nProperty].hasValue()) + continue; + switch( comphelper::findValue(aInternalPropertyNames, rPropertyNames[nProperty]) ) + { + case PROPERTYHANDLE_VISIBLE_IMPRESSVIEW: + { + if( !(seqValues[nProperty] >>= m_bVisibleImpressView) ) + OSL_FAIL("Wrong type of \"SlideSorterBar\\VisibleImpressView\"!" ); + } + break; + case PROPERTYHANDLE_VISIBLE_OUTLINEVIEW : + { + if( !(seqValues[nProperty] >>= m_bVisibleOutlineView) ) + OSL_FAIL("Wrong type of \"SlideSorterBar\\VisibleOutlineView\"!" ); + } + break; + case PROPERTYHANDLE_VISIBLE_NOTESVIEW : + { + if( !(seqValues[nProperty] >>= m_bVisibleNotesView) ) + OSL_FAIL("Wrong type of \"SlideSorterBar\\VisibleNotesView\"!" ); + } + break; + case PROPERTYHANDLE_VISIBLE_HANDOUTVIEW : + { + if( !(seqValues[nProperty] >>= m_bVisibleHandoutView) ) + OSL_FAIL("Wrong type of \"SlideSorterBar\\VisibleHandoutView\"!" ); + } + break; + case PROPERTYHANDLE_VISIBLE_SLIDESORTERVIEW : + { + if( !(seqValues[nProperty] >>= m_bVisibleSlideSorterView) ) + OSL_FAIL("Wrong type of \"SlideSorterBar\\VisibleSlideSorterView\"!" ); + } + break; + + case PROPERTYHANDLE_VISIBLE_DRAWVIEW : + { + if( !(seqValues[nProperty] >>= m_bVisibleDrawView) ) + OSL_FAIL("Wrong type of \"SlideSorterBar\\VisibleDrawView\"!" ); + } + break; + } + } +} + +void SvtSlideSorterBarOptions_Impl::Notify( const Sequence< OUString >& rPropertyNames ) +{ + Load( rPropertyNames ); +} + +void SvtSlideSorterBarOptions_Impl::ImplCommit() +{ + // Get names of supported properties, create a list for values and copy current values to it. + sal_Int32 nCount = m_seqPropertyNames.getLength(); + Sequence< Any > seqValues ( nCount ); + auto seqValuesRange = asNonConstRange(seqValues); + for( sal_Int32 nProperty=0; nProperty<nCount; ++nProperty ) + { + switch( nProperty ) + { + case PROPERTYHANDLE_VISIBLE_IMPRESSVIEW: + { + seqValuesRange[nProperty] <<= m_bVisibleImpressView; + break; + } + case PROPERTYHANDLE_VISIBLE_OUTLINEVIEW: + { + seqValuesRange[nProperty] <<= m_bVisibleOutlineView; + break; + } + case PROPERTYHANDLE_VISIBLE_NOTESVIEW: + { + seqValuesRange[nProperty] <<= m_bVisibleNotesView; + break; + } + case PROPERTYHANDLE_VISIBLE_HANDOUTVIEW: + { + seqValuesRange[nProperty] <<= m_bVisibleHandoutView; + break; + } + case PROPERTYHANDLE_VISIBLE_SLIDESORTERVIEW: + { + seqValuesRange[nProperty] <<= m_bVisibleSlideSorterView; + break; + } + case PROPERTYHANDLE_VISIBLE_DRAWVIEW: + { + seqValuesRange[nProperty] <<= m_bVisibleDrawView; + break; + } + + } + } + // Set properties in configuration. + PutProperties( m_seqPropertyNames, seqValues ); +} + +Sequence< OUString > SvtSlideSorterBarOptions_Impl::GetPropertyNames() +{ + // Build list of configuration key names. + return + { + PROPERTYNAME_VISIBLE_IMPRESSVIEW, + PROPERTYNAME_VISIBLE_OUTLINEVIEW, + PROPERTYNAME_VISIBLE_NOTESVIEW, + PROPERTYNAME_VISIBLE_HANDOUTVIEW, + PROPERTYNAME_VISIBLE_SLIDESORTERVIEW, + PROPERTYNAME_VISIBLE_DRAWVIEW, + }; +} + +void SvtSlideSorterBarOptions_Impl::SetVisibleViewImpl( bool& bVisibleView, bool bVisible ) +{ + if( bVisibleView != bVisible ) + { + bVisibleView = bVisible; + SetModified(); + } +} + +namespace { + std::weak_ptr<SvtSlideSorterBarOptions_Impl> g_pSlideSorterBarOptions; +} + +SvtSlideSorterBarOptions::SvtSlideSorterBarOptions() +{ + // Global access, must be guarded (multithreading!). + std::unique_lock aGuard( GetInitMutex() ); + + m_pImpl = g_pSlideSorterBarOptions.lock(); + if( !m_pImpl ) + { + m_pImpl = std::make_shared<SvtSlideSorterBarOptions_Impl>(); + g_pSlideSorterBarOptions = m_pImpl; + } +} + +SvtSlideSorterBarOptions::~SvtSlideSorterBarOptions() +{ + // Global access, must be guarded (multithreading!) + std::unique_lock aGuard( GetInitMutex() ); + + m_pImpl.reset(); +} + +bool SvtSlideSorterBarOptions::GetVisibleImpressView() const +{ + static const bool bRunningUnitTest = getenv("LO_TESTNAME"); + return m_pImpl->m_bVisibleImpressView && (!bRunningUnitTest || !comphelper::LibreOfficeKit::isActive()); +} + +void SvtSlideSorterBarOptions::SetVisibleImpressView(bool bVisible) +{ + m_pImpl->SetVisibleImpressView( bVisible ); +} + +bool SvtSlideSorterBarOptions::GetVisibleOutlineView() const +{ + return m_pImpl->m_bVisibleOutlineView; +} + +void SvtSlideSorterBarOptions::SetVisibleOutlineView(bool bVisible) +{ + m_pImpl->SetVisibleOutlineView( bVisible ); +} + +bool SvtSlideSorterBarOptions::GetVisibleNotesView() const +{ + return m_pImpl->m_bVisibleNotesView; +} + +void SvtSlideSorterBarOptions::SetVisibleNotesView(bool bVisible) +{ + m_pImpl->SetVisibleNotesView( bVisible ); +} + +bool SvtSlideSorterBarOptions::GetVisibleHandoutView() const +{ + return m_pImpl->m_bVisibleHandoutView; +} + +void SvtSlideSorterBarOptions::SetVisibleHandoutView(bool bVisible) +{ + m_pImpl->SetVisibleHandoutView( bVisible ); +} + +bool SvtSlideSorterBarOptions::GetVisibleSlideSorterView() const +{ + return m_pImpl->m_bVisibleSlideSorterView && !comphelper::LibreOfficeKit::isActive(); +} + +void SvtSlideSorterBarOptions::SetVisibleSlideSorterView(bool bVisible) +{ + m_pImpl->SetVisibleSlideSorterView( bVisible ); +} + +bool SvtSlideSorterBarOptions::GetVisibleDrawView() const +{ + return m_pImpl->m_bVisibleDrawView; +} + +void SvtSlideSorterBarOptions::SetVisibleDrawView(bool bVisible) +{ + m_pImpl->SetVisibleDrawView( bVisible ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/config/test/test.cxx b/svtools/source/config/test/test.cxx new file mode 100644 index 000000000..c84d2ca9f --- /dev/null +++ b/svtools/source/config/test/test.cxx @@ -0,0 +1,215 @@ +/* -*- 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 . + */ + + +// switches +// use it to enable test scenarios + + +#define TEST_DYNAMICMENUOPTIONS + +#include <unotools/dynamicmenuoptions.hxx> + +#include <cppuhelper/bootstrap.hxx> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/registry/XSimpleRegistry.hpp> + +#include <cppuhelper/servicefactory.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/Sequence.h> + +#include <rtl/ustring> +#include <rtl/ustrbuf.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> + +using namespace ::osl ; +using namespace ::comphelper ; +using namespace ::com::sun::star::uno ; +using namespace ::com::sun::star::lang ; +using namespace ::com::sun::star::beans ; +using namespace ::com::sun::star::registry ; + +class TestApplication : public Application +{ + + // interface + + public: + void Main(); + + + // test methods + + private: + void impl_testDynamicMenuOptions(); + + + // helper methods + + private: + static Reference< XMultiServiceFactory > getUNOServiceManager(); + + + // member + + private: + +}; // class TestApplication + + +// global variables + + +TestApplication aTestApplication ; + + +// main + + +void TestApplication::Main() +{ + /**-*********************************************************************************************************** + initialize program + **************************************************************************************************************/ + + // Init global servicemanager and set it for external services. + ::comphelper::setProcessServiceFactory( TestApplication::getUNOServiceManager() ); + // Control success of operation. + OSL_ENSURE( !(::comphelper::getProcessServiceFactory()!=TestApplication::getUNOServiceManager()), "TestApplication::Main() Global servicemanager not right initialized." ); + + /**-*********************************************************************************************************** + test area + **************************************************************************************************************/ + + #ifdef TEST_DYNAMICMENUOPTIONS + impl_testDynamicMenuOptions(); + #endif + +// Execute(); + OSL_FAIL( "Test was successful!" ); +} + + +// test configuration of dynamic menus "New" and "Wizard" + +void TestApplication::impl_testDynamicMenuOptions() +{ + SvtDynamicMenuOptions aCFG; + + // Test: + // read menus + // if( menus == empty ) + // { + // fill it with samples + // read it again + // } + // output content + + Sequence< Sequence< PropertyValue > > lNewMenu = aCFG.GetMenu( EDynamicMenuType::NewMenu ); + Sequence< Sequence< PropertyValue > > lWizardMenu = aCFG.GetMenu( EDynamicMenuType::WizardMenu ); + + if( lNewMenu.getLength() < 1 ) + { + aCFG.AppendItem( EDynamicMenuType::NewMenu, "private:factory/swriter", "new writer", "icon_writer", "_blank"); + aCFG.AppendItem( EDynamicMenuType::NewMenu, "private:factory/scalc", "new calc", "icon_calc", "_blank"); + aCFG.AppendItem( EDynamicMenuType::NewMenu, "private:factory/sdraw", "new draw", "icon_draw", "_blank"); + + lNewMenu = aCFG.GetMenu( EDynamicMenuType::NewMenu ); + } + + if( lWizardMenu.getLength() < 1 ) + { + aCFG.AppendItem( EDynamicMenuType::WizardMenu, "file://a", "system file", "icon_file", "_self"); + aCFG.AppendItem( EDynamicMenuType::WizardMenu, "ftp://b", "ftp host", "icon_ftp", "_self"); + aCFG.AppendItem( EDynamicMenuType::WizardMenu, "http://c", "www", "icon_www", "_self"); + + lWizardMenu = aCFG.GetMenu( EDynamicMenuType::WizardMenu ); + } + + sal_uInt32 nItemCount ; + sal_uInt32 nItem ; + sal_uInt32 nPropertyCount; + sal_uInt32 nProperty ; + OUString sPropertyValue; + OUStringBuffer sOut( 5000 ) ; + + nItemCount = lNewMenu.getLength(); + for( nItem=0; nItem<nItemCount; ++nItem ) + { + nPropertyCount = lNewMenu[nItem].getLength(); + for( nProperty=0; nProperty<nPropertyCount; ++nProperty ) + { + lNewMenu[nItem][nProperty].Value >>= sPropertyValue; + + sOut.appendAscii ( "New/" ); + sOut.append ( (sal_Int32)nItem ); + sOut.appendAscii ( "/" ); + sOut.append ( lNewMenu[nItem][nProperty].Name ); + sOut.appendAscii ( " = " ); + sOut.append ( sPropertyValue ); + sOut.appendAscii ( "\n" ); + } + } + + sOut.appendAscii("\n--------------------------------------\n"); + + nItemCount = lWizardMenu.getLength(); + for( nItem=0; nItem<nItemCount; ++nItem ) + { + nPropertyCount = lNewMenu[nItem].getLength(); + for( nProperty=0; nProperty<nPropertyCount; ++nProperty ) + { + lWizardMenu[nItem][nProperty].Value >>= sPropertyValue; + + sOut.appendAscii ( "Wizard/" ); + sOut.append ( (sal_Int32)nItem ); + sOut.appendAscii ( "/" ); + sOut.append ( lNewMenu[nItem][nProperty].Name ); + sOut.appendAscii ( " = " ); + sOut.append ( sPropertyValue ); + sOut.appendAscii ( "\n" ); + } + } + + SAL_WARN( "svtools", sOut ); +} + + +// create new uno servicemanager by using normal applicat.rdb and user.rdb of an office installation! +// Don't use this application at the same time like the office! + +Reference< XMultiServiceFactory > TestApplication::getUNOServiceManager() +{ + static Reference< XMultiServiceFactory > smgr; + if( ! smgr.is() ) + { + Reference< XComponentContext > rCtx = + cppu::defaultBootstrap_InitialComponentContext(); + smgr.set( rCtx->getServiceManager() , UNO_QUERY ); + } + return smgr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/contnr/querydelete.cxx b/svtools/source/contnr/querydelete.cxx new file mode 100644 index 000000000..9b97983f7 --- /dev/null +++ b/svtools/source/contnr/querydelete.cxx @@ -0,0 +1,35 @@ +/* -*- 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 <svtools/querydelete.hxx> + +namespace svtools +{ +QueryDeleteDlg_Impl::QueryDeleteDlg_Impl(weld::Widget* pParent, std::u16string_view rName) + : MessageDialogController(pParent, "svt/ui/querydeletedialog.ui", "QueryDeleteDialog") + , m_xAllButton(m_xBuilder->weld_button("all")) +{ + // display specified texts + m_xDialog->set_secondary_text(m_xDialog->get_secondary_text().replaceFirst("%s", rName)); +} + +QueryDeleteDlg_Impl::~QueryDeleteDlg_Impl() {} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/accessibleruler.cxx b/svtools/source/control/accessibleruler.cxx new file mode 100644 index 000000000..c7718935b --- /dev/null +++ b/svtools/source/control/accessibleruler.cxx @@ -0,0 +1,362 @@ +/* -*- 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 <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/IllegalAccessibleComponentStateException.hpp> +#include <unotools/accessiblestatesethelper.hxx> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <comphelper/accessibleeventnotifier.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <toolkit/helper/convert.hxx> +#include <vcl/svapp.hxx> +#include <osl/mutex.hxx> +#include <tools/gen.hxx> + +#include <svtools/ruler.hxx> +#include "accessibleruler.hxx" + +using namespace ::cppu; +using namespace ::osl; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::accessibility; + + +//===== internal ============================================================ + +SvtRulerAccessible::SvtRulerAccessible( + const uno::Reference< XAccessible >& rxParent, Ruler& rRepr, const OUString& rName ) : + + SvtRulerAccessible_Base( m_aMutex ), + msName( rName ), + mxParent( rxParent ), + mpRepr( &rRepr ), + mnClientId( 0 ) +{ +} + +SvtRulerAccessible::~SvtRulerAccessible() +{ + + if( IsAlive() ) + { + osl_atomic_increment( &m_refCount ); + dispose(); // set mpRepr = NULL & release all children + } +} + +//===== XAccessible ========================================================= + +uno::Reference< XAccessibleContext > SAL_CALL SvtRulerAccessible::getAccessibleContext() +{ + return this; +} + +//===== XAccessibleComponent ================================================ + +sal_Bool SAL_CALL SvtRulerAccessible::containsPoint( const awt::Point& rPoint ) +{ + // no guard -> done in getBounds() +// return GetBoundingBox().IsInside( VCLPoint( rPoint ) ); + return tools::Rectangle( Point( 0, 0 ), GetBoundingBox().GetSize() ).Contains( VCLPoint( rPoint ) ); +} + +uno::Reference< XAccessible > SAL_CALL SvtRulerAccessible::getAccessibleAtPoint( const awt::Point& ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + ThrowExceptionIfNotAlive(); + + uno::Reference< XAccessible > xRet; + + + return xRet; +} + +awt::Rectangle SAL_CALL SvtRulerAccessible::getBounds() +{ + // no guard -> done in GetBoundingBox() + return AWTRectangle( GetBoundingBox() ); +} + +awt::Point SAL_CALL SvtRulerAccessible::getLocation() +{ + // no guard -> done in GetBoundingBox() + return AWTPoint( GetBoundingBox().TopLeft() ); +} + +awt::Point SAL_CALL SvtRulerAccessible::getLocationOnScreen() +{ + // no guard -> done in GetBoundingBoxOnScreen() + return AWTPoint( GetBoundingBoxOnScreen().TopLeft() ); +} + +awt::Size SAL_CALL SvtRulerAccessible::getSize() +{ + // no guard -> done in GetBoundingBox() + return AWTSize( GetBoundingBox().GetSize() ); +} + +bool SvtRulerAccessible::isVisible() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + ThrowExceptionIfNotAlive(); + + return mpRepr->IsVisible(); +} + +//===== XAccessibleContext ================================================== +sal_Int32 SAL_CALL SvtRulerAccessible::getAccessibleChildCount() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + ThrowExceptionIfNotAlive(); + + return 0; +} + +uno::Reference< XAccessible > SAL_CALL SvtRulerAccessible::getAccessibleChild( sal_Int32 ) +{ + uno::Reference< XAccessible > xChild ; + + return xChild; +} + +uno::Reference< XAccessible > SAL_CALL SvtRulerAccessible::getAccessibleParent() +{ + return mxParent; +} + +sal_Int32 SAL_CALL SvtRulerAccessible::getAccessibleIndexInParent() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + // Use a simple but slow solution for now. Optimize later. + + // Iterate over all the parent's children and search for this object. + if( mxParent.is() ) + { + uno::Reference< XAccessibleContext > xParentContext( mxParent->getAccessibleContext() ); + if( xParentContext.is() ) + { + sal_Int32 nChildCount = xParentContext->getAccessibleChildCount(); + for( sal_Int32 i = 0 ; i < nChildCount ; ++i ) + { + uno::Reference< XAccessible > xChild( xParentContext->getAccessibleChild( i ) ); + if( xChild.get() == static_cast<XAccessible*>(this) ) + return i; + } + } + } + + // Return -1 to indicate that this object's parent does not know about the + // object. + return -1; +} + +sal_Int16 SAL_CALL SvtRulerAccessible::getAccessibleRole() +{ + return AccessibleRole::RULER; +} + +OUString SAL_CALL SvtRulerAccessible::getAccessibleDescription() +{ + return OUString(); +} + +OUString SAL_CALL SvtRulerAccessible::getAccessibleName() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return msName; +} + +/** Return empty uno::Reference to indicate that the relation set is not + supported. +*/ +uno::Reference< XAccessibleRelationSet > SAL_CALL SvtRulerAccessible::getAccessibleRelationSet() +{ + return uno::Reference< XAccessibleRelationSet >(); +} + + +uno::Reference< XAccessibleStateSet > SAL_CALL SvtRulerAccessible::getAccessibleStateSet() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + rtl::Reference<utl::AccessibleStateSetHelper> pStateSetHelper = new utl::AccessibleStateSetHelper; + + if( IsAlive() ) + { + pStateSetHelper->AddState( AccessibleStateType::ENABLED ); + + pStateSetHelper->AddState( AccessibleStateType::SHOWING ); + + if( isVisible() ) + pStateSetHelper->AddState( AccessibleStateType::VISIBLE ); + + if ( mpRepr->GetStyle() & WB_HORZ ) + pStateSetHelper->AddState( AccessibleStateType::HORIZONTAL ); + else + pStateSetHelper->AddState( AccessibleStateType::VERTICAL ); + + if(pStateSetHelper->contains(AccessibleStateType::FOCUSABLE)) + { + pStateSetHelper->RemoveState( AccessibleStateType::FOCUSABLE ); + } + + } + + + return pStateSetHelper; +} + +lang::Locale SAL_CALL SvtRulerAccessible::getLocale() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if( mxParent.is() ) + { + uno::Reference< XAccessibleContext > xParentContext( mxParent->getAccessibleContext() ); + if( xParentContext.is() ) + return xParentContext->getLocale(); + } + + // No parent. Therefore throw exception to indicate this cluelessness. + throw IllegalAccessibleComponentStateException(); +} + +void SAL_CALL SvtRulerAccessible::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) +{ + if (xListener.is()) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if (!mnClientId) + mnClientId = comphelper::AccessibleEventNotifier::registerClient( ); + comphelper::AccessibleEventNotifier::addEventListener( mnClientId, xListener ); + } +} + +void SAL_CALL SvtRulerAccessible::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) +{ + if (!(xListener.is() && mnClientId)) + return; + + ::osl::MutexGuard aGuard( m_aMutex ); + + sal_Int32 nListenerCount = comphelper::AccessibleEventNotifier::removeEventListener( mnClientId, xListener ); + if ( !nListenerCount ) + { + // no listeners anymore + // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client), + // and at least to us not firing any events anymore, in case somebody calls + // NotifyAccessibleEvent, again + comphelper::AccessibleEventNotifier::revokeClient( mnClientId ); + mnClientId = 0; + } +} + +void SAL_CALL SvtRulerAccessible::grabFocus() +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + ThrowExceptionIfNotAlive(); + + mpRepr->GrabFocus(); +} + +sal_Int32 SvtRulerAccessible::getForeground( ) +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + ThrowExceptionIfNotAlive(); + + return sal_Int32(mpRepr->GetControlForeground()); +} +sal_Int32 SvtRulerAccessible::getBackground( ) +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + ThrowExceptionIfNotAlive(); + + return sal_Int32(mpRepr->GetControlBackground()); +} + +// XServiceInfo +OUString SAL_CALL SvtRulerAccessible::getImplementationName() +{ + return "com.sun.star.comp.ui.SvtRulerAccessible"; +} + +sal_Bool SAL_CALL SvtRulerAccessible::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService( this, sServiceName ); +} + +Sequence< OUString > SAL_CALL SvtRulerAccessible::getSupportedServiceNames() +{ + return { "com.sun.star.accessibility.AccessibleContext" }; +} + +//===== XTypeProvider ======================================================= +Sequence< sal_Int8 > SAL_CALL SvtRulerAccessible::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +void SAL_CALL SvtRulerAccessible::disposing() +{ + if( rBHelper.bDisposed ) + return; + + ::osl::MutexGuard aGuard( m_aMutex ); + mpRepr = nullptr; // object dies with representation + + // Send a disposing to all listeners. + if ( mnClientId ) + { + comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing( mnClientId, *this ); + mnClientId = 0; + } + mxParent.clear(); +} + +tools::Rectangle SvtRulerAccessible::GetBoundingBoxOnScreen() +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + ThrowExceptionIfNotAlive(); + return tools::Rectangle( mpRepr->GetParent()->OutputToAbsoluteScreenPixel( mpRepr->GetPosPixel() ), mpRepr->GetSizePixel() ); +} + +tools::Rectangle SvtRulerAccessible::GetBoundingBox() +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + ThrowExceptionIfNotAlive(); + + return tools::Rectangle( mpRepr->GetPosPixel(), mpRepr->GetSizePixel() ); +} + +void SvtRulerAccessible::ThrowExceptionIfNotAlive() +{ + if( rBHelper.bDisposed || rBHelper.bInDispose ) + throw lang::DisposedException(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/accessibleruler.hxx b/svtools/source/control/accessibleruler.hxx new file mode 100644 index 000000000..bd7d46123 --- /dev/null +++ b/svtools/source/control/accessibleruler.hxx @@ -0,0 +1,188 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SVTOOLS_ACCESSIBLERULER_HXX +#define INCLUDED_SVTOOLS_ACCESSIBLERULER_HXX + +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <cppuhelper/interfacecontainer.h> +#include <cppuhelper/compbase5.hxx> +#include <cppuhelper/basemutex.hxx> +#include <vcl/vclptr.hxx> + +namespace tools { class Rectangle; } +class Ruler; + + +typedef ::cppu::WeakAggComponentImplHelper5< + css::accessibility::XAccessible, + css::accessibility::XAccessibleComponent, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleEventBroadcaster, + css::lang::XServiceInfo > + SvtRulerAccessible_Base; + +class SvtRulerAccessible final : public ::cppu::BaseMutex, public SvtRulerAccessible_Base +{ +public: + //===== internal ======================================================== + SvtRulerAccessible( + const css::uno::Reference< css::accessibility::XAccessible>& rxParent, Ruler& rRepresentation, const OUString& rName ); + + /// @throws css::uno::RuntimeException + bool + isVisible(); + + //===== XAccessible ===================================================== + + virtual css::uno::Reference< css::accessibility::XAccessibleContext> SAL_CALL + getAccessibleContext() override; + + //===== XAccessibleComponent ============================================ + + virtual sal_Bool SAL_CALL + containsPoint( const css::awt::Point& rPoint ) override; + + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL + getAccessibleAtPoint( const css::awt::Point& rPoint ) override; + + virtual css::awt::Rectangle SAL_CALL + getBounds() override; + + virtual css::awt::Point SAL_CALL + getLocation() override; + + virtual css::awt::Point SAL_CALL + getLocationOnScreen() override; + + virtual css::awt::Size SAL_CALL + getSize() override; + + virtual void SAL_CALL + grabFocus() override; + + virtual sal_Int32 SAL_CALL + getForeground( ) override; + virtual sal_Int32 SAL_CALL + getBackground( ) override; + + //===== XAccessibleContext ============================================== + + virtual sal_Int32 SAL_CALL + getAccessibleChildCount() override; + + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleChild( sal_Int32 nIndex ) override; + + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleParent() override; + + virtual sal_Int32 SAL_CALL + getAccessibleIndexInParent() override; + + virtual sal_Int16 SAL_CALL + getAccessibleRole() override; + + virtual OUString SAL_CALL + getAccessibleDescription() override; + + virtual OUString SAL_CALL + getAccessibleName() override; + + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL + getAccessibleRelationSet() override; + + virtual css::uno::Reference< css::accessibility::XAccessibleStateSet > SAL_CALL + getAccessibleStateSet() override; + + virtual css::lang::Locale SAL_CALL + getLocale() override; + //===== XAccessibleEventBroadcaster ===================================== + + virtual void SAL_CALL + addAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + virtual void SAL_CALL + removeAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + //===== XServiceInfo ==================================================== + + virtual OUString SAL_CALL + getImplementationName() override; + + virtual sal_Bool SAL_CALL + supportsService( const OUString& sServiceName ) override; + + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + //===== XTypeProvider =================================================== + + virtual css::uno::Sequence<sal_Int8> SAL_CALL + getImplementationId() override; + +private: + + virtual ~SvtRulerAccessible() override; + + virtual void SAL_CALL disposing() override; + + /// @returns true if it's disposed or in disposing + inline bool IsAlive() const; + + /// @throws DisposedException if it's not alive + void ThrowExceptionIfNotAlive(); + + /// @Return the object's current bounding box relative to the desktop. + /// + /// @throws css::uno::RuntimeException + tools::Rectangle GetBoundingBoxOnScreen(); + + /// @Return the object's current bounding box relative to the parent object. + /// + /// @throws css::uno::RuntimeException + tools::Rectangle GetBoundingBox(); + + /// Name of this object. + OUString msName; + + /// Reference to the parent object. + css::uno::Reference< css::accessibility::XAccessible > + mxParent; + + /// pointer to internal representation + VclPtr<Ruler> mpRepr; + + /// client id in the AccessibleEventNotifier queue + sal_uInt32 mnClientId; +}; + +inline bool SvtRulerAccessible::IsAlive() const +{ + return !rBHelper.bDisposed && !rBHelper.bInDispose; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/asynclink.cxx b/svtools/source/control/asynclink.cxx new file mode 100644 index 000000000..95b8a6685 --- /dev/null +++ b/svtools/source/control/asynclink.cxx @@ -0,0 +1,71 @@ +/* -*- 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 <svtools/asynclink.hxx> +#include <tools/debug.hxx> +#include <vcl/svapp.hxx> + + +namespace svtools { + +void AsynchronLink::Call( void* pObj, bool bAllowDoubles ) +{ + if( !_aLink.IsSet() ) + return; + + _pArg = pObj; + DBG_ASSERT( bAllowDoubles || !_nEventId, "Already made a call" ); + ClearPendingCall(); + std::scoped_lock aGuard(_aMutex); + _nEventId = Application::PostUserEvent( LINK( this, AsynchronLink, HandleCall_PostUserEvent) ); +} + +AsynchronLink::~AsynchronLink() +{ + if( _nEventId ) + { + Application::RemoveUserEvent( _nEventId ); + } +} + +void AsynchronLink::ClearPendingCall() +{ + std::scoped_lock aGuard(_aMutex); + if( _nEventId ) + { + Application::RemoveUserEvent( _nEventId ); + _nEventId = nullptr; + } +} + +IMPL_LINK_NOARG( AsynchronLink, HandleCall_PostUserEvent, void*, void ) +{ + { + std::scoped_lock aGuard(_aMutex); + _nEventId = nullptr; + // need to release the lock before calling the client since + // the client may call back into us + } + _aLink.Call( _pArg ); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/collatorres.cxx b/svtools/source/control/collatorres.cxx new file mode 100644 index 000000000..d88113998 --- /dev/null +++ b/svtools/source/control/collatorres.cxx @@ -0,0 +1,65 @@ +/* -*- 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 <svtools/svtresid.hxx> +#include <svtools/strings.hrc> +#include <svtools/collatorres.hxx> + +// implementation of the collator-algorithm-name translation +CollatorResource::CollatorResource() +{ + m_aData.emplace_back("alphanumeric", SvtResId(STR_SVT_COLLATE_ALPHANUMERIC)); + m_aData.emplace_back("charset", SvtResId(STR_SVT_COLLATE_CHARSET)); + m_aData.emplace_back("dict", SvtResId(STR_SVT_COLLATE_DICTIONARY)); + m_aData.emplace_back("normal", SvtResId(STR_SVT_COLLATE_NORMAL)); + m_aData.emplace_back("pinyin", SvtResId(STR_SVT_COLLATE_PINYIN)); + m_aData.emplace_back("radical", SvtResId(STR_SVT_COLLATE_RADICAL)); + m_aData.emplace_back("stroke", SvtResId(STR_SVT_COLLATE_STROKE)); + m_aData.emplace_back("unicode", SvtResId(STR_SVT_COLLATE_UNICODE)); + m_aData.emplace_back("zhuyin", SvtResId(STR_SVT_COLLATE_ZHUYIN)); + m_aData.emplace_back("phonebook", SvtResId(STR_SVT_COLLATE_PHONEBOOK)); + m_aData.emplace_back("phonetic (alphanumeric first)", SvtResId(STR_SVT_COLLATE_PHONETIC_F)); + m_aData.emplace_back("phonetic (alphanumeric last)", SvtResId(STR_SVT_COLLATE_PHONETIC_L)); +} + +const OUString& CollatorResource::GetTranslation(const OUString& r_Algorithm) +{ + sal_Int32 nIndex = r_Algorithm.indexOf('.'); + OUString aLocaleFreeAlgorithm; + + if (nIndex == -1) + { + aLocaleFreeAlgorithm = r_Algorithm; + } + else + { + nIndex += 1; + aLocaleFreeAlgorithm = r_Algorithm.copy(nIndex); + } + + for (size_t i = 0; i < m_aData.size(); ++i) + { + if (aLocaleFreeAlgorithm == m_aData[i].GetAlgorithm()) + return m_aData[i].GetTranslation(); + } + + return r_Algorithm; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/ctrlbox.cxx b/svtools/source/control/ctrlbox.cxx new file mode 100644 index 000000000..af86920a5 --- /dev/null +++ b/svtools/source/control/ctrlbox.cxx @@ -0,0 +1,1672 @@ +/* -*- 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 <config_folders.h> + +#include <comphelper/lok.hxx> +#include <i18nutil/unicode.hxx> +#include <officecfg/Office/Common.hxx> +#include <tools/stream.hxx> +#include <vcl/customweld.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <vcl/fieldvalues.hxx> +#include <vcl/settings.hxx> +#include <vcl/image.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weldutils.hxx> +#include <rtl/math.hxx> +#include <sal/macros.h> +#include <sal/log.hxx> +#include <comphelper/string.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/syslocale.hxx> + +#include <svtools/borderline.hxx> +#include <svtools/sampletext.hxx> +#include <svtools/svtresid.hxx> +#include <svtools/strings.hrc> +#include <svtools/ctrlbox.hxx> +#include <svtools/ctrltool.hxx> +#include <svtools/borderhelper.hxx> +#include <svtools/valueset.hxx> + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <editeng/borderline.hxx> + +#include <rtl/bootstrap.hxx> + +#include <borderline.hrc> + +#include <stdio.h> + +#define IMGOUTERTEXTSPACE 5 +#define EXTRAFONTSIZE 5 +#define GAPTOEXTRAPREVIEW 10 +#define MINGAPWIDTH 2 + +constexpr OUStringLiteral FONTNAMEBOXMRUENTRIESFILE = u"/user/config/fontnameboxmruentries"; + + +BorderWidthImpl::BorderWidthImpl( BorderWidthImplFlags nFlags, double nRate1, double nRate2, double nRateGap ): + m_nFlags( nFlags ), + m_nRate1( nRate1 ), + m_nRate2( nRate2 ), + m_nRateGap( nRateGap ) +{ +} + +bool BorderWidthImpl::operator== ( const BorderWidthImpl& r ) const +{ + return ( m_nFlags == r.m_nFlags ) && + ( m_nRate1 == r.m_nRate1 ) && + ( m_nRate2 == r.m_nRate2 ) && + ( m_nRateGap == r.m_nRateGap ); +} + +tools::Long BorderWidthImpl::GetLine1( tools::Long nWidth ) const +{ + tools::Long result = static_cast<tools::Long>(m_nRate1); + if ( m_nFlags & BorderWidthImplFlags::CHANGE_LINE1 ) + { + tools::Long const nConstant2 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE2) ? 0 : m_nRate2; + tools::Long const nConstantD = (m_nFlags & BorderWidthImplFlags::CHANGE_DIST ) ? 0 : m_nRateGap; + result = std::max<tools::Long>(0, + static_cast<tools::Long>((m_nRate1 * nWidth) + 0.5) + - (nConstant2 + nConstantD)); + if (result == 0 && m_nRate1 > 0.0 && nWidth > 0) + { // fdo#51777: hack to essentially treat 1 twip DOUBLE border + result = 1; // as 1 twip SINGLE border + } + } + return result; +} + +tools::Long BorderWidthImpl::GetLine2( tools::Long nWidth ) const +{ + tools::Long result = static_cast<tools::Long>(m_nRate2); + if ( m_nFlags & BorderWidthImplFlags::CHANGE_LINE2) + { + tools::Long const nConstant1 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE1) ? 0 : m_nRate1; + tools::Long const nConstantD = (m_nFlags & BorderWidthImplFlags::CHANGE_DIST ) ? 0 : m_nRateGap; + result = std::max<tools::Long>(0, + static_cast<tools::Long>((m_nRate2 * nWidth) + 0.5) + - (nConstant1 + nConstantD)); + } + return result; +} + +tools::Long BorderWidthImpl::GetGap( tools::Long nWidth ) const +{ + tools::Long result = static_cast<tools::Long>(m_nRateGap); + if ( m_nFlags & BorderWidthImplFlags::CHANGE_DIST ) + { + tools::Long const nConstant1 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE1) ? 0 : m_nRate1; + tools::Long const nConstant2 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE2) ? 0 : m_nRate2; + result = std::max<tools::Long>(0, + static_cast<tools::Long>((m_nRateGap * nWidth) + 0.5) + - (nConstant1 + nConstant2)); + } + + // Avoid having too small distances (less than 0.1pt) + if ( result < MINGAPWIDTH && m_nRate1 > 0 && m_nRate2 > 0 ) + result = MINGAPWIDTH; + + return result; +} + +static double lcl_getGuessedWidth( tools::Long nTested, double nRate, bool bChanging ) +{ + double nWidth = -1.0; + if ( bChanging ) + nWidth = double( nTested ) / nRate; + else + { + if ( rtl::math::approxEqual(double( nTested ), nRate) ) + nWidth = nRate; + } + + return nWidth; +} + +tools::Long BorderWidthImpl::GuessWidth( tools::Long nLine1, tools::Long nLine2, tools::Long nGap ) +{ + std::vector< double > aToCompare; + bool bInvalid = false; + + bool bLine1Change = bool( m_nFlags & BorderWidthImplFlags::CHANGE_LINE1 ); + double nWidth1 = lcl_getGuessedWidth( nLine1, m_nRate1, bLine1Change ); + if ( bLine1Change ) + aToCompare.push_back( nWidth1 ); + else if (nWidth1 < 0) + bInvalid = true; + + bool bLine2Change = bool( m_nFlags & BorderWidthImplFlags::CHANGE_LINE2 ); + double nWidth2 = lcl_getGuessedWidth( nLine2, m_nRate2, bLine2Change ); + if ( bLine2Change ) + aToCompare.push_back( nWidth2 ); + else if (nWidth2 < 0) + bInvalid = true; + + bool bGapChange = bool( m_nFlags & BorderWidthImplFlags::CHANGE_DIST ); + double nWidthGap = lcl_getGuessedWidth( nGap, m_nRateGap, bGapChange ); + if ( bGapChange && nGap >= MINGAPWIDTH ) + aToCompare.push_back( nWidthGap ); + else if ( !bGapChange && nWidthGap < 0 ) + bInvalid = true; + + // non-constant line width factors must sum to 1 + assert((((bLine1Change) ? m_nRate1 : 0) + + ((bLine2Change) ? m_nRate2 : 0) + + ((bGapChange) ? m_nRateGap : 0)) - 1.0 < 0.00001 ); + + double nWidth = 0.0; + if ( (!bInvalid) && (!aToCompare.empty()) ) + { + nWidth = *aToCompare.begin(); + for (auto const& elem : aToCompare) + { + bInvalid = ( nWidth != elem ); + if (bInvalid) + break; + } + nWidth = bInvalid ? 0.0 : nLine1 + nLine2 + nGap; + } + + return nWidth; +} + +static void lclDrawPolygon( OutputDevice& rDev, const basegfx::B2DPolygon& rPolygon, tools::Long nWidth, SvxBorderLineStyle nDashing ) +{ + AntialiasingFlags nOldAA = rDev.GetAntialiasing(); + rDev.SetAntialiasing( nOldAA & ~AntialiasingFlags::Enable ); + + tools::Long nPix = rDev.PixelToLogic(Size(1, 1)).Width(); + basegfx::B2DPolyPolygon aPolygons = svtools::ApplyLineDashing(rPolygon, nDashing, nPix); + + // Handle problems of width 1px in Pixel mode: 0.5px gives a 1px line + if (rDev.GetMapMode().GetMapUnit() == MapUnit::MapPixel && nWidth == nPix) + nWidth = 0; + + for ( sal_uInt32 i = 0; i < aPolygons.count( ); i++ ) + { + const basegfx::B2DPolygon& aDash = aPolygons.getB2DPolygon( i ); + basegfx::B2DPoint aStart = aDash.getB2DPoint( 0 ); + basegfx::B2DPoint aEnd = aDash.getB2DPoint( aDash.count() - 1 ); + + basegfx::B2DVector aVector( aEnd - aStart ); + aVector.normalize( ); + const basegfx::B2DVector aPerpendicular(basegfx::getPerpendicular(aVector)); + + const basegfx::B2DVector aWidthOffset( double( nWidth ) / 2 * aPerpendicular); + basegfx::B2DPolygon aDashPolygon; + aDashPolygon.append( aStart + aWidthOffset ); + aDashPolygon.append( aEnd + aWidthOffset ); + aDashPolygon.append( aEnd - aWidthOffset ); + aDashPolygon.append( aStart - aWidthOffset ); + aDashPolygon.setClosed( true ); + + rDev.DrawPolygon( aDashPolygon ); + } + + rDev.SetAntialiasing( nOldAA ); +} + +namespace svtools { + +/** + * Dashing array must start with a line width and end with a blank width. + */ +static std::vector<double> GetDashing( SvxBorderLineStyle nDashing ) +{ + std::vector<double> aPattern; + switch (nDashing) + { + case SvxBorderLineStyle::DOTTED: + aPattern.push_back( 1.0 ); // line + aPattern.push_back( 2.0 ); // blank + break; + case SvxBorderLineStyle::DASHED: + aPattern.push_back( 16.0 ); // line + aPattern.push_back( 5.0 ); // blank + break; + case SvxBorderLineStyle::FINE_DASHED: + aPattern.push_back( 6.0 ); // line + aPattern.push_back( 2.0 ); // blank + break; + case SvxBorderLineStyle::DASH_DOT: + aPattern.push_back( 16.0 ); // line + aPattern.push_back( 5.0 ); // blank + aPattern.push_back( 5.0 ); // line + aPattern.push_back( 5.0 ); // blank + break; + case SvxBorderLineStyle::DASH_DOT_DOT: + aPattern.push_back( 16.0 ); // line + aPattern.push_back( 5.0 ); // blank + aPattern.push_back( 5.0 ); // line + aPattern.push_back( 5.0 ); // blank + aPattern.push_back( 5.0 ); // line + aPattern.push_back( 5.0 ); // blank + break; + default: + ; + } + + return aPattern; +} + +namespace { + +class ApplyScale +{ + double mfScale; +public: + explicit ApplyScale( double fScale ) : mfScale(fScale) {} + void operator() ( double& rVal ) + { + rVal *= mfScale; + } +}; + +} + +std::vector<double> GetLineDashing( SvxBorderLineStyle nDashing, double fScale ) +{ + std::vector<double> aPattern = GetDashing(nDashing); + std::for_each(aPattern.begin(), aPattern.end(), ApplyScale(fScale)); + return aPattern; +} + +basegfx::B2DPolyPolygon ApplyLineDashing( const basegfx::B2DPolygon& rPolygon, SvxBorderLineStyle nDashing, double fScale ) +{ + std::vector<double> aPattern = GetDashing(nDashing); + std::for_each(aPattern.begin(), aPattern.end(), ApplyScale(fScale)); + + basegfx::B2DPolyPolygon aPolygons; + + if (aPattern.empty()) + aPolygons.append(rPolygon); + else + basegfx::utils::applyLineDashing(rPolygon, aPattern, &aPolygons); + + return aPolygons; +} + +void DrawLine( OutputDevice& rDev, const Point& rP1, const Point& rP2, + sal_uInt32 nWidth, SvxBorderLineStyle nDashing ) +{ + DrawLine( rDev, basegfx::B2DPoint( rP1.X(), rP1.Y() ), + basegfx::B2DPoint( rP2.X(), rP2.Y( ) ), nWidth, nDashing ); +} + +void DrawLine( OutputDevice& rDev, const basegfx::B2DPoint& rP1, const basegfx::B2DPoint& rP2, + sal_uInt32 nWidth, SvxBorderLineStyle nDashing ) +{ + basegfx::B2DPolygon aPolygon; + aPolygon.append( rP1 ); + aPolygon.append( rP2 ); + lclDrawPolygon( rDev, aPolygon, nWidth, nDashing ); +} + +} + +static Size gUserItemSz; +static int gFontNameBoxes; +static size_t gPreviewsPerDevice; +static std::vector<VclPtr<VirtualDevice>> gFontPreviewVirDevs; +static std::vector<OUString> gRenderedFontNames; + +namespace +{ + void calcCustomItemSize(const weld::ComboBox& rComboBox) + { + gUserItemSz = Size(rComboBox.get_approximate_digit_width() * 52, rComboBox.get_text_height()); + gUserItemSz.setHeight(gUserItemSz.Height() * 16); + gUserItemSz.setHeight(gUserItemSz.Height() / 10); + + size_t nMaxDeviceHeight = SAL_MAX_INT16 / 16; // see limitXCreatePixmap and be generous wrt up to x16 hidpi + assert(gUserItemSz.Height() != 0); + gPreviewsPerDevice = gUserItemSz.Height() == 0 ? 16 : nMaxDeviceHeight / gUserItemSz.Height(); + } +} + +IMPL_LINK(FontNameBox, SettingsChangedHdl, VclSimpleEvent&, rEvent, void) +{ + if (rEvent.GetId() != VclEventId::ApplicationDataChanged) + return; + + DataChangedEvent* pData = static_cast<DataChangedEvent*>(static_cast<VclWindowEvent&>(rEvent).GetData()); + if (pData->GetType() == DataChangedEventType::SETTINGS) + { + gFontPreviewVirDevs.clear(); + gRenderedFontNames.clear(); + calcCustomItemSize(*m_xComboBox); + if (mbWYSIWYG && mpFontList && !mpFontList->empty()) + { + mnPreviewProgress = 0; + maUpdateIdle.Start(); + } + } +} + +FontNameBox::FontNameBox(std::unique_ptr<weld::ComboBox> p) + : m_xComboBox(std::move(p)) + , mnPreviewProgress(0) + , mbWYSIWYG(false) + , maUpdateIdle("FontNameBox Preview Update") +{ + ++gFontNameBoxes; + InitFontMRUEntriesFile(); + + maUpdateIdle.SetPriority(TaskPriority::LOWEST); + maUpdateIdle.SetInvokeHandler(LINK(this, FontNameBox, UpdateHdl)); + + Application::AddEventListener(LINK(this, FontNameBox, SettingsChangedHdl)); +} + +FontNameBox::~FontNameBox() +{ + Application::RemoveEventListener(LINK(this, FontNameBox, SettingsChangedHdl)); + + if (mpFontList) + { + SaveMRUEntries (maFontMRUEntriesFile); + ImplDestroyFontList(); + } + --gFontNameBoxes; + if (!gFontNameBoxes) + { + for (auto &rDev : gFontPreviewVirDevs) + rDev.disposeAndClear(); + gFontPreviewVirDevs.clear(); + gRenderedFontNames.clear(); + } +} + +void FontNameBox::SaveMRUEntries(const OUString& aFontMRUEntriesFile) const +{ + OString aEntries(OUStringToOString(m_xComboBox->get_mru_entries(), + RTL_TEXTENCODING_UTF8)); + + if (aEntries.isEmpty() || aFontMRUEntriesFile.isEmpty()) + return; + + SvFileStream aStream; + aStream.Open( aFontMRUEntriesFile, StreamMode::WRITE | StreamMode::TRUNC ); + if( ! (aStream.IsOpen() && aStream.IsWritable()) ) + { + SAL_INFO("svtools.control", "FontNameBox::SaveMRUEntries: opening mru entries file " << aFontMRUEntriesFile << " failed"); + return; + } + + aStream.SetLineDelimiter( LINEEND_LF ); + aStream.WriteLine( aEntries ); + aStream.WriteLine( "" ); +} + +void FontNameBox::LoadMRUEntries( const OUString& aFontMRUEntriesFile ) +{ + if (aFontMRUEntriesFile.isEmpty()) + return; + + if (!officecfg::Office::Common::Font::View::ShowFontBoxWYSIWYG::get()) + return; + + SvFileStream aStream( aFontMRUEntriesFile, StreamMode::READ ); + if( ! aStream.IsOpen() ) + { + SAL_INFO("svtools.control", "FontNameBox::LoadMRUEntries: opening mru entries file " << aFontMRUEntriesFile << " failed"); + return; + } + + OString aLine; + aStream.ReadLine( aLine ); + OUString aEntries = OStringToOUString(aLine, + RTL_TEXTENCODING_UTF8); + m_xComboBox->set_mru_entries(aEntries); +} + +void FontNameBox::InitFontMRUEntriesFile() +{ + OUString sUserConfigDir("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"); + rtl::Bootstrap::expandMacros(sUserConfigDir); + + maFontMRUEntriesFile = sUserConfigDir; + if( !maFontMRUEntriesFile.isEmpty() ) + { + maFontMRUEntriesFile += FONTNAMEBOXMRUENTRIESFILE; + } +} + +void FontNameBox::ImplDestroyFontList() +{ + mpFontList.reset(); + mnPreviewProgress = 0; + maUpdateIdle.Stop(); +} + +void FontNameBox::Fill( const FontList* pList ) +{ + // store old text and clear box + OUString aOldText = m_xComboBox->get_active_text(); + OUString rEntries = m_xComboBox->get_mru_entries(); + bool bLoadFromFile = rEntries.isEmpty(); + m_xComboBox->freeze(); + m_xComboBox->clear(); + + ImplDestroyFontList(); + mpFontList.reset(new ImplFontList); + + // insert fonts + size_t nFontCount = pList->GetFontNameCount(); + for (size_t i = 0; i < nFontCount; ++i) + { + const FontMetric& rFontMetric = pList->GetFontName(i); + m_xComboBox->append(OUString::number(i), rFontMetric.GetFamilyName()); + mpFontList->push_back(rFontMetric); + } + + if (bLoadFromFile) + LoadMRUEntries(maFontMRUEntriesFile); + else + m_xComboBox->set_mru_entries(rEntries); + + m_xComboBox->thaw(); + + if (mbWYSIWYG && nFontCount) + { + assert(mnPreviewProgress == 0 && "ImplDestroyFontList wasn't called"); + maUpdateIdle.Start(); + } + + // restore text + if (!aOldText.isEmpty()) + set_active_or_entry_text(aOldText); +} + +void FontNameBox::EnableWYSIWYG(bool bEnable) +{ + if (comphelper::LibreOfficeKit::isActive()) + return; + if (mbWYSIWYG == bEnable) + return; + mbWYSIWYG = bEnable; + + if (mbWYSIWYG) + { + calcCustomItemSize(*m_xComboBox); + m_xComboBox->connect_custom_get_size(LINK(this, FontNameBox, CustomGetSizeHdl)); + m_xComboBox->connect_custom_render(LINK(this, FontNameBox, CustomRenderHdl)); + } + else + { + m_xComboBox->connect_custom_get_size(Link<OutputDevice&, Size>()); + m_xComboBox->connect_custom_render(Link<weld::ComboBox::render_args, void>()); + } + m_xComboBox->set_custom_renderer(mbWYSIWYG); +} + +IMPL_LINK_NOARG(FontNameBox, CustomGetSizeHdl, OutputDevice&, Size) +{ + return mbWYSIWYG ? gUserItemSz : Size(); +} + +namespace +{ + tools::Long shrinkFontToFit(OUString const &rSampleText, tools::Long nH, vcl::Font &rFont, OutputDevice &rDevice, tools::Rectangle &rTextRect) + { + tools::Long nWidth = 0; + + Size aSize( rFont.GetFontSize() ); + + //Make sure it fits in the available height + while (aSize.Height() > 0) + { + if (!rDevice.GetTextBoundRect(rTextRect, rSampleText)) + break; + if (rTextRect.GetHeight() <= nH) + { + nWidth = rTextRect.GetWidth(); + break; + } + + aSize.AdjustHeight( -(EXTRAFONTSIZE) ); + rFont.SetFontSize(aSize); + rDevice.SetFont(rFont); + } + + return nWidth; + } +} + +IMPL_LINK_NOARG(FontNameBox, UpdateHdl, Timer*, void) +{ + CachePreview(mnPreviewProgress++, nullptr); + // tdf#132536 limit to ~25 pre-rendered for now. The font caches look + // b0rked, the massive charmaps are ~never swapped out, and don't count + // towards the size of a font in the font cache. + if (mnPreviewProgress < std::min<size_t>(25, mpFontList->size())) + maUpdateIdle.Start(); +} + +static void DrawPreview(const FontMetric& rFontMetric, const Point& rTopLeft, OutputDevice& rDevice, bool bSelected) +{ + rDevice.Push(vcl::PushFlags::TEXTCOLOR); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + if (bSelected) + rDevice.SetTextColor(rStyleSettings.GetHighlightTextColor()); + else + rDevice.SetTextColor(rStyleSettings.GetDialogTextColor()); + + tools::Long nX = rTopLeft.X(); + tools::Long nH = gUserItemSz.Height(); + + nX += IMGOUTERTEXTSPACE; + + const bool bSymbolFont = isSymbolFont(rFontMetric); + + vcl::Font aOldFont(rDevice.GetFont()); + Size aSize( aOldFont.GetFontSize() ); + aSize.AdjustHeight(EXTRAFONTSIZE ); + vcl::Font aFont( rFontMetric ); + aFont.SetFontSize( aSize ); + rDevice.SetFont(aFont); + + bool bUsingCorrectFont = true; + tools::Rectangle aTextRect; + + // Preview the font name + const OUString& sFontName = rFontMetric.GetFamilyName(); + + //If it shouldn't or can't draw its own name because it doesn't have the glyphs + if (!canRenderNameOfSelectedFont(rDevice)) + bUsingCorrectFont = false; + else + { + //Make sure it fits in the available height, shrinking the font if necessary + bUsingCorrectFont = shrinkFontToFit(sFontName, nH, aFont, rDevice, aTextRect) != 0; + } + + if (!bUsingCorrectFont) + { + rDevice.SetFont(aOldFont); + rDevice.GetTextBoundRect(aTextRect, sFontName); + } + + tools::Long nTextHeight = aTextRect.GetHeight(); + tools::Long nDesiredGap = (nH-nTextHeight)/2; + tools::Long nVertAdjust = nDesiredGap - aTextRect.Top(); + Point aPos( nX, rTopLeft.Y() + nVertAdjust ); + rDevice.DrawText(aPos, sFontName); + tools::Long nTextX = aPos.X() + aTextRect.GetWidth() + GAPTOEXTRAPREVIEW; + + if (!bUsingCorrectFont) + rDevice.SetFont(aFont); + + OUString sSampleText; + + if (!bSymbolFont) + { + const bool bNameBeginsWithLatinText = rFontMetric.GetFamilyName()[0] <= 'z'; + + if (bNameBeginsWithLatinText || !bUsingCorrectFont) + sSampleText = makeShortRepresentativeTextForSelectedFont(rDevice); + } + + //If we're not a symbol font, but could neither render our own name and + //we can't determine what script it would like to render, then try a + //few well known scripts + if (sSampleText.isEmpty() && !bUsingCorrectFont) + { + static const UScriptCode aScripts[] = + { + USCRIPT_ARABIC, + USCRIPT_HEBREW, + + USCRIPT_BENGALI, + USCRIPT_GURMUKHI, + USCRIPT_GUJARATI, + USCRIPT_ORIYA, + USCRIPT_TAMIL, + USCRIPT_TELUGU, + USCRIPT_KANNADA, + USCRIPT_MALAYALAM, + USCRIPT_SINHALA, + USCRIPT_DEVANAGARI, + + USCRIPT_THAI, + USCRIPT_LAO, + USCRIPT_GEORGIAN, + USCRIPT_TIBETAN, + USCRIPT_SYRIAC, + USCRIPT_MYANMAR, + USCRIPT_ETHIOPIC, + USCRIPT_KHMER, + USCRIPT_MONGOLIAN, + + USCRIPT_KOREAN, + USCRIPT_JAPANESE, + USCRIPT_HAN, + USCRIPT_SIMPLIFIED_HAN, + USCRIPT_TRADITIONAL_HAN, + + USCRIPT_GREEK + }; + + for (const UScriptCode& rScript : aScripts) + { + OUString sText = makeShortRepresentativeTextForScript(rScript); + if (!sText.isEmpty()) + { + bool bHasSampleTextGlyphs = (-1 == rDevice.HasGlyphs(aFont, sText)); + if (bHasSampleTextGlyphs) + { + sSampleText = sText; + break; + } + } + } + + static const UScriptCode aMinimalScripts[] = + { + USCRIPT_HEBREW, //e.g. biblical hebrew + USCRIPT_GREEK + }; + + for (const UScriptCode& rMinimalScript : aMinimalScripts) + { + OUString sText = makeShortMinimalTextForScript(rMinimalScript); + if (!sText.isEmpty()) + { + bool bHasSampleTextGlyphs = (-1 == rDevice.HasGlyphs(aFont, sText)); + if (bHasSampleTextGlyphs) + { + sSampleText = sText; + break; + } + } + } + } + + //If we're a symbol font, or for some reason the font still couldn't + //render something representative of what it would like to render then + //make up some semi-random text that it *can* display + if (bSymbolFont || (!bUsingCorrectFont && sSampleText.isEmpty())) + sSampleText = makeShortRepresentativeSymbolTextForSelectedFont(rDevice); + + if (!sSampleText.isEmpty()) + { + const Size &rItemSize = gUserItemSz; + + //leave a little border at the edge + tools::Long nSpace = rItemSize.Width() - nTextX - IMGOUTERTEXTSPACE; + if (nSpace >= 0) + { + //Make sure it fits in the available height, and get how wide that would be + tools::Long nWidth = shrinkFontToFit(sSampleText, nH, aFont, rDevice, aTextRect); + //Chop letters off until it fits in the available width + while (nWidth > nSpace || nWidth > gUserItemSz.Width()) + { + sSampleText = sSampleText.copy(0, sSampleText.getLength()-1); + nWidth = rDevice.GetTextBoundRect(aTextRect, sSampleText) ? + aTextRect.GetWidth() : 0; + } + + //center the text on the line + if (!sSampleText.isEmpty() && nWidth) + { + nTextHeight = aTextRect.GetHeight(); + nDesiredGap = (nH-nTextHeight)/2; + nVertAdjust = nDesiredGap - aTextRect.Top(); + aPos = Point(nTextX + nSpace - nWidth, rTopLeft.Y() + nVertAdjust); + rDevice.DrawText(aPos, sSampleText); + } + } + } + + rDevice.SetFont(aOldFont); + rDevice.Pop(); +} + +OutputDevice& FontNameBox::CachePreview(size_t nIndex, Point* pTopLeft) +{ + SolarMutexGuard aGuard; + const FontMetric& rFontMetric = (*mpFontList)[nIndex]; + const OUString& rFontName = rFontMetric.GetFamilyName(); + + size_t nPreviewIndex; + auto xFind = std::find(gRenderedFontNames.begin(), gRenderedFontNames.end(), rFontName); + bool bPreviewAvailable = xFind != gRenderedFontNames.end(); + if (!bPreviewAvailable) + { + nPreviewIndex = gRenderedFontNames.size(); + gRenderedFontNames.push_back(rFontName); + } + else + nPreviewIndex = std::distance(gRenderedFontNames.begin(), xFind); + + size_t nPage = nPreviewIndex / gPreviewsPerDevice; + size_t nIndexInPage = nPreviewIndex - (nPage * gPreviewsPerDevice); + + Point aTopLeft(0, gUserItemSz.Height() * nIndexInPage); + + if (!bPreviewAvailable) + { + if (nPage >= gFontPreviewVirDevs.size()) + { + gFontPreviewVirDevs.emplace_back(m_xComboBox->create_render_virtual_device()); + VirtualDevice& rDevice = *gFontPreviewVirDevs.back(); + rDevice.SetOutputSizePixel(Size(gUserItemSz.Width(), gUserItemSz.Height() * gPreviewsPerDevice)); + weld::SetPointFont(rDevice, m_xComboBox->get_font()); + assert(gFontPreviewVirDevs.size() == nPage + 1); + } + + DrawPreview(rFontMetric, aTopLeft, *gFontPreviewVirDevs.back(), false); + } + + if (pTopLeft) + *pTopLeft = aTopLeft; + + return *gFontPreviewVirDevs[nPage]; +} + +IMPL_LINK(FontNameBox, CustomRenderHdl, weld::ComboBox::render_args, aPayload, void) +{ + vcl::RenderContext& rRenderContext = std::get<0>(aPayload); + const ::tools::Rectangle& rRect = std::get<1>(aPayload); + bool bSelected = std::get<2>(aPayload); + const OUString& rId = std::get<3>(aPayload); + + sal_uInt32 nIndex = rId.toUInt32(); + + Point aDestPoint(rRect.TopLeft()); + auto nMargin = (rRect.GetHeight() - gUserItemSz.Height()) / 2; + aDestPoint.AdjustY(nMargin); + + if (bSelected) + { + const FontMetric& rFontMetric = (*mpFontList)[nIndex]; + DrawPreview(rFontMetric, aDestPoint, rRenderContext, true); + } + else + { + // use cache of unselected entries + Point aTopLeft; + OutputDevice& rDevice = CachePreview(nIndex, &aTopLeft); + + rRenderContext.DrawOutDev(aDestPoint, gUserItemSz, + aTopLeft, gUserItemSz, + rDevice); + } +} + +void FontNameBox::set_active_or_entry_text(const OUString& rText) +{ + const int nFound = m_xComboBox->find_text(rText); + if (nFound != -1) + m_xComboBox->set_active(nFound); + m_xComboBox->set_entry_text(rText); +} + +FontStyleBox::FontStyleBox(std::unique_ptr<weld::ComboBox> p) + : m_xComboBox(std::move(p)) +{ + //Use the standard texts to get an optimal size and stick to that size. + //That should stop the character dialog dancing around. + auto nMaxLen = m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_LIGHT)).Width(); + nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_LIGHT_ITALIC)).Width()); + nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_NORMAL)).Width()); + nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_NORMAL_ITALIC)).Width()); + nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BOLD)).Width()); + nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BOLD_ITALIC)).Width()); + nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BLACK)).Width()); + nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BLACK_ITALIC)).Width()); + m_xComboBox->set_entry_width_chars(std::ceil(nMaxLen / m_xComboBox->get_approximate_digit_width())); +} + +void FontStyleBox::Fill( std::u16string_view rName, const FontList* pList ) +{ + OUString aOldText = m_xComboBox->get_active_text(); + int nPos = m_xComboBox->get_active(); + + m_xComboBox->freeze(); + m_xComboBox->clear(); + + // does a font with this name already exist? + sal_Handle hFontMetric = pList->GetFirstFontMetric( rName ); + if ( hFontMetric ) + { + OUString aStyleText; + FontWeight eLastWeight = WEIGHT_DONTKNOW; + FontItalic eLastItalic = ITALIC_NONE; + FontWidth eLastWidth = WIDTH_DONTKNOW; + bool bNormal = false; + bool bItalic = false; + bool bBold = false; + bool bBoldItalic = false; + bool bInsert = false; + FontMetric aFontMetric; + while ( hFontMetric ) + { + aFontMetric = FontList::GetFontMetric( hFontMetric ); + + FontWeight eWeight = aFontMetric.GetWeight(); + FontItalic eItalic = aFontMetric.GetItalic(); + FontWidth eWidth = aFontMetric.GetWidthType(); + // Only if the attributes are different, we insert the + // Font to avoid double Entries in different languages + if ( (eWeight != eLastWeight) || (eItalic != eLastItalic) || + (eWidth != eLastWidth) ) + { + if ( bInsert ) + m_xComboBox->append_text(aStyleText); + + if ( eWeight <= WEIGHT_NORMAL ) + { + if ( eItalic != ITALIC_NONE ) + bItalic = true; + else + bNormal = true; + } + else + { + if ( eItalic != ITALIC_NONE ) + bBoldItalic = true; + else + bBold = true; + } + + // For wrong StyleNames we replace this with the correct once + aStyleText = pList->GetStyleName( aFontMetric ); + bInsert = m_xComboBox->find_text(aStyleText) == -1; + if ( !bInsert ) + { + aStyleText = pList->GetStyleName( eWeight, eItalic ); + bInsert = m_xComboBox->find_text(aStyleText) == -1; + } + + eLastWeight = eWeight; + eLastItalic = eItalic; + eLastWidth = eWidth; + } + else + { + if ( bInsert ) + { + // If we have two names for the same attributes + // we prefer the translated standard names + const OUString& rAttrStyleText = pList->GetStyleName( eWeight, eItalic ); + if (rAttrStyleText != aStyleText) + { + OUString aTempStyleText = pList->GetStyleName( aFontMetric ); + if (rAttrStyleText == aTempStyleText) + aStyleText = rAttrStyleText; + bInsert = m_xComboBox->find_text(aStyleText) == -1; + } + } + } + + if ( !bItalic && (aStyleText == pList->GetItalicStr()) ) + bItalic = true; + else if ( !bBold && (aStyleText == pList->GetBoldStr()) ) + bBold = true; + else if ( !bBoldItalic && (aStyleText == pList->GetBoldItalicStr()) ) + bBoldItalic = true; + + hFontMetric = FontList::GetNextFontMetric( hFontMetric ); + } + + if ( bInsert ) + m_xComboBox->append_text(aStyleText); + + // certain style as copy + if ( bNormal ) + { + if ( !bItalic ) + m_xComboBox->append_text(pList->GetItalicStr()); + if ( !bBold ) + m_xComboBox->append_text(pList->GetBoldStr()); + } + if ( !bBoldItalic ) + { + if ( bNormal || bItalic || bBold ) + m_xComboBox->append_text(pList->GetBoldItalicStr()); + } + } + else + { + // insert standard styles if no font + m_xComboBox->append_text(pList->GetNormalStr()); + m_xComboBox->append_text(pList->GetItalicStr()); + m_xComboBox->append_text(pList->GetBoldStr()); + m_xComboBox->append_text(pList->GetBoldItalicStr()); + } + + m_xComboBox->thaw(); + + if (!aOldText.isEmpty()) + { + int nFound = m_xComboBox->find_text(aOldText); + if (nFound != -1) + m_xComboBox->set_active(nFound); + else + { + if (nPos >= m_xComboBox->get_count()) + m_xComboBox->set_active(0); + else + m_xComboBox->set_active(nPos); + } + } +} + +FontSizeBox::FontSizeBox(std::unique_ptr<weld::ComboBox> p) + : pFontList(nullptr) + , nSavedValue(0) + , nMin(20) + , nMax(9999) + , eUnit(FieldUnit::POINT) + , nDecimalDigits(1) + , nRelMin(0) + , nRelMax(0) + , nRelStep(0) + , nPtRelMin(0) + , nPtRelMax(0) + , nPtRelStep(0) + , bRelativeMode(false) + , bRelative(false) + , bPtRelative(false) + , bStdSize(false) + , m_xComboBox(std::move(p)) +{ + m_xComboBox->set_entry_width_chars(std::ceil(m_xComboBox->get_pixel_size(format_number(105)).Width() / + m_xComboBox->get_approximate_digit_width())); + m_xComboBox->connect_focus_out(LINK(this, FontSizeBox, ReformatHdl)); + m_xComboBox->connect_changed(LINK(this, FontSizeBox, ModifyHdl)); +} + +void FontSizeBox::set_active_or_entry_text(const OUString& rText) +{ + const int nFound = m_xComboBox->find_text(rText); + if (nFound != -1) + m_xComboBox->set_active(nFound); + m_xComboBox->set_entry_text(rText); +} + +IMPL_LINK(FontSizeBox, ReformatHdl, weld::Widget&, rWidget, void) +{ + FontSizeNames aFontSizeNames(Application::GetSettings().GetUILanguageTag().getLanguageType()); + if (!bRelativeMode || !aFontSizeNames.IsEmpty()) + { + if (aFontSizeNames.Name2Size(m_xComboBox->get_active_text()) != 0) + return; + } + + set_value(get_value()); + + m_aFocusOutHdl.Call(rWidget); +} + +IMPL_LINK(FontSizeBox, ModifyHdl, weld::ComboBox&, rBox, void) +{ + if (bRelativeMode) + { + OUString aStr = comphelper::string::stripStart(rBox.get_active_text(), ' '); + + bool bNewMode = bRelative; + bool bOldPtRelMode = bPtRelative; + + if ( bRelative ) + { + bPtRelative = false; + const sal_Unicode* pStr = aStr.getStr(); + while ( *pStr ) + { + if ( ((*pStr < '0') || (*pStr > '9')) && (*pStr != '%') && !unicode::isSpace(*pStr) ) + { + if ( ('-' == *pStr || '+' == *pStr) && !bPtRelative ) + bPtRelative = true; + else if ( bPtRelative && 'p' == *pStr && 't' == *++pStr ) + ; + else + { + bNewMode = false; + break; + } + } + pStr++; + } + } + else if (!aStr.isEmpty()) + { + if ( -1 != aStr.indexOf('%') ) + { + bNewMode = true; + bPtRelative = false; + } + + if ( '-' == aStr[0] || '+' == aStr[0] ) + { + bNewMode = true; + bPtRelative = true; + } + } + + if ( bNewMode != bRelative || bPtRelative != bOldPtRelMode ) + SetRelative( bNewMode ); + } + m_aChangeHdl.Call(rBox); +} + +void FontSizeBox::Fill( const FontList* pList ) +{ + // remember for relative mode + pFontList = pList; + + // no font sizes need to be set for relative mode + if ( bRelative ) + return; + + // query font sizes + const int* pTempAry; + const int* pAry = nullptr; + + pAry = FontList::GetStdSizeAry(); + + // first insert font size names (for simplified/traditional chinese) + FontSizeNames aFontSizeNames( Application::GetSettings().GetUILanguageTag().getLanguageType() ); + if ( pAry == FontList::GetStdSizeAry() ) + { + // for standard sizes we don't need to bother + if (bStdSize && m_xComboBox->get_count() && aFontSizeNames.IsEmpty()) + return; + bStdSize = true; + } + else + bStdSize = false; + + int nSelectionStart, nSelectionEnd; + m_xComboBox->get_entry_selection_bounds(nSelectionStart, nSelectionEnd); + OUString aStr = m_xComboBox->get_active_text(); + + m_xComboBox->freeze(); + m_xComboBox->clear(); + int nPos = 0; + + if ( !aFontSizeNames.IsEmpty() ) + { + if ( pAry == FontList::GetStdSizeAry() ) + { + // for scalable fonts all font size names + sal_uLong nCount = aFontSizeNames.Count(); + for( sal_uLong i = 0; i < nCount; i++ ) + { + OUString aSizeName = aFontSizeNames.GetIndexName( i ); + int nSize = aFontSizeNames.GetIndexSize( i ); + OUString sId(OUString::number(-nSize)); // mark as special + m_xComboBox->insert(nPos, aSizeName, &sId, nullptr, nullptr); + nPos++; + } + } + else + { + // for fixed size fonts only selectable font size names + pTempAry = pAry; + while ( *pTempAry ) + { + OUString aSizeName = aFontSizeNames.Size2Name( *pTempAry ); + if ( !aSizeName.isEmpty() ) + { + OUString sId(OUString::number(-(*pTempAry))); // mark as special + m_xComboBox->insert(nPos, aSizeName, &sId, nullptr, nullptr); + nPos++; + } + pTempAry++; + } + } + } + + // then insert numerical font size values + pTempAry = pAry; + while (*pTempAry) + { + InsertValue(*pTempAry); + ++pTempAry; + } + + set_active_or_entry_text(aStr); + m_xComboBox->select_entry_region(nSelectionStart, nSelectionEnd); + m_xComboBox->thaw(); +} + +void FontSizeBox::EnableRelativeMode( sal_uInt16 nNewMin, sal_uInt16 nNewMax, sal_uInt16 nStep ) +{ + bRelativeMode = true; + nRelMin = nNewMin; + nRelMax = nNewMax; + nRelStep = nStep; + SetUnit(FieldUnit::POINT); +} + +void FontSizeBox::EnablePtRelativeMode( short nNewMin, short nNewMax, short nStep ) +{ + bRelativeMode = true; + nPtRelMin = nNewMin; + nPtRelMax = nNewMax; + nPtRelStep = nStep; + SetUnit(FieldUnit::POINT); +} + +void FontSizeBox::InsertValue(int i) +{ + OUString sNumber(OUString::number(i)); + m_xComboBox->append(sNumber, format_number(i)); +} + +void FontSizeBox::SetRelative( bool bNewRelative ) +{ + if ( !bRelativeMode ) + return; + + int nSelectionStart, nSelectionEnd; + m_xComboBox->get_entry_selection_bounds(nSelectionStart, nSelectionEnd); + OUString aStr = comphelper::string::stripStart(m_xComboBox->get_active_text(), ' '); + + if (bNewRelative) + { + bRelative = true; + bStdSize = false; + + m_xComboBox->clear(); + + if (bPtRelative) + { + SetDecimalDigits( 1 ); + SetRange(nPtRelMin, nPtRelMax); + SetUnit(FieldUnit::POINT); + + short i = nPtRelMin, n = 0; + // JP 30.06.98: more than 100 values are not useful + while ( i <= nPtRelMax && n++ < 100 ) + { + InsertValue( i ); + i = i + nPtRelStep; + } + } + else + { + SetDecimalDigits(0); + SetRange(nRelMin, nRelMax); + SetUnit(FieldUnit::PERCENT); + + sal_uInt16 i = nRelMin; + while ( i <= nRelMax ) + { + InsertValue( i ); + i = i + nRelStep; + } + } + } + else + { + if (pFontList) + m_xComboBox->clear(); + bRelative = bPtRelative = false; + SetDecimalDigits(1); + SetRange(20, 9999); + SetUnit(FieldUnit::POINT); + if ( pFontList) + Fill( pFontList ); + } + + set_active_or_entry_text(aStr); + m_xComboBox->select_entry_region(nSelectionStart, nSelectionEnd); +} + +OUString FontSizeBox::format_number(int nValue) const +{ + OUString sRet; + + //pawn percent off to icu to decide whether percent is separated from its number for this locale + if (eUnit == FieldUnit::PERCENT) + { + double fValue = nValue; + fValue /= weld::SpinButton::Power10(nDecimalDigits); + sRet = unicode::formatPercent(fValue, Application::GetSettings().GetUILanguageTag()); + } + else + { + const SvtSysLocale aSysLocale; + const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData(); + sRet = rLocaleData.getNum(nValue, nDecimalDigits, true, false); + if (eUnit != FieldUnit::NONE && eUnit != FieldUnit::DEGREE) + sRet += " "; + assert(eUnit != FieldUnit::PERCENT); + sRet += weld::MetricSpinButton::MetricToString(eUnit); + } + + if (bRelativeMode && bPtRelative && (0 <= nValue) && !sRet.isEmpty()) + sRet = "+" + sRet; + + return sRet; +} + +void FontSizeBox::SetValue(int nNewValue, FieldUnit eInUnit) +{ + auto nTempValue = vcl::ConvertValue(nNewValue, 0, GetDecimalDigits(), eInUnit, GetUnit()); + if (nTempValue < nMin) + nTempValue = nMin; + else if (nTempValue > nMax) + nTempValue = nMax; + if (!bRelative) + { + FontSizeNames aFontSizeNames(Application::GetSettings().GetUILanguageTag().getLanguageType()); + // conversion loses precision; however font sizes should + // never have a problem with that + OUString aName = aFontSizeNames.Size2Name(nTempValue); + if (!aName.isEmpty() && m_xComboBox->find_text(aName) != -1) + { + m_xComboBox->set_active_text(aName); + return; + } + } + OUString aResult = format_number(nTempValue); + set_active_or_entry_text(aResult); +} + +void FontSizeBox::set_value(int nNewValue) +{ + SetValue(nNewValue, eUnit); +} + +int FontSizeBox::get_value() const +{ + OUString aStr = m_xComboBox->get_active_text(); + if (!bRelative) + { + FontSizeNames aFontSizeNames(Application::GetSettings().GetUILanguageTag().getLanguageType()); + auto nValue = aFontSizeNames.Name2Size(aStr); + if (nValue) + return vcl::ConvertValue(nValue, 0, GetDecimalDigits(), GetUnit(), GetUnit()); + } + + const SvtSysLocale aSysLocale; + const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData(); + double fResult(0.0); + (void)vcl::TextToValue(aStr, fResult, 0, GetDecimalDigits(), rLocaleData, GetUnit()); + if (!aStr.isEmpty()) + { + if (fResult < nMin) + fResult = nMin; + else if (fResult > nMax) + fResult = nMax; + } + return fResult; +} + +SvxBorderLineStyle SvtLineListBox::GetSelectEntryStyle() const +{ + if (m_xLineSet->IsNoSelection()) + return SvxBorderLineStyle::NONE; + auto nId = m_xLineSet->GetSelectedItemId(); + return static_cast<SvxBorderLineStyle>(nId - 1); +} + +namespace +{ + Size getPreviewSize(const weld::Widget& rControl) + { + return Size(rControl.get_approximate_digit_width() * 15, rControl.get_text_height()); + } +} + +void SvtLineListBox::ImpGetLine( tools::Long nLine1, tools::Long nLine2, tools::Long nDistance, + Color aColor1, Color aColor2, Color aColorDist, + SvxBorderLineStyle nStyle, BitmapEx& rBmp ) +{ + Size aSize(getPreviewSize(*m_xControl)); + + // SourceUnit to Twips + if ( eSourceUnit == FieldUnit::POINT ) + { + nLine1 /= 5; + nLine2 /= 5; + nDistance /= 5; + } + + // Paint the lines + aSize = aVirDev->PixelToLogic( aSize ); + tools::Long nPix = aVirDev->PixelToLogic( Size( 0, 1 ) ).Height(); + sal_uInt32 n1 = nLine1; + sal_uInt32 n2 = nLine2; + tools::Long nDist = nDistance; + n1 += nPix-1; + n1 -= n1%nPix; + if ( n2 ) + { + nDist += nPix-1; + nDist -= nDist%nPix; + n2 += nPix-1; + n2 -= n2%nPix; + } + tools::Long nVirHeight = n1+nDist+n2; + if ( nVirHeight > aSize.Height() ) + aSize.setHeight( nVirHeight ); + // negative width should not be drawn + if ( aSize.Width() <= 0 ) + return; + + Size aVirSize = aVirDev->LogicToPixel( aSize ); + if ( aVirDev->GetOutputSizePixel() != aVirSize ) + aVirDev->SetOutputSizePixel( aVirSize ); + aVirDev->SetFillColor( aColorDist ); + aVirDev->DrawRect( tools::Rectangle( Point(), aSize ) ); + + aVirDev->SetFillColor( aColor1 ); + + double y1 = double( n1 ) / 2; + svtools::DrawLine( *aVirDev, basegfx::B2DPoint( 0, y1 ), basegfx::B2DPoint( aSize.Width( ), y1 ), n1, nStyle ); + + if ( n2 ) + { + double y2 = n1 + nDist + double( n2 ) / 2; + aVirDev->SetFillColor( aColor2 ); + svtools::DrawLine( *aVirDev, basegfx::B2DPoint( 0, y2 ), basegfx::B2DPoint( aSize.Width(), y2 ), n2, SvxBorderLineStyle::SOLID ); + } + rBmp = aVirDev->GetBitmapEx( Point(), Size( aSize.Width(), n1+nDist+n2 ) ); +} + +SvtLineListBox::SvtLineListBox(std::unique_ptr<weld::MenuButton> pControl) + : m_xControl(std::move(pControl)) + , m_xBuilder(Application::CreateBuilder(m_xControl.get(), "svt/ui/linewindow.ui")) + , m_xTopLevel(m_xBuilder->weld_widget("line_popup_window")) + , m_xNoneButton(m_xBuilder->weld_button("none_line_button")) + , m_xLineSet(new ValueSet(nullptr)) + , m_xLineSetWin(new weld::CustomWeld(*m_xBuilder, "lineset", *m_xLineSet)) + , m_nWidth( 5 ) + , aVirDev(VclPtr<VirtualDevice>::Create()) + , aColor(COL_BLACK) + , maPaintCol(COL_BLACK) +{ + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + m_xLineSet->SetStyle(WinBits(WB_FLATVALUESET | WB_NO_DIRECTSELECT | WB_TABSTOP)); + m_xLineSet->SetItemHeight(rStyleSettings.GetListBoxPreviewDefaultPixelSize().Height() + 1); + m_xLineSet->SetColCount(1); + m_xLineSet->SetSelectHdl(LINK(this, SvtLineListBox, ValueSelectHdl)); + + m_xNoneButton->connect_clicked(LINK(this, SvtLineListBox, NoneHdl)); + + m_xTopLevel->connect_focus_in(LINK(this, SvtLineListBox, FocusHdl)); + m_xControl->set_popover(m_xTopLevel.get()); + m_xControl->connect_toggled(LINK(this, SvtLineListBox, ToggleHdl)); + + // lock size to these maxes height/width so it doesn't jump around in size + m_xControl->set_label(GetLineStyleName(SvxBorderLineStyle::NONE)); + Size aNonePrefSize = m_xControl->get_preferred_size(); + m_xControl->set_label(""); + aVirDev->SetOutputSizePixel(getPreviewSize(*m_xControl)); + m_xControl->set_image(aVirDev); + Size aSolidPrefSize = m_xControl->get_preferred_size(); + m_xControl->set_size_request(std::max(aNonePrefSize.Width(), aSolidPrefSize.Width()), + std::max(aNonePrefSize.Height(), aSolidPrefSize.Height())); + + eSourceUnit = FieldUnit::POINT; + + aVirDev->SetLineColor(); + aVirDev->SetMapMode(MapMode(MapUnit::MapTwip)); + + UpdatePaintLineColor(); +} + +IMPL_LINK_NOARG(SvtLineListBox, FocusHdl, weld::Widget&, void) +{ + if (GetSelectEntryStyle() == SvxBorderLineStyle::NONE) + m_xNoneButton->grab_focus(); + else + m_xLineSet->GrabFocus(); +} + +IMPL_LINK(SvtLineListBox, ToggleHdl, weld::Toggleable&, rButton, void) +{ + if (rButton.get_active()) + FocusHdl(*m_xTopLevel); +} + +IMPL_LINK_NOARG(SvtLineListBox, NoneHdl, weld::Button&, void) +{ + SelectEntry(SvxBorderLineStyle::NONE); + ValueSelectHdl(nullptr); +} + +SvtLineListBox::~SvtLineListBox() +{ +} + +OUString SvtLineListBox::GetLineStyleName(SvxBorderLineStyle eStyle) +{ + OUString sRet; + for (sal_uInt32 i = 0; i < SAL_N_ELEMENTS(RID_SVXSTR_BORDERLINE); ++i) + { + if (eStyle == RID_SVXSTR_BORDERLINE[i].second) + { + sRet = SvtResId(RID_SVXSTR_BORDERLINE[i].first); + break; + } + } + return sRet; +} + +sal_Int32 SvtLineListBox::GetStylePos( sal_Int32 nListPos ) const +{ + sal_Int32 nPos = -1; + --nListPos; + + sal_Int32 n = 0; + size_t i = 0; + size_t nCount = m_vLineList.size(); + while ( nPos == -1 && i < nCount ) + { + if ( nListPos == n ) + nPos = static_cast<sal_Int32>(i); + n++; + i++; + } + + return nPos; +} + +void SvtLineListBox::SelectEntry(SvxBorderLineStyle nStyle) +{ + if (nStyle == SvxBorderLineStyle::NONE) + m_xLineSet->SetNoSelection(); + else + m_xLineSet->SelectItem(static_cast<sal_Int16>(nStyle) + 1); + UpdatePreview(); +} + +void SvtLineListBox::InsertEntry( + const BorderWidthImpl& rWidthImpl, SvxBorderLineStyle nStyle, tools::Long nMinWidth, + ColorFunc pColor1Fn, ColorFunc pColor2Fn, ColorDistFunc pColorDistFn ) +{ + m_vLineList.emplace_back(new ImpLineListData( + rWidthImpl, nStyle, nMinWidth, pColor1Fn, pColor2Fn, pColorDistFn)); +} + +void SvtLineListBox::UpdatePaintLineColor() +{ + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + Color aNewCol(rSettings.GetWindowColor().IsDark() ? rSettings.GetLabelTextColor() : aColor); + + bool bRet = aNewCol != maPaintCol; + + if( bRet ) + maPaintCol = aNewCol; +} + +void SvtLineListBox::UpdateEntries() +{ + UpdatePaintLineColor( ); + + SvxBorderLineStyle eSelected = GetSelectEntryStyle(); + + // Remove the old entries + m_xLineSet->Clear(); + + // Add the new entries based on the defined width + + sal_uInt16 n = 0; + sal_uInt16 nCount = m_vLineList.size( ); + while ( n < nCount ) + { + auto& pData = m_vLineList[ n ]; + BitmapEx aBmp; + ImpGetLine( pData->GetLine1ForWidth( m_nWidth ), + pData->GetLine2ForWidth( m_nWidth ), + pData->GetDistForWidth( m_nWidth ), + GetColorLine1(m_xLineSet->GetItemCount()), + GetColorLine2(m_xLineSet->GetItemCount()), + GetColorDist(m_xLineSet->GetItemCount()), + pData->GetStyle(), aBmp ); + sal_Int16 nItemId = static_cast<sal_Int16>(pData->GetStyle()) + 1; + m_xLineSet->InsertItem(nItemId, Image(aBmp), GetLineStyleName(pData->GetStyle())); + if (pData->GetStyle() == eSelected) + m_xLineSet->SelectItem(nItemId); + n++; + } + + m_xLineSet->SetOptimalSize(); +} + +Color SvtLineListBox::GetColorLine1( sal_Int32 nPos ) +{ + sal_Int32 nStyle = GetStylePos( nPos ); + if (nStyle == -1) + return GetPaintColor( ); + auto& pData = m_vLineList[ nStyle ]; + return pData->GetColorLine1( GetColor( ) ); +} + +Color SvtLineListBox::GetColorLine2( sal_Int32 nPos ) +{ + sal_Int32 nStyle = GetStylePos(nPos); + if (nStyle == -1) + return GetPaintColor( ); + auto& pData = m_vLineList[ nStyle ]; + return pData->GetColorLine2( GetColor( ) ); +} + +Color SvtLineListBox::GetColorDist( sal_Int32 nPos ) +{ + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + Color rResult = rSettings.GetFieldColor(); + + sal_Int32 nStyle = GetStylePos( nPos ); + if (nStyle == -1) + return rResult; + auto& pData = m_vLineList[ nStyle ]; + return pData->GetColorDist( GetColor( ), rResult ); +} + +IMPL_LINK_NOARG(SvtLineListBox, ValueSelectHdl, ValueSet*, void) +{ + maSelectHdl.Call(*this); + UpdatePreview(); + if (m_xControl->get_active()) + m_xControl->set_active(false); +} + +void SvtLineListBox::UpdatePreview() +{ + SvxBorderLineStyle eStyle = GetSelectEntryStyle(); + for (sal_uInt32 i = 0; i < SAL_N_ELEMENTS(RID_SVXSTR_BORDERLINE); ++i) + { + if (eStyle == RID_SVXSTR_BORDERLINE[i].second) + { + m_xControl->set_label(SvtResId(RID_SVXSTR_BORDERLINE[i].first)); + break; + } + } + + if (eStyle == SvxBorderLineStyle::NONE) + { + m_xControl->set_image(nullptr); + m_xControl->set_label(GetLineStyleName(SvxBorderLineStyle::NONE)); + } + else + { + Image aImage(m_xLineSet->GetItemImage(m_xLineSet->GetSelectedItemId())); + m_xControl->set_label(""); + const auto nPos = (aVirDev->GetOutputSizePixel().Height() - aImage.GetSizePixel().Height()) / 2; + aVirDev->Push(vcl::PushFlags::MAPMODE); + aVirDev->SetMapMode(MapMode(MapUnit::MapPixel)); + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + aVirDev->SetBackground(rSettings.GetFieldColor()); + aVirDev->Erase(); + aVirDev->DrawImage(Point(0, nPos), aImage); + m_xControl->set_image(aVirDev.get()); + aVirDev->Pop(); + } +} + +SvtCalendarBox::SvtCalendarBox(std::unique_ptr<weld::MenuButton> pControl, bool bUseLabel) + : m_bUseLabel(bUseLabel) + , m_xControl(std::move(pControl)) + , m_xBuilder(Application::CreateBuilder(m_xControl.get(), "svt/ui/datewindow.ui")) + , m_xTopLevel(m_xBuilder->weld_widget("date_popup_window")) + , m_xCalendar(m_xBuilder->weld_calendar("date")) +{ + m_xControl->set_popover(m_xTopLevel.get()); + m_xCalendar->connect_selected(LINK(this, SvtCalendarBox, SelectHdl)); + m_xCalendar->connect_activated(LINK(this, SvtCalendarBox, ActivateHdl)); +} + +void SvtCalendarBox::set_date(const Date& rDate) +{ + m_xCalendar->set_date(rDate); + set_label_from_date(); +} + +void SvtCalendarBox::set_label_from_date() +{ + if (!m_bUseLabel) + return; + const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper(); + m_xControl->set_label(rLocaleData.getDate(m_xCalendar->get_date())); +} + +IMPL_LINK_NOARG(SvtCalendarBox, SelectHdl, weld::Calendar&, void) +{ + set_label_from_date(); + m_aSelectHdl.Call(*this); +} + +IMPL_LINK_NOARG(SvtCalendarBox, ActivateHdl, weld::Calendar&, void) +{ + if (m_xControl->get_active()) + m_xControl->set_active(false); + m_aActivatedHdl.Call(*this); +} + +SvtCalendarBox::~SvtCalendarBox() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/ctrltool.cxx b/svtools/source/control/ctrltool.cxx new file mode 100644 index 000000000..31c3c33b8 --- /dev/null +++ b/svtools/source/control/ctrltool.cxx @@ -0,0 +1,847 @@ +/* -*- 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 <memory> +#include <string.h> +#include <string_view> + +#include <tools/debug.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <sal/macros.h> +#include <svtools/strings.hrc> +#include <svtools/svtresid.hxx> +#include <svtools/ctrltool.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <o3tl/string_view.hxx> +#include <comphelper/lok.hxx> + +// Standard fontsizes for scalable Fonts +const int FontList::aStdSizeAry[] = +{ + 60, + 70, + 80, + 90, + 100, + 105, + 110, + 120, + 130, + 140, + 150, + 160, + 180, + 200, + 210, + 220, + 240, + 260, + 280, + 320, + 360, + 400, + 420, + 440, + 480, + 540, + 600, + 660, + 720, + 800, + 880, + 960, + 0 +}; + +namespace { + +class ImplFontListFontMetric : public FontMetric +{ + friend FontList; + +private: + ImplFontListFontMetric* mpNext; + +public: + ImplFontListFontMetric( const FontMetric& rInfo ) : + FontMetric( rInfo ), mpNext(nullptr) + { + } + +}; + +enum class FontListFontNameType +{ + NONE = 0x00, + PRINTER = 0x01, + SCREEN = 0x02, +}; + +} +namespace o3tl +{ + template<> struct typed_flags<FontListFontNameType> : is_typed_flags<FontListFontNameType, 0x3> {}; +} + +class ImplFontListNameInfo +{ + friend class FontList; + +private: + OUString maSearchName; + ImplFontListFontMetric* mpFirst; + FontListFontNameType mnType; + + explicit ImplFontListNameInfo(const OUString& rSearchName) + : maSearchName(rSearchName) + , mpFirst(nullptr) + , mnType(FontListFontNameType::NONE) + { + } +}; + +//sort normal to the start +static int sortWeightValue(FontWeight eWeight) +{ + if (eWeight < WEIGHT_NORMAL) + return eWeight + 1; + if (eWeight > WEIGHT_NORMAL) + return eWeight - 1; + return 0; // eWeight == WEIGHT_NORMAL +} + +static sal_Int32 ImplCompareFontMetric( ImplFontListFontMetric* pInfo1, + ImplFontListFontMetric* pInfo2 ) +{ + //Sort non italic before italics + if ( pInfo1->GetItalic() < pInfo2->GetItalic() ) + return -1; + else if ( pInfo1->GetItalic() > pInfo2->GetItalic() ) + return 1; + + //Sort normal weight to the start, followed by lightest to heaviest weights + int nWeight1 = sortWeightValue(pInfo1->GetWeight()); + int nWeight2 = sortWeightValue(pInfo2->GetWeight()); + + if ( nWeight1 < nWeight2 ) + return -1; + else if ( nWeight1 > nWeight2 ) + return 1; + + return pInfo1->GetStyleName().compareTo( pInfo2->GetStyleName() ); +} + +static OUString ImplMakeSearchString(const OUString& rStr) +{ + return rStr.toAsciiLowerCase(); +} + +static OUString ImplMakeSearchStringFromName(std::u16string_view rStr) +{ + // check for features before alternate font separator + if (size_t nColon = rStr.find(':'); nColon != std::u16string_view::npos) + if (size_t nSemiColon = rStr.find(';'); nSemiColon == std::u16string_view::npos || nColon < nSemiColon) + return ImplMakeSearchString(OUString(o3tl::getToken(rStr, 0, ':' ))); + return ImplMakeSearchString(OUString(o3tl::getToken(rStr, 0, ';' ))); +} + +ImplFontListNameInfo* FontList::ImplFind(std::u16string_view rSearchName, sal_uInt32* pIndex) const +{ + // Append if there is no entry in the list or if the entry is larger + // then the last one. We only compare to the last entry as the list of VCL + // is returned sorted, which increases the probability that appending + // is more likely + if (m_Entries.empty()) + { + if ( pIndex ) + *pIndex = SAL_MAX_UINT32; + return nullptr; + } + else + { + const ImplFontListNameInfo* pCmpData = m_Entries.back().get(); + sal_Int32 nComp = rSearchName.compare( pCmpData->maSearchName ); + if (nComp > 0) + { + if ( pIndex ) + *pIndex = SAL_MAX_UINT32; + return nullptr; + } + else if (nComp == 0) + return const_cast<ImplFontListNameInfo*>(pCmpData); + } + + // search fonts in the list + const ImplFontListNameInfo* pCompareData; + const ImplFontListNameInfo* pFoundData = nullptr; + size_t nLow = 0; + size_t nHigh = m_Entries.size() - 1; + size_t nMid; + + do + { + nMid = (nLow + nHigh) / 2; + pCompareData = m_Entries[nMid].get(); + sal_Int32 nComp = rSearchName.compare(pCompareData->maSearchName); + if (nComp < 0) + { + if ( !nMid ) + break; + nHigh = nMid-1; + } + else + { + if (nComp > 0) + nLow = nMid + 1; + else + { + pFoundData = pCompareData; + break; + } + } + } + while ( nLow <= nHigh ); + + if ( pIndex ) + { + sal_Int32 nComp = rSearchName.compare(pCompareData->maSearchName); + if (nComp > 0) + *pIndex = (nMid+1); + else + *pIndex = nMid; + } + + return const_cast<ImplFontListNameInfo*>(pFoundData); +} + +ImplFontListNameInfo* FontList::ImplFindByName(std::u16string_view rStr) const +{ + OUString aSearchName = ImplMakeSearchStringFromName(rStr); + return ImplFind( aSearchName, nullptr ); +} + +void FontList::ImplInsertFonts(OutputDevice* pDevice, bool bInsertData) +{ + rtl_TextEncoding eSystemEncoding = osl_getThreadTextEncoding(); + + FontListFontNameType nType; + if ( pDevice->GetOutDevType() != OUTDEV_PRINTER ) + nType = FontListFontNameType::SCREEN; + else + nType = FontListFontNameType::PRINTER; + + // inquire all fonts from the device + int n = pDevice->GetFontFaceCollectionCount(); + if (n == 0 && comphelper::LibreOfficeKit::isActive()) + { + pDevice->RefreshFontData(true); + n = pDevice->GetFontFaceCollectionCount(); + } + + for (int i = 0; i < n; ++i) + { + FontMetric aFontMetric = pDevice->GetFontMetricFromCollection( i ); + OUString aSearchName(aFontMetric.GetFamilyName()); + ImplFontListNameInfo* pData; + sal_uInt32 nIndex; + aSearchName = ImplMakeSearchString(aSearchName); + pData = ImplFind( aSearchName, &nIndex ); + + if ( !pData ) + { + if ( bInsertData ) + { + ImplFontListFontMetric* pNewInfo = new ImplFontListFontMetric( aFontMetric ); + pData = new ImplFontListNameInfo( aSearchName ); + pData->mpFirst = pNewInfo; + pNewInfo->mpNext = nullptr; + + if (nIndex < static_cast<sal_uInt32>(m_Entries.size())) + m_Entries.insert(m_Entries.begin()+nIndex, + std::unique_ptr<ImplFontListNameInfo>(pData)); + else + m_Entries.push_back(std::unique_ptr<ImplFontListNameInfo>(pData)); + } + } + else + { + if ( bInsertData ) + { + bool bInsert = true; + ImplFontListFontMetric* pPrev = nullptr; + ImplFontListFontMetric* pTemp = pData->mpFirst; + ImplFontListFontMetric* pNewInfo = new ImplFontListFontMetric( aFontMetric ); + while ( pTemp ) + { + sal_Int32 eComp = ImplCompareFontMetric( pNewInfo, pTemp ); + if ( eComp <= 0 ) + { + if ( eComp == 0 ) + { + // Overwrite charset, because charset should match + // with the system charset + if ( (pTemp->GetCharSet() != eSystemEncoding) && + (pNewInfo->GetCharSet() == eSystemEncoding) ) + { + ImplFontListFontMetric* pTemp2 = pTemp->mpNext; + *static_cast<FontMetric*>(pTemp) = *static_cast<FontMetric*>(pNewInfo); + pTemp->mpNext = pTemp2; + } + delete pNewInfo; + bInsert = false; + } + + break; + } + + pPrev = pTemp; + pTemp = pTemp->mpNext; + } + + if ( bInsert ) + { + pNewInfo->mpNext = pTemp; + if ( pPrev ) + pPrev->mpNext = pNewInfo; + else + pData->mpFirst = pNewInfo; + } + } + } + + if ( pData ) + pData->mnType |= nType; + } +} + +FontList::FontList(OutputDevice* pDevice, OutputDevice* pDevice2) +{ + // initialise variables + mpDev = pDevice; + mpDev2 = pDevice2; + + // store style names + maLight = SvtResId(STR_SVT_STYLE_LIGHT); + maLightItalic = SvtResId(STR_SVT_STYLE_LIGHT_ITALIC); + maNormal = SvtResId(STR_SVT_STYLE_NORMAL); + maNormalItalic = SvtResId(STR_SVT_STYLE_NORMAL_ITALIC); + maBold = SvtResId(STR_SVT_STYLE_BOLD); + maBoldItalic = SvtResId(STR_SVT_STYLE_BOLD_ITALIC); + maBlack = SvtResId(STR_SVT_STYLE_BLACK); + maBlackItalic = SvtResId(STR_SVT_STYLE_BLACK_ITALIC); + + ImplInsertFonts(pDevice, true); + + // if required compare to the screen fonts + // in order to map the duplicates to Equal + bool bCompareWindow = false; + if ( !pDevice2 && (pDevice->GetOutDevType() == OUTDEV_PRINTER) ) + { + bCompareWindow = true; + pDevice2 = Application::GetDefaultDevice(); + } + + if ( pDevice2 && + (pDevice2->GetOutDevType() != pDevice->GetOutDevType()) ) + ImplInsertFonts(pDevice2, !bCompareWindow); +} + +FontList::~FontList() +{ + // delete FontMetrics + ImplFontListFontMetric *pTemp, *pInfo; + for (auto const& it : m_Entries) + { + pInfo = it->mpFirst; + while ( pInfo ) + { + pTemp = pInfo->mpNext; + delete pInfo; + pInfo = pTemp; + } + } +} + +std::unique_ptr<FontList> FontList::Clone() const +{ + return std::unique_ptr<FontList>(new FontList(mpDev, mpDev2)); +} + +const OUString& FontList::GetStyleName(FontWeight eWeight, FontItalic eItalic) const +{ + if ( eWeight > WEIGHT_BOLD ) + { + if ( eItalic > ITALIC_NONE ) + return maBlackItalic; + else + return maBlack; + } + else if ( eWeight > WEIGHT_MEDIUM ) + { + if ( eItalic > ITALIC_NONE ) + return maBoldItalic; + else + return maBold; + } + else if ( eWeight > WEIGHT_LIGHT ) + { + if ( eItalic > ITALIC_NONE ) + return maNormalItalic; + else + return maNormal; + } + else if ( eWeight != WEIGHT_DONTKNOW ) + { + if ( eItalic > ITALIC_NONE ) + return maLightItalic; + else + return maLight; + } + else + { + if ( eItalic > ITALIC_NONE ) + return maNormalItalic; + else + return maNormal; + } +} + +OUString FontList::GetStyleName(const FontMetric& rInfo) const +{ + OUString aStyleName = rInfo.GetStyleName(); + FontWeight eWeight = rInfo.GetWeight(); + FontItalic eItalic = rInfo.GetItalic(); + + // return synthetic Name if no StyleName was set + if (aStyleName.isEmpty()) + aStyleName = GetStyleName(eWeight, eItalic); + else + { + // Translate StyleName to localized name + OUString aCompareStyleName = aStyleName.toAsciiLowerCase().replaceAll(" ", ""); + if (aCompareStyleName == "bold") + aStyleName = maBold; + else if (aCompareStyleName == "bolditalic") + aStyleName = maBoldItalic; + else if (aCompareStyleName == "italic") + aStyleName = maNormalItalic; + else if (aCompareStyleName == "standard") + aStyleName = maNormal; + else if (aCompareStyleName == "regular") + aStyleName = maNormal; + else if (aCompareStyleName == "medium") + aStyleName = maNormal; + else if (aCompareStyleName == "light") + aStyleName = maLight; + else if (aCompareStyleName == "lightitalic") + aStyleName = maLightItalic; + else if (aCompareStyleName == "black") + aStyleName = maBlack; + else if (aCompareStyleName == "blackitalic") + aStyleName = maBlackItalic; + /* tdf#107700 support some less common style names with localization */ + else if (aCompareStyleName == "book") + aStyleName = SvtResId(STR_SVT_STYLE_BOOK); + else if (aCompareStyleName == "boldoblique") + aStyleName = SvtResId(STR_SVT_STYLE_BOLD_OBLIQUE); + else if (aCompareStyleName == "condensed") + aStyleName = SvtResId(STR_SVT_STYLE_CONDENSED); + else if (aCompareStyleName == "condensedbold") + aStyleName = SvtResId(STR_SVT_STYLE_CONDENSED_BOLD); + else if (aCompareStyleName == "condensedbolditalic") + aStyleName = SvtResId(STR_SVT_STYLE_CONDENSED_BOLD_ITALIC); + else if (aCompareStyleName == "condensedboldoblique") + aStyleName = SvtResId(STR_SVT_STYLE_CONDENSED_BOLD_OBLIQUE); + else if (aCompareStyleName == "condenseditalic") + aStyleName = SvtResId(STR_SVT_STYLE_CONDENSED_ITALIC); + else if (aCompareStyleName == "condensedoblique") + aStyleName = SvtResId(STR_SVT_STYLE_CONDENSED_OBLIQUE); + else if (aCompareStyleName == "extralight") + aStyleName = SvtResId(STR_SVT_STYLE_EXTRALIGHT); + else if (aCompareStyleName == "extralightitalic") + aStyleName = SvtResId(STR_SVT_STYLE_EXTRALIGHT_ITALIC); + /* Medium is synonym with Normal */ + else if (aCompareStyleName == "mediumitalic") + aStyleName = maNormalItalic; + else if (aCompareStyleName == "oblique") + aStyleName = SvtResId(STR_SVT_STYLE_OBLIQUE); + else if (aCompareStyleName == "semibold") + aStyleName = SvtResId(STR_SVT_STYLE_SEMIBOLD); + else if (aCompareStyleName == "semibolditalic") + aStyleName = SvtResId(STR_SVT_STYLE_SEMIBOLD_ITALIC); + + // fix up StyleName, because the PS Printer driver from + // W2000 returns wrong StyleNames (e.g. Bold instead of Bold Italic + // for Helvetica) + if ( eItalic > ITALIC_NONE ) + { + if ( (aStyleName == maNormal) || + (aStyleName == maBold) || + (aStyleName == maLight) || + (aStyleName == maBlack) ) + aStyleName = GetStyleName( eWeight, eItalic ); + } + } + + return aStyleName; +} + +OUString FontList::GetFontMapText( const FontMetric& rInfo ) const +{ + if ( rInfo.GetFamilyName().isEmpty() ) + { + return OUString(); + } + + // Search Fontname + ImplFontListNameInfo* pData = ImplFindByName( rInfo.GetFamilyName() ); + if ( !pData ) + { + if (maMapNotAvailable.isEmpty()) + maMapNotAvailable = SvtResId(STR_SVT_FONTMAP_NOTAVAILABLE); + return maMapNotAvailable; + } + + // search for synthetic style + FontListFontNameType nType = pData->mnType; + const OUString& rStyleName = rInfo.GetStyleName(); + if (!rStyleName.isEmpty()) + { + bool bNotSynthetic = false; + FontWeight eWeight = rInfo.GetWeight(); + FontItalic eItalic = rInfo.GetItalic(); + ImplFontListFontMetric* pFontMetric = pData->mpFirst; + while ( pFontMetric ) + { + if ( (eWeight == pFontMetric->GetWeight()) && + (eItalic == pFontMetric->GetItalic()) ) + { + bNotSynthetic = true; + break; + } + + pFontMetric = pFontMetric->mpNext; + } + + if ( !bNotSynthetic ) + { + if (maMapStyleNotAvailable.isEmpty()) + const_cast<FontList*>(this)->maMapStyleNotAvailable = SvtResId(STR_SVT_FONTMAP_STYLENOTAVAILABLE); + return maMapStyleNotAvailable; + } + } + + // Only Printer-Font? + if ( nType == FontListFontNameType::PRINTER ) + { + if (maMapPrinterOnly.isEmpty()) + const_cast<FontList*>(this)->maMapPrinterOnly = SvtResId(STR_SVT_FONTMAP_PRINTERONLY); + return maMapPrinterOnly; + } + else + { + if (maMapBoth.isEmpty()) + const_cast<FontList*>(this)->maMapBoth = SvtResId(STR_SVT_FONTMAP_BOTH); + return maMapBoth; + } +} + +namespace +{ + FontMetric makeMissing(ImplFontListFontMetric const * pFontNameInfo, std::u16string_view rName, + FontWeight eWeight, FontItalic eItalic) + { + FontMetric aInfo; + // if the fontname matches, we copy as much as possible + if (pFontNameInfo) + { + aInfo = *pFontNameInfo; + aInfo.SetStyleName(OUString()); + } + + aInfo.SetWeight(eWeight); + aInfo.SetItalic(eItalic); + + //If this is a known but uninstalled symbol font which we can remap to + //OpenSymbol then toggle its charset to be a symbol font + if (ConvertChar::GetRecodeData(rName, u"OpenSymbol")) + aInfo.SetCharSet(RTL_TEXTENCODING_SYMBOL); + + return aInfo; + } +} + +FontMetric FontList::Get(const OUString& rName, const OUString& rStyleName) const +{ + ImplFontListNameInfo* pData = ImplFindByName( rName ); + ImplFontListFontMetric* pFontMetric = nullptr; + ImplFontListFontMetric* pFontNameInfo = nullptr; + if ( pData ) + { + ImplFontListFontMetric* pSearchInfo = pData->mpFirst; + pFontNameInfo = pSearchInfo; + pSearchInfo = pData->mpFirst; + while ( pSearchInfo ) + { + if (rStyleName.equalsIgnoreAsciiCase(GetStyleName(*pSearchInfo))) + { + pFontMetric = pSearchInfo; + break; + } + + pSearchInfo = pSearchInfo->mpNext; + } + } + + // reproduce attributes if data could not be found + FontMetric aInfo; + if ( !pFontMetric ) + { + FontWeight eWeight = WEIGHT_DONTKNOW; + FontItalic eItalic = ITALIC_NONE; + + if ( rStyleName == maNormal ) + { + eItalic = ITALIC_NONE; + eWeight = WEIGHT_NORMAL; + } + else if ( rStyleName == maNormalItalic ) + { + eItalic = ITALIC_NORMAL; + eWeight = WEIGHT_NORMAL; + } + else if ( rStyleName == maBold ) + { + eItalic = ITALIC_NONE; + eWeight = WEIGHT_BOLD; + } + else if ( rStyleName == maBoldItalic ) + { + eItalic = ITALIC_NORMAL; + eWeight = WEIGHT_BOLD; + } + else if ( rStyleName == maLight ) + { + eItalic = ITALIC_NONE; + eWeight = WEIGHT_LIGHT; + } + else if ( rStyleName == maLightItalic ) + { + eItalic = ITALIC_NORMAL; + eWeight = WEIGHT_LIGHT; + } + else if ( rStyleName == maBlack ) + { + eItalic = ITALIC_NONE; + eWeight = WEIGHT_BLACK; + } + else if ( rStyleName == maBlackItalic ) + { + eItalic = ITALIC_NORMAL; + eWeight = WEIGHT_BLACK; + } + aInfo = makeMissing(pFontNameInfo, rName, eWeight, eItalic); + } + else + aInfo = *pFontMetric; + + // set Fontname to keep FontAlias + aInfo.SetFamilyName( rName ); + aInfo.SetStyleName( rStyleName ); + + return aInfo; +} + +FontMetric FontList::Get(const OUString& rName, + FontWeight eWeight, FontItalic eItalic) const +{ + ImplFontListNameInfo* pData = ImplFindByName( rName ); + ImplFontListFontMetric* pFontMetric = nullptr; + ImplFontListFontMetric* pFontNameInfo = nullptr; + if ( pData ) + { + ImplFontListFontMetric* pSearchInfo = pData->mpFirst; + pFontNameInfo = pSearchInfo; + while ( pSearchInfo ) + { + if ( (eWeight == pSearchInfo->GetWeight()) && + (eItalic == pSearchInfo->GetItalic()) ) + { + pFontMetric = pSearchInfo; + break; + } + + pSearchInfo = pSearchInfo->mpNext; + } + } + + // reproduce attributes if data could not be found + FontMetric aInfo; + if ( !pFontMetric ) + aInfo = makeMissing(pFontNameInfo, rName, eWeight, eItalic); + else + aInfo = *pFontMetric; + + // set Fontname to keep FontAlias + aInfo.SetFamilyName( rName ); + + return aInfo; +} + +bool FontList::IsAvailable(std::u16string_view rName) const +{ + return (ImplFindByName( rName ) != nullptr); +} + +const FontMetric& FontList::GetFontName(size_t const nFont) const +{ + DBG_ASSERT( nFont < GetFontNameCount(), "FontList::GetFontName(): nFont >= Count" ); + + return *(m_Entries[nFont]->mpFirst); +} + +sal_Handle FontList::GetFirstFontMetric(std::u16string_view rName) const +{ + ImplFontListNameInfo* pData = ImplFindByName( rName ); + if ( !pData ) + return nullptr; + else + return static_cast<sal_Handle>(pData->mpFirst); +} + +sal_Handle FontList::GetNextFontMetric( sal_Handle hFontMetric ) +{ + ImplFontListFontMetric* pInfo = static_cast<ImplFontListFontMetric*>(hFontMetric); + return static_cast<sal_Handle>(pInfo->mpNext); +} + +const FontMetric& FontList::GetFontMetric( sal_Handle hFontMetric ) +{ + ImplFontListFontMetric* pInfo = static_cast<ImplFontListFontMetric*>(hFontMetric); + return *pInfo; +} + +struct ImplFSNameItem +{ + sal_Int32 mnSize; + const char* mszUtf8Name; +}; + +const ImplFSNameItem aImplSimplifiedChinese[] = +{ + { 50, "\xe5\x85\xab\xe5\x8f\xb7" }, + { 55, "\xe4\xb8\x83\xe5\x8f\xb7" }, + { 65, "\xe5\xb0\x8f\xe5\x85\xad" }, + { 75, "\xe5\x85\xad\xe5\x8f\xb7" }, + { 90, "\xe5\xb0\x8f\xe4\xba\x94" }, + { 105, "\xe4\xba\x94\xe5\x8f\xb7" }, + { 120, "\xe5\xb0\x8f\xe5\x9b\x9b" }, + { 140, "\xe5\x9b\x9b\xe5\x8f\xb7" }, + { 150, "\xe5\xb0\x8f\xe4\xb8\x89" }, + { 160, "\xe4\xb8\x89\xe5\x8f\xb7" }, + { 180, "\xe5\xb0\x8f\xe4\xba\x8c" }, + { 220, "\xe4\xba\x8c\xe5\x8f\xb7" }, + { 240, "\xe5\xb0\x8f\xe4\xb8\x80" }, + { 260, "\xe4\xb8\x80\xe5\x8f\xb7" }, + { 360, "\xe5\xb0\x8f\xe5\x88\x9d" }, + { 420, "\xe5\x88\x9d\xe5\x8f\xb7" } +}; + +FontSizeNames::FontSizeNames( LanguageType eLanguage ) +{ + if ( eLanguage == LANGUAGE_DONTKNOW ) + eLanguage = Application::GetSettings().GetUILanguageTag().getLanguageType(); + if ( eLanguage == LANGUAGE_SYSTEM ) + eLanguage = MsLangId::getConfiguredSystemUILanguage(); + + if (MsLangId::isSimplifiedChinese(eLanguage)) + { + // equivalent for traditional chinese disabled by popular request, #i89077# + mpArray = aImplSimplifiedChinese; + mnElem = SAL_N_ELEMENTS(aImplSimplifiedChinese); + } + else + { + mpArray = nullptr; + mnElem = 0; + } +} + +sal_Int32 FontSizeNames::Name2Size( std::u16string_view rName ) const +{ + if ( mnElem ) + { + OString aName(OUStringToOString(rName, + RTL_TEXTENCODING_UTF8)); + + // linear search is sufficient for this rare case + for( tools::Long i = mnElem; --i >= 0; ) + if ( aName == mpArray[i].mszUtf8Name ) + return mpArray[i].mnSize; + } + + return 0; +} + +OUString FontSizeNames::Size2Name( sal_Int32 nValue ) const +{ + OUString aStr; + + // binary search + for( tools::Long lower = 0, upper = mnElem - 1; lower <= upper; ) + { + tools::Long mid = (upper + lower) >> 1; + if ( nValue == mpArray[mid].mnSize ) + { + aStr = OUString( mpArray[mid].mszUtf8Name, strlen(mpArray[mid].mszUtf8Name), RTL_TEXTENCODING_UTF8 ); + break; + } + else if ( nValue < mpArray[mid].mnSize ) + upper = mid - 1; + else /* ( nValue > mpArray[mid].mnSize ) */ + lower = mid + 1; + } + + return aStr; +} + +OUString FontSizeNames::GetIndexName( sal_Int32 nIndex ) const +{ + OUString aStr; + + if ( nIndex < mnElem ) + aStr = OUString( mpArray[nIndex].mszUtf8Name, strlen(mpArray[nIndex].mszUtf8Name), RTL_TEXTENCODING_UTF8 ); + + return aStr; +} + +sal_Int32 FontSizeNames::GetIndexSize( sal_Int32 nIndex ) const +{ + if ( nIndex >= mnElem ) + return 0; + return mpArray[nIndex].mnSize; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/indexentryres.cxx b/svtools/source/control/indexentryres.cxx new file mode 100644 index 000000000..9420a37b2 --- /dev/null +++ b/svtools/source/control/indexentryres.cxx @@ -0,0 +1,58 @@ +/* -*- 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 <svtools/svtresid.hxx> +#include <svtools/strings.hrc> +#include <svtools/indexentryres.hxx> + +// implementation of the indexentry-algorithm-name translation +IndexEntryResource::IndexEntryResource() +{ + m_aData.emplace_back("alphanumeric", SvtResId(STR_SVT_INDEXENTRY_ALPHANUMERIC)); + m_aData.emplace_back("dict", SvtResId(STR_SVT_INDEXENTRY_DICTIONARY)); + m_aData.emplace_back("pinyin", SvtResId(STR_SVT_INDEXENTRY_PINYIN)); + m_aData.emplace_back("radical", SvtResId(STR_SVT_INDEXENTRY_RADICAL)); + m_aData.emplace_back("stroke", SvtResId(STR_SVT_INDEXENTRY_STROKE)); + m_aData.emplace_back("zhuyin", SvtResId(STR_SVT_INDEXENTRY_ZHUYIN)); + m_aData.emplace_back("phonetic (alphanumeric first) (grouped by syllable)", SvtResId(STR_SVT_INDEXENTRY_PHONETIC_FS)); + m_aData.emplace_back("phonetic (alphanumeric first) (grouped by consonant)", SvtResId(STR_SVT_INDEXENTRY_PHONETIC_FC)); + m_aData.emplace_back("phonetic (alphanumeric last) (grouped by syllable)", SvtResId(STR_SVT_INDEXENTRY_PHONETIC_LS)); + m_aData.emplace_back("phonetic (alphanumeric last) (grouped by consonant)", SvtResId(STR_SVT_INDEXENTRY_PHONETIC_LC)); +} + +const OUString& IndexEntryResource::GetTranslation(const OUString &r_Algorithm) +{ + sal_Int32 nIndex = r_Algorithm.indexOf('.'); + OUString aLocaleFreeAlgorithm; + + if (nIndex == -1) + aLocaleFreeAlgorithm = r_Algorithm; + else { + nIndex += 1; + aLocaleFreeAlgorithm = r_Algorithm.copy(nIndex); + } + + for (size_t i = 0; i < m_aData.size(); ++i) + if (aLocaleFreeAlgorithm == m_aData[i].GetAlgorithm()) + return m_aData[i].GetTranslation(); + return r_Algorithm; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/inettbc.cxx b/svtools/source/control/inettbc.cxx new file mode 100644 index 000000000..d2afe69cd --- /dev/null +++ b/svtools/source/control/inettbc.cxx @@ -0,0 +1,1111 @@ +/* -*- 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 UNX +#include <pwd.h> +#endif + +#include <svtools/inettbc.hxx> +#include <tools/diagnose_ex.h> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/ucb/NumberedSortingInfo.hpp> +#include <com/sun/star/ucb/UniversalContentBroker.hpp> +#include <com/sun/star/ucb/XAnyCompareFactory.hpp> +#include <com/sun/star/ucb/XCommandProcessor2.hpp> +#include <com/sun/star/ucb/XProgressHandler.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/ucb/SortedDynamicResultSetFactory.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <salhelper/thread.hxx> +#include <tools/debug.hxx> +#include <osl/file.hxx> +#include <osl/mutex.hxx> +#include <unotools/historyoptions.hxx> +#include <unotools/pathoptions.hxx> +#include <ucbhelper/commandenvironment.hxx> +#include <ucbhelper/content.hxx> +#include <unotools/ucbhelper.hxx> +#include <svtools/asynclink.hxx> +#include <svtools/urlfilter.hxx> + +#include <mutex> +#include <vector> +#include <algorithm> + +using namespace ::ucbhelper; +using namespace ::utl; +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::uno; + +class SvtURLBox_Impl +{ +public: + std::vector<OUString> aURLs; + std::vector<OUString> aCompletions; + std::vector<WildCard> m_aFilters; + + static bool TildeParsing( OUString& aText, OUString& aBaseUrl ); + + SvtURLBox_Impl( ) + { + FilterMatch::createWildCardFilterList(OUString(),m_aFilters); + } +}; + +class SvtMatchContext_Impl: public salhelper::Thread +{ + std::vector<OUString> aPickList; + std::vector<OUString> aCompletions; + std::vector<OUString> aURLs; + svtools::AsynchronLink aLink; + OUString aText; + SvtURLBox* pBox; + bool bOnlyDirectories; + bool bNoSelection; + + std::mutex mutex_; + bool stopped_; + css::uno::Reference< css::ucb::XCommandProcessor > processor_; + sal_Int32 commandId_; + + DECL_LINK( Select_Impl, void*, void ); + + virtual ~SvtMatchContext_Impl() override; + virtual void execute() override; + void doExecute(); + void Insert( const OUString& rCompletion, const OUString& rURL, bool bForce = false); + void ReadFolder( const OUString& rURL, const OUString& rMatch, bool bSmart ); + static void FillPicklist(std::vector<OUString>& rPickList); + +public: + SvtMatchContext_Impl( SvtURLBox* pBoxP, const OUString& rText ); + void Stop(); +}; + + +namespace +{ + ::osl::Mutex& theSvtMatchContextMutex() + { + static ::osl::Mutex SINGLETON; + return SINGLETON; + } +} + +SvtMatchContext_Impl::SvtMatchContext_Impl(SvtURLBox* pBoxP, const OUString& rText) + : Thread( "MatchContext_Impl" ) + , aLink( LINK( this, SvtMatchContext_Impl, Select_Impl ) ) + , aText( rText ) + , pBox( pBoxP ) + , bOnlyDirectories( pBoxP->bOnlyDirectories ) + , bNoSelection( pBoxP->bNoSelection ) + , stopped_(false) + , commandId_(0) +{ + FillPicklist( aPickList ); +} + +SvtMatchContext_Impl::~SvtMatchContext_Impl() +{ + aLink.ClearPendingCall(); +} + +void SvtMatchContext_Impl::FillPicklist(std::vector<OUString>& rPickList) +{ + // Read the history of picks + std::vector< SvtHistoryOptions::HistoryItem > seqPicklist = SvtHistoryOptions::GetList( EHistoryType::PickList ); + sal_uInt32 nCount = seqPicklist.size(); + + for( sal_uInt32 nItem=0; nItem < nCount; nItem++ ) + { + INetURLObject aURL; + aURL.SetURL( seqPicklist[nItem].sTitle ); + rPickList.insert(rPickList.begin() + nItem, aURL.GetMainURL(INetURLObject::DecodeMechanism::WithCharset)); + } +} + +void SvtMatchContext_Impl::Stop() +{ + css::uno::Reference< css::ucb::XCommandProcessor > proc; + sal_Int32 id(0); + { + std::scoped_lock g(mutex_); + if (!stopped_) { + stopped_ = true; + proc = processor_; + id = commandId_; + } + } + if (proc.is()) { + proc->abort(id); + } + terminate(); +} + +void SvtMatchContext_Impl::execute( ) +{ + doExecute(); + aLink.Call( this ); +} + + +// This method is called via AsynchronLink, so it has the SolarMutex and +// calling solar code ( VCL ... ) is safe. It is called when the thread is +// terminated ( finished work or stopped ). Cancelling the thread via +// Cancellable does not discard the information gained so far, it +// inserts all collected completions into the listbox. + +IMPL_LINK_NOARG( SvtMatchContext_Impl, Select_Impl, void*, void ) +{ + // avoid recursion through cancel button + { + std::scoped_lock g(mutex_); + if (stopped_) { + // Completion was stopped, no display: + return; + } + } + + // insert all completed strings into the listbox + pBox->clear(); + + for (auto const& completion : aCompletions) + { + // convert the file into a URL + OUString sURL; + osl::FileBase::getFileURLFromSystemPath(completion, sURL); + // note: if this doesn't work, we're not interested in: we're checking the + // untouched sCompletion then + + if ( !sURL.isEmpty() && !sURL.endsWith("/") ) + { + OUString sUpperURL( sURL.toAsciiUpperCase() ); + + if ( ::std::none_of( pBox->pImpl->m_aFilters.begin(), + pBox->pImpl->m_aFilters.end(), + FilterMatch( sUpperURL ) ) ) + { // this URL is not allowed + continue; + } + } + + pBox->append_text(completion); + } + + pBox->EnableAutocomplete(!bNoSelection); + + // transfer string lists to listbox and forget them + pBox->pImpl->aURLs = aURLs; + pBox->pImpl->aCompletions = aCompletions; + aURLs.clear(); + aCompletions.clear(); + + // the box has this control as a member so we have to set that member + // to zero before deleting ourself. + pBox->pCtx.clear(); +} + +void SvtMatchContext_Impl::Insert( const OUString& rCompletion, + const OUString& rURL, + bool bForce ) +{ + if( !bForce ) + { + // avoid doubles + if(find(aCompletions.begin(), aCompletions.end(), rCompletion) != aCompletions.end()) + return; + } + + aCompletions.push_back(rCompletion); + aURLs.push_back(rURL); +} + + +void SvtMatchContext_Impl::ReadFolder( const OUString& rURL, + const OUString& rMatch, + bool bSmart ) +{ + // check folder to scan + if( !UCBContentHelper::IsFolder( rURL ) ) + return; + + bool bPureHomePath = false; +#ifdef UNX + bPureHomePath = aText.startsWith( "~" ) && aText.indexOf( '/' ) == -1; +#endif + + bool bExectMatch = bPureHomePath + || aText == "." + || aText.endsWith("/.") + || aText.endsWith("/.."); + + // for pure home paths ( ~username ) the '.' at the end of rMatch + // means that it points to root catalog + // this is done only for file contents since home paths parsing is useful only for them + if ( bPureHomePath && rMatch == "file:///." ) + { + // a home that refers to / + + OUString aNewText = aText + "/"; + Insert( aNewText, rURL, true ); + + return; + } + + // string to match with + INetURLObject aMatchObj( rMatch ); + OUString aMatchName; + + if ( rURL != aMatchObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) + { + aMatchName = aMatchObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ); + + // matching is always done case insensitive, but completion will be case sensitive and case preserving + aMatchName = aMatchName.toAsciiLowerCase(); + + // if the matchstring ends with a slash, we must search for this also + if ( rMatch.endsWith("/") ) + aMatchName += "/"; + } + + sal_Int32 nMatchLen = aMatchName.getLength(); + + INetURLObject aFolderObj( rURL ); + DBG_ASSERT( aFolderObj.GetProtocol() != INetProtocol::NotValid, "Invalid URL!" ); + + try + { + Content aCnt( aFolderObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), + new ::ucbhelper::CommandEnvironment( uno::Reference< XInteractionHandler >(), + uno::Reference< XProgressHandler >() ), + comphelper::getProcessComponentContext() ); + uno::Reference< XResultSet > xResultSet; + + try + { + ResultSetInclude eInclude = INCLUDE_FOLDERS_AND_DOCUMENTS; + if ( bOnlyDirectories ) + eInclude = INCLUDE_FOLDERS_ONLY; + uno::Reference< XDynamicResultSet > xDynResultSet = aCnt.createDynamicCursor( { "Title", "IsFolder" }, eInclude ); + + uno::Reference < XAnyCompareFactory > xCompare; + uno::Reference < XSortedDynamicResultSetFactory > xSRSFac = + SortedDynamicResultSetFactory::create( ::comphelper::getProcessComponentContext() ); + + uno::Reference< XDynamicResultSet > xDynamicResultSet = + xSRSFac->createSortedDynamicResultSet( xDynResultSet, { { 2, false }, { 1, true } }, xCompare ); + + if ( xDynamicResultSet.is() ) + { + xResultSet = xDynamicResultSet->getStaticResultSet(); + } + } + catch( css::uno::Exception& ) {} + + if ( xResultSet.is() ) + { + uno::Reference< XRow > xRow( xResultSet, UNO_QUERY ); + uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY ); + + try + { + while ( schedule() && xResultSet->next() ) + { + OUString aURL = xContentAccess->queryContentIdentifierString(); + OUString aTitle = xRow->getString(1); + bool bIsFolder = xRow->getBoolean(2); + + // matching is always done case insensitive, but completion will be case sensitive and case preserving + aTitle = aTitle.toAsciiLowerCase(); + + if ( + !nMatchLen || + (bExectMatch && aMatchName == aTitle) || + (!bExectMatch && aTitle.startsWith(aMatchName)) + ) + { + // all names fit if matchstring is empty + INetURLObject aObj( aURL ); + sal_Unicode aDelimiter = '/'; + if ( bSmart ) + // when parsing is done "smart", the delimiter must be "guessed" + aObj.getFSysPath( static_cast<FSysStyle>(FSysStyle::Detect & ~FSysStyle::Vos), &aDelimiter ); + + if ( bIsFolder ) + aObj.setFinalSlash(); + + // get the last name of the URL + OUString aMatch = aObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ); + OUString aInput( aText ); + if ( nMatchLen ) + { + if (aText.endsWith(".") || bPureHomePath) + { + // if a "special folder" URL was typed, don't touch the user input + aMatch = aMatch.copy( nMatchLen ); + } + else + { + // make the user input case preserving + DBG_ASSERT( aInput.getLength() >= nMatchLen, "Suspicious Matching!" ); + aInput = aInput.copy( 0, aInput.getLength() - nMatchLen ); + } + } + + aInput += aMatch; + + // folders should get a final slash automatically + if ( bIsFolder ) + aInput += OUStringChar(aDelimiter); + + Insert( aInput, aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), true ); + } + } + } + catch( css::uno::Exception& ) + { + } + } + } + catch( css::uno::Exception& ) + { + } +} + +void SvtMatchContext_Impl::doExecute() +{ + ::osl::MutexGuard aGuard( theSvtMatchContextMutex() ); + { + // have we been stopped while we were waiting for the mutex? + std::scoped_lock g(mutex_); + if (stopped_) { + return; + } + } + + // Reset match lists + aCompletions.clear(); + aURLs.clear(); + + // check for input + if ( aText.isEmpty() ) + return; + + if( aText.indexOf( '*' ) != -1 || aText.indexOf( '?' ) != -1 ) + // no autocompletion for wildcards + return; + + OUString aMatch; + INetProtocol eProt = INetURLObject::CompareProtocolScheme( aText ); + INetProtocol eBaseProt = INetURLObject::CompareProtocolScheme( pBox->aBaseURL ); + if ( pBox->aBaseURL.isEmpty() ) + eBaseProt = INetURLObject::CompareProtocolScheme( SvtPathOptions().GetWorkPath() ); + INetProtocol eSmartProt = pBox->GetSmartProtocol(); + + // if the user input is a valid URL, go on with it + // otherwise it could be parsed smart with a predefined smart protocol + // ( or if this is not set with the protocol of a predefined base URL ) + if( eProt == INetProtocol::NotValid || eProt == eSmartProt || (eSmartProt == INetProtocol::NotValid && eProt == eBaseProt) ) + { + // not stopped yet ? + if( schedule() ) + { + if ( eProt == INetProtocol::NotValid ) + aMatch = SvtURLBox::ParseSmart( aText, pBox->aBaseURL ); + else + aMatch = aText; + if ( !aMatch.isEmpty() ) + { + INetURLObject aURLObject( aMatch ); + OUString aMainURL( aURLObject.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + // Disable autocompletion for anything but the (local) file + // system (for which access is hopefully fast), as the logic of + // how SvtMatchContext_Impl is used requires this code to run to + // completion before further user input is processed, and even + // SvtMatchContext_Impl::Stop does not guarantee a speedy + // return: + if ( !aMainURL.isEmpty() + && aURLObject.GetProtocol() == INetProtocol::File ) + { + // if text input is a directory, it must be part of the match list! Until then it is scanned + bool folder = false; + if (aURLObject.hasFinalSlash()) { + try { + css::uno::Reference< css::uno::XComponentContext > + ctx(comphelper::getProcessComponentContext()); + css::uno::Reference< + css::ucb::XUniversalContentBroker > ucb( + css::ucb::UniversalContentBroker::create( + ctx)); + css::uno::Sequence< css::beans::Property > prop{ + { /* Name */ "IsFolder", + /* Handle */ -1, + /* Type */ cppu::UnoType< bool >::get(), + /* Attributes */ {} } + }; + css::uno::Any res; + css::uno::Reference< css::ucb::XCommandProcessor > + proc( + ucb->queryContent( + ucb->createContentIdentifier(aMainURL)), + css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::ucb::XCommandProcessor2 > + proc2(proc, css::uno::UNO_QUERY); + sal_Int32 id = proc->createCommandIdentifier(); + try { + { + std::scoped_lock g(mutex_); + processor_ = proc; + commandId_ = id; + } + res = proc->execute( + css::ucb::Command( + "getPropertyValues", -1, + css::uno::Any(prop)), + id, + css::uno::Reference< + css::ucb::XCommandEnvironment >()); + } catch (...) { + if (proc2.is()) { + try { + proc2->releaseCommandIdentifier(id); + } catch (css::uno::RuntimeException &) { + TOOLS_WARN_EXCEPTION("svtools.control", "ignoring"); + } + } + throw; + } + if (proc2.is()) { + proc2->releaseCommandIdentifier(id); + } + { + std::scoped_lock g(mutex_); + processor_.clear(); + // At least the neon-based WebDAV UCP does not + // properly support aborting commands, so return + // anyway now if an abort request had been + // ignored and the command execution only + // returned "successfully" after some timeout: + if (stopped_) { + return; + } + } + css::uno::Reference< css::sdbc::XRow > row( + res, css::uno::UNO_QUERY_THROW); + folder = row->getBoolean(1) && !row->wasNull(); + } catch (css::uno::Exception &) { + TOOLS_WARN_EXCEPTION("svtools.control", "ignoring"); + return; + } + } + if (folder) + Insert( aText, aMatch ); + else + // otherwise the parent folder will be taken + aURLObject.removeSegment(); + + // scan directory and insert all matches + ReadFolder( aURLObject.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aMatch, eProt == INetProtocol::NotValid ); + } + } + } + } + + if ( bOnlyDirectories ) + // don't scan history picklist if only directories are allowed, picklist contains only files + return; + + bool bFull = false; + + INetURLObject aCurObj; + OUString aCurString, aCurMainURL; + INetURLObject aObj; + aObj.SetSmartProtocol( eSmartProt == INetProtocol::NotValid ? INetProtocol::Http : eSmartProt ); + for( ;; ) + { + for(const auto& rPick : aPickList) + { + if (!schedule()) + break; + + aCurObj.SetURL(rPick); + aCurObj.SetSmartURL( aCurObj.GetURLNoPass()); + aCurMainURL = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + if( eProt != INetProtocol::NotValid && aCurObj.GetProtocol() != eProt ) + continue; + + if( eSmartProt != INetProtocol::NotValid && aCurObj.GetProtocol() != eSmartProt ) + continue; + + switch( aCurObj.GetProtocol() ) + { + case INetProtocol::Http: + case INetProtocol::Https: + case INetProtocol::Ftp: + { + if( eProt == INetProtocol::NotValid && !bFull ) + { + aObj.SetSmartURL( aText ); + if( aObj.GetURLPath().getLength() > 1 ) + continue; + } + + aCurString = aCurMainURL; + if( eProt == INetProtocol::NotValid ) + { + // try if text matches the scheme + OUString aScheme( INetURLObject::GetScheme( aCurObj.GetProtocol() ) ); + if ( aScheme.startsWithIgnoreAsciiCase( aText ) && aText.getLength() < aScheme.getLength() ) + { + if( bFull ) + aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + else + { + aCurObj.SetMark( u"" ); + aCurObj.SetParam( u"" ); + aCurObj.SetURLPath( u"" ); + aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + + Insert( aMatch, aMatch ); + } + + // now try smart matching + aCurString = aCurString.copy( aScheme.getLength() ); + } + + if( aCurString.startsWithIgnoreAsciiCase( aText ) ) + { + if( bFull ) + aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + else + { + aCurObj.SetMark( u"" ); + aCurObj.SetParam( u"" ); + aCurObj.SetURLPath( u"" ); + aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + + OUString aURL( aMatch ); + if( eProt == INetProtocol::NotValid ) + aMatch = aMatch.copy( INetURLObject::GetScheme( aCurObj.GetProtocol() ).getLength() ); + + if( aText.getLength() < aMatch.getLength() ) + Insert( aMatch, aURL ); + + continue; + } + break; + } + default: + { + if( bFull ) + continue; + + if( aCurMainURL.startsWith(aText) ) + { + if( aText.getLength() < aCurMainURL.getLength() ) + Insert( aCurMainURL, aCurMainURL ); + + continue; + } + break; + } + } + } + + if( !bFull ) + bFull = true; + else + break; + } +} + +/** Parse leading ~ for Unix systems, + does nothing for Windows + */ +bool SvtURLBox_Impl::TildeParsing( + OUString& +#ifdef UNX + aText +#endif + , OUString& +#ifdef UNX + aBaseURL +#endif +) +{ +#ifdef UNX + if( aText.startsWith( "~" ) ) + { + OUString aParseTilde; + bool bTrailingSlash = true; // use trailing slash + + if( aText.getLength() == 1 || aText[ 1 ] == '/' ) + { + // covers "~" or "~/..." cases + const char* aHomeLocation = getenv( "HOME" ); + if( !aHomeLocation ) + aHomeLocation = ""; + + aParseTilde = OUString::createFromAscii(aHomeLocation); + + // in case the whole path is just "~" then there should + // be no trailing slash at the end + if( aText.getLength() == 1 ) + bTrailingSlash = false; + } + else + { + // covers "~username" and "~username/..." cases + sal_Int32 nNameEnd = aText.indexOf( '/' ); + OUString aUserName = aText.copy( 1, ( nNameEnd != -1 ) ? nNameEnd : ( aText.getLength() - 1 ) ); + + struct passwd* pPasswd = nullptr; +#ifdef __sun + Sequence< sal_Int8 > sBuf( 1024 ); + struct passwd aTmp; + sal_Int32 nRes = getpwnam_r( OUStringToOString( aUserName, RTL_TEXTENCODING_ASCII_US ).getStr(), + &aTmp, + (char*)sBuf.getArray(), + 1024, + &pPasswd ); + if( !nRes && pPasswd ) + aParseTilde = OUString::createFromAscii(pPasswd->pw_dir); + else + return false; // no such user +#else + pPasswd = getpwnam( OUStringToOString( aUserName, RTL_TEXTENCODING_ASCII_US ).getStr() ); + if( pPasswd ) + aParseTilde = OUString::createFromAscii(pPasswd->pw_dir); + else + return false; // no such user +#endif + + // in case the path is "~username" then there should + // be no trailing slash at the end + if( nNameEnd == -1 ) + bTrailingSlash = false; + } + + if( !bTrailingSlash ) + { + if( aParseTilde.isEmpty() || aParseTilde == "/" ) + { + // "/" path should be converted to "/." + aParseTilde = "/."; + } + else + { + // "blabla/" path should be converted to "blabla" + aParseTilde = comphelper::string::stripEnd(aParseTilde, '/'); + } + } + else + { + if( !aParseTilde.endsWith("/") ) + aParseTilde += "/"; + if( aText.getLength() > 2 ) + aParseTilde += aText.subView( 2 ); + } + + aText = aParseTilde; + aBaseURL.clear(); // tilde provide absolute path + } +#endif + + return true; +} + +//-- + +OUString SvtURLBox::ParseSmart( const OUString& _aText, const OUString& _aBaseURL ) +{ + OUString aMatch; + OUString aText = _aText; + OUString aBaseURL = _aBaseURL; + + // parse ~ for Unix systems + // does nothing for Windows + if( !SvtURLBox_Impl::TildeParsing( aText, aBaseURL ) ) + return OUString(); + + if( !aBaseURL.isEmpty() ) + { + INetProtocol eBaseProt = INetURLObject::CompareProtocolScheme( aBaseURL ); + + // if a base URL is set the string may be parsed relative + if( aText.startsWith( "/" ) ) + { + // text starting with slashes means absolute file URLs + OUString aTemp = INetURLObject::GetScheme( eBaseProt ); + + // file URL must be correctly encoded! + OUString aTextURL = INetURLObject::encode( aText, INetURLObject::PART_FPATH, + INetURLObject::EncodeMechanism::All ); + aTemp += aTextURL; + + INetURLObject aTmp( aTemp ); + if ( !aTmp.HasError() && aTmp.GetProtocol() != INetProtocol::NotValid ) + aMatch = aTmp.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + else + { + OUString aSmart( aText ); + INetURLObject aObj( aBaseURL ); + + // HRO: I suppose this hack should only be done for Windows !!!??? +#ifdef _WIN32 + // HRO: INetURLObject::smatRel2Abs does not recognize '\\' as a relative path + // but in case of "\\\\" INetURLObject is right - this is an absolute path ! + + if( aText.startsWith("\\") && (aText.getLength() < 2 || aText[ 1 ] != '\\') ) + { + // cut to first segment + OUString aTmp = INetURLObject::GetScheme( eBaseProt ) + "/"; + aTmp += aObj.getName( 0, true, INetURLObject::DecodeMechanism::WithCharset ); + aObj.SetURL( aTmp ); + + aSmart = aSmart.copy(1); + } +#endif + // base URL must be a directory ! + aObj.setFinalSlash(); + + // take base URL and append current input + bool bWasAbsolute = false; +#ifdef UNX + // encode file URL correctly + aSmart = INetURLObject::encode( aSmart, INetURLObject::PART_FPATH, INetURLObject::EncodeMechanism::All ); +#endif + INetURLObject aTmp( aObj.smartRel2Abs( aSmart, bWasAbsolute ) ); + + if ( aText.endsWith(".") ) + // INetURLObject appends a final slash for the directories "." and "..", this is a bug! + // Remove it as a workaround + aTmp.removeFinalSlash(); + if ( !aTmp.HasError() && aTmp.GetProtocol() != INetProtocol::NotValid ) + aMatch = aTmp.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + } + else + { + OUString aTmpMatch; + osl::FileBase::getFileURLFromSystemPath( aText, aTmpMatch ); + aMatch = aTmpMatch; + } + + return aMatch; +} + +IMPL_LINK_NOARG(SvtURLBox, TryAutoComplete, Timer *, void) +{ + OUString aCurText = m_xWidget->get_active_text(); + int nStartPos, nEndPos; + m_xWidget->get_entry_selection_bounds(nStartPos, nEndPos); + if (std::max(nStartPos, nEndPos) != aCurText.getLength()) + return; + + auto nLen = std::min(nStartPos, nEndPos); + aCurText = aCurText.copy( 0, nLen ); + if (!aCurText.isEmpty()) + { + if (pCtx.is()) + { + pCtx->Stop(); + pCtx->join(); + pCtx.clear(); + } + pCtx = new SvtMatchContext_Impl(this, aCurText); + pCtx->launch(); + } + else + m_xWidget->clear(); +} + +SvtURLBox::SvtURLBox(std::unique_ptr<weld::ComboBox> pWidget) + : aChangedIdle("svtools::URLBox aChangedIdle") + , eSmartProtocol(INetProtocol::NotValid) + , bOnlyDirectories( false ) + , bHistoryDisabled( false ) + , bNoSelection( false ) + , m_xWidget(std::move(pWidget)) +{ + //don't grow to fix mega-long urls + Size aSize(m_xWidget->get_preferred_size()); + m_xWidget->set_size_request(aSize.Width(), -1); + + Init(); + + m_xWidget->connect_focus_in(LINK(this, SvtURLBox, FocusInHdl)); + m_xWidget->connect_focus_out(LINK(this, SvtURLBox, FocusOutHdl)); + m_xWidget->connect_changed(LINK(this, SvtURLBox, ChangedHdl)); + + aChangedIdle.SetInvokeHandler(LINK(this, SvtURLBox, TryAutoComplete)); +} + +void SvtURLBox::Init() +{ + pImpl.reset( new SvtURLBox_Impl ); + + m_xWidget->set_entry_completion(false); + + UpdatePicklistForSmartProtocol_Impl(); +} + +SvtURLBox::~SvtURLBox() +{ + if (pCtx.is()) + { + pCtx->Stop(); + pCtx->join(); + } +} + +void SvtURLBox::SetSmartProtocol(INetProtocol eProt) +{ + if ( eSmartProtocol != eProt ) + { + eSmartProtocol = eProt; + UpdatePicklistForSmartProtocol_Impl(); + } +} + +void SvtURLBox::UpdatePicklistForSmartProtocol_Impl() +{ + m_xWidget->clear(); + if ( bHistoryDisabled ) + return; + + if (bHistoryDisabled) + return; + + // read history pick list + const std::vector< SvtHistoryOptions::HistoryItem > seqPicklist = SvtHistoryOptions::GetList( EHistoryType::PickList ); + INetURLObject aCurObj; + + for( const SvtHistoryOptions::HistoryItem& rPropertySet : seqPicklist ) + { + aCurObj.SetURL( rPropertySet.sURL ); + + if ( !rPropertySet.sURL.isEmpty() && ( eSmartProtocol != INetProtocol::NotValid ) ) + { + if( aCurObj.GetProtocol() != eSmartProtocol ) + continue; + } + + OUString aURL( aCurObj.GetMainURL( INetURLObject::DecodeMechanism::WithCharset ) ); + + if ( !aURL.isEmpty() ) + { + bool bFound = aURL.endsWith("/"); + if ( !bFound ) + { + OUString aUpperURL = aURL.toAsciiUpperCase(); + + bFound = ::std::any_of(pImpl->m_aFilters.begin(), + pImpl->m_aFilters.end(), + FilterMatch( aUpperURL ) ); + } + if ( bFound ) + { + OUString aFile; + if (osl::FileBase::getSystemPathFromFileURL(aURL, aFile) == osl::FileBase::E_None) + m_xWidget->append_text(aFile); + else + m_xWidget->append_text(aURL); + } + } + } +} + +IMPL_LINK_NOARG(SvtURLBox, ChangedHdl, weld::ComboBox&, void) +{ + aChangeHdl.Call(*m_xWidget); + aChangedIdle.Start(); //launch this to happen on idle after cursor position will have been set +} + +IMPL_LINK_NOARG(SvtURLBox, FocusInHdl, weld::Widget&, void) +{ +#ifndef UNX + // pb: don't select automatically on unix #93251# + m_xWidget->select_entry_region(0, -1); +#endif + aFocusInHdl.Call(*m_xWidget); +} + +IMPL_LINK_NOARG(SvtURLBox, FocusOutHdl, weld::Widget&, void) +{ + if (pCtx.is()) + { + pCtx->Stop(); + pCtx->join(); + pCtx.clear(); + } + aFocusOutHdl.Call(*m_xWidget); +} + +void SvtURLBox::SetOnlyDirectories( bool bDir ) +{ + bOnlyDirectories = bDir; + if ( bOnlyDirectories ) + m_xWidget->clear(); +} + +void SvtURLBox::SetNoURLSelection( bool bSet ) +{ + bNoSelection = bSet; +} + +OUString SvtURLBox::GetURL() +{ + // wait for end of autocompletion + ::osl::MutexGuard aGuard( theSvtMatchContextMutex() ); + + OUString aText(m_xWidget->get_active_text()); + if (MatchesPlaceHolder(aText)) + return aPlaceHolder; + + // try to get the right case preserving URL from the list of URLs + for(std::vector<OUString>::iterator i = pImpl->aCompletions.begin(), j = pImpl->aURLs.begin(); i != pImpl->aCompletions.end() && j != pImpl->aURLs.end(); ++i, ++j) + { + if((*i) == aText) + return *j; + } + +#ifdef _WIN32 + // erase trailing spaces on Windows since they are invalid on this OS and + // most of the time they are inserted by accident via copy / paste + aText = comphelper::string::stripEnd(aText, ' '); + if ( aText.isEmpty() ) + return aText; + // #i9739# +#endif + + INetURLObject aObj( aText ); + if( aText.indexOf( '*' ) != -1 || aText.indexOf( '?' ) != -1 ) + { + // no autocompletion for wildcards + INetURLObject aTempObj; + if ( eSmartProtocol != INetProtocol::NotValid ) + aTempObj.SetSmartProtocol( eSmartProtocol ); + if ( aTempObj.SetSmartURL( aText ) ) + return aTempObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + else + return aText; + } + + if ( aObj.GetProtocol() == INetProtocol::NotValid ) + { + OUString aName = ParseSmart( aText, aBaseURL ); + aObj.SetURL(aName); + OUString aURL( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + if ( aURL.isEmpty() ) + // aText itself is invalid, and even together with aBaseURL, it could not + // made valid -> no chance + return aText; + + bool bSlash = aObj.hasFinalSlash(); + { + OUString aFileURL; + + Any aAny = UCBContentHelper::GetProperty(aURL, "CasePreservingURL"); + bool success = (aAny >>= aFileURL); + OUString aTitle; + if(success) + aTitle = INetURLObject(aFileURL).getName( + INetURLObject::LAST_SEGMENT, + true, + INetURLObject::DecodeMechanism::WithCharset ); + else + success = + UCBContentHelper::GetTitle(aURL,&aTitle); + + if( success && aTitle != "/" && aTitle != "." ) + { + aObj.setName( aTitle ); + if ( bSlash ) + aObj.setFinalSlash(); + } + } + } + + return aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); +} + +void SvtURLBox::SetBaseURL( const OUString& rURL ) +{ + ::osl::MutexGuard aGuard( theSvtMatchContextMutex() ); + + // Reset match lists + pImpl->aCompletions.clear(); + pImpl->aURLs.clear(); + + aBaseURL = rURL; +} + +void SvtURLBox::DisableHistory() +{ + bHistoryDisabled = true; + UpdatePicklistForSmartProtocol_Impl(); +} + +void SvtURLBox::SetFilter(const OUString& _sFilter) +{ + pImpl->m_aFilters.clear(); + FilterMatch::createWildCardFilterList(_sFilter,pImpl->m_aFilters); +} + +void FilterMatch::createWildCardFilterList(const OUString& _rFilterList,::std::vector< WildCard >& _rFilters) +{ + if( _rFilterList.getLength() ) + { + // filter is given + sal_Int32 nIndex = 0; + OUString sToken; + do + { + sToken = _rFilterList.getToken( 0, ';', nIndex ); + if ( !sToken.isEmpty() ) + { + _rFilters.emplace_back( sToken.toAsciiUpperCase() ); + } + } + while ( nIndex >= 0 ); + } + else + { + // no filter is given -> match all + _rFilters.emplace_back(u"*" ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/ruler.cxx b/svtools/source/control/ruler.cxx new file mode 100644 index 000000000..89164e6ed --- /dev/null +++ b/svtools/source/control/ruler.cxx @@ -0,0 +1,2778 @@ +/* -*- 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/debug.hxx> +#include <tools/poly.hxx> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> +#include <vcl/vcllayout.hxx> +#include <vcl/virdev.hxx> +#include <vcl/ptrstyle.hxx> +#include <sal/log.hxx> + +#include <svtools/ruler.hxx> +#include <svtools/svtresid.hxx> +#include <svtools/strings.hrc> +#include <svtools/colorcfg.hxx> +#include "accessibleruler.hxx" + +#include <memory> +#include <vector> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::accessibility; + +#define RULER_OFF 3 +#define RULER_RESIZE_OFF 4 +#define RULER_MIN_SIZE 3 + +#define RULER_VAR_SIZE 8 + +#define RULER_UPDATE_LINES 0x01 + +#define RULER_CLIP 150 + +#define RULER_UNIT_MM 0 +#define RULER_UNIT_CM 1 +#define RULER_UNIT_M 2 +#define RULER_UNIT_KM 3 +#define RULER_UNIT_INCH 4 +#define RULER_UNIT_FOOT 5 +#define RULER_UNIT_MILE 6 +#define RULER_UNIT_POINT 7 +#define RULER_UNIT_PICA 8 +#define RULER_UNIT_CHAR 9 +#define RULER_UNIT_LINE 10 +#define RULER_UNIT_COUNT 11 + +namespace +{ +/** + * Pre-calculates glyph items for rText on rRenderContext. Subsequent calls + * avoid the calculation and just return a pointer to rTextGlyphs. + */ +SalLayoutGlyphs* lcl_GetRulerTextGlyphs(const vcl::RenderContext& rRenderContext, const OUString& rText, + SalLayoutGlyphs& rTextGlyphs) +{ + if (rTextGlyphs.IsValid()) + // Use pre-calculated result. + return &rTextGlyphs; + + // Calculate glyph items. + + std::unique_ptr<SalLayout> pLayout = rRenderContext.ImplLayout( + rText, 0, rText.getLength(), Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly); + if (!pLayout) + return nullptr; + + // Remember the calculation result. + rTextGlyphs = pLayout->GetGlyphs(); + + return &rTextGlyphs; +} +} + +class ImplRulerData +{ + friend class Ruler; + +private: + std::vector<RulerLine> pLines; + std::vector<RulerBorder> pBorders; + std::vector<RulerIndent> pIndents; + std::vector<RulerTab> pTabs; + + tools::Long nNullVirOff; + tools::Long nRulVirOff; + tools::Long nRulWidth; + tools::Long nPageOff; + tools::Long nPageWidth; + tools::Long nNullOff; + tools::Long nMargin1; + tools::Long nMargin2; + // In this context, "frame margin" means paragraph margins (indents) + tools::Long nLeftFrameMargin; + tools::Long nRightFrameMargin; + RulerMarginStyle nMargin1Style; + RulerMarginStyle nMargin2Style; + bool bAutoPageWidth; + bool bTextRTL; + +public: + ImplRulerData(); +}; + +ImplRulerData::ImplRulerData() : + nNullVirOff (0), + nRulVirOff (0), + nRulWidth (0), + nPageOff (0), + nPageWidth (0), + nNullOff (0), + nMargin1 (0), + nMargin2 (0), + nLeftFrameMargin (0), + nRightFrameMargin (0), + nMargin1Style (RulerMarginStyle::NONE), + nMargin2Style (RulerMarginStyle::NONE), + bAutoPageWidth (true), // Page width == EditWin width + bTextRTL (false) +{ +} + +const RulerUnitData aImplRulerUnitTab[RULER_UNIT_COUNT] = +{ +{ MapUnit::Map100thMM, 100, 25.0, 25.0, 50.0, 100.0, " mm" }, // MM +{ MapUnit::Map100thMM, 1000, 100.0, 500.0, 1000.0, 1000.0, " cm" }, // CM +{ MapUnit::MapMM, 1000, 10.0, 250.0, 500.0, 1000.0, " m" }, // M +{ MapUnit::MapCM, 100000, 12500.0, 25000.0, 50000.0, 100000.0, " km" }, // KM +{ MapUnit::Map1000thInch, 1000, 62.5, 125.0, 500.0, 1000.0, "\"" }, // INCH +{ MapUnit::Map100thInch, 1200, 120.0, 120.0, 600.0, 1200.0, "'" }, // FOOT +{ MapUnit::Map10thInch, 633600, 63360.0, 63360.0, 316800.0, 633600.0, " miles" }, // MILE +{ MapUnit::MapPoint, 1, 12.0, 12.0, 12.0, 36.0, " pt" }, // POINT +{ MapUnit::Map100thMM, 423, 423.0, 423.0, 423.0, 846.0, " pc" }, // PICA +{ MapUnit::Map100thMM, 371, 371.0, 371.0, 371.0, 743.0, " ch" }, // CHAR +{ MapUnit::Map100thMM, 551, 551.0, 551.0, 551.0, 1102.0, " li" } // LINE +}; + +static RulerTabData ruler_tab = +{ + 0, // DPIScaleFactor to be set + 7, // ruler_tab_width + 6, // ruler_tab_height + 2, // ruler_tab_height2 + 2, // ruler_tab_width2 + 8, // ruler_tab_cwidth + 4, // ruler_tab_cwidth2 + 4, // ruler_tab_cwidth3 + 2, // ruler_tab_cwidth4 + 4, // ruler_tab_dheight + 1, // ruler_tab_dheight2 + 5, // ruler_tab_dwidth + 3, // ruler_tab_dwidth2 + 3, // ruler_tab_dwidth3 + 1, // ruler_tab_dwidth4 + 5 // ruler_tab_textoff +}; + +void Ruler::ImplInit( WinBits nWinBits ) +{ + // Set default WinBits + if ( !(nWinBits & WB_VERT) ) + { + nWinBits |= WB_HORZ; + + // RTL: no UI mirroring for horizontal rulers, because + // the document is also not mirrored + EnableRTL( false ); + } + + // Initialize variables + mnWinStyle = nWinBits; // Window-Style + mnBorderOff = 0; // Border-Offset + mnWinOff = 0; // EditWinOffset + mnWinWidth = 0; // EditWinWidth + mnWidth = 0; // Window width + mnHeight = 0; // Window height + mnVirOff = 0; // Offset of VirtualDevice from top-left corner + mnVirWidth = 0; // width or height from VirtualDevice + mnVirHeight = 0; // height of width from VirtualDevice + mnDragPos = 0; // Drag-Position (Null point) + mnDragAryPos = 0; // Drag-Array-Index + mnDragSize = RulerDragSize::Move; // Did size change at dragging + mnDragModifier = 0; // Modifier key at dragging + mnExtraStyle = 0; // Style of Extra field + mnCharWidth = 371; + mnLineHeight = 551; + mbCalc = true; // Should recalculate page width + mbFormat = true; // Should redraw + mbDrag = false; // Currently at dragging + mbDragDelete = false; // Has mouse left the dragging area + mbDragCanceled = false; // Dragging cancelled? + mbAutoWinWidth = true; // EditWinWidth == RulerWidth + mbActive = true; // Is ruler active + mnUpdateFlags = 0; // What needs to be updated + mpData = mpSaveData.get(); // Pointer to normal data + meExtraType = RulerExtra::DontKnow; // What is in extra field + meDragType = RulerType::DontKnow; // Which element is dragged + + // Initialize Units + mnUnitIndex = RULER_UNIT_CM; + meUnit = FieldUnit::CM; + maZoom = Fraction( 1, 1 ); + + // Recalculate border widths + if ( nWinBits & WB_BORDER ) + mnBorderWidth = 1; + else + mnBorderWidth = 0; + + // Settings + ImplInitSettings( true, true, true ); + + // Setup the default size + tools::Rectangle aRect; + GetOutDev()->GetTextBoundRect( aRect, "0123456789" ); + tools::Long nDefHeight = aRect.GetHeight() + RULER_OFF * 2 + ruler_tab.textoff * 2 + mnBorderWidth; + + Size aDefSize; + if ( nWinBits & WB_HORZ ) + aDefSize.setHeight( nDefHeight ); + else + aDefSize.setWidth( nDefHeight ); + SetOutputSizePixel( aDefSize ); + SetType(WindowType::RULER); +} + +Ruler::Ruler( vcl::Window* pParent, WinBits nWinStyle ) : + Window( pParent, nWinStyle & WB_3DLOOK ), + maVirDev( VclPtr<VirtualDevice>::Create(*GetOutDev()) ), + maMapMode( MapUnit::Map100thMM ), + mpSaveData(new ImplRulerData), + mpData(nullptr), + mpDragData(new ImplRulerData) +{ + // Check to see if the ruler constructor has + // already been called before otherwise + // we end up with over-scaled elements + if (ruler_tab.DPIScaleFactor == 0) + { + ruler_tab.DPIScaleFactor = GetDPIScaleFactor(); + ruler_tab.width *= ruler_tab.DPIScaleFactor; + ruler_tab.height *= ruler_tab.DPIScaleFactor; + ruler_tab.height2 *= ruler_tab.DPIScaleFactor; + ruler_tab.width2 *= ruler_tab.DPIScaleFactor; + ruler_tab.cwidth *= ruler_tab.DPIScaleFactor; + ruler_tab.cwidth2 *= ruler_tab.DPIScaleFactor; + ruler_tab.cwidth3 *= ruler_tab.DPIScaleFactor; + ruler_tab.cwidth4 *= ruler_tab.DPIScaleFactor; + ruler_tab.dheight *= ruler_tab.DPIScaleFactor; + ruler_tab.dheight2 *= ruler_tab.DPIScaleFactor; + ruler_tab.dwidth *= ruler_tab.DPIScaleFactor; + ruler_tab.dwidth2 *= ruler_tab.DPIScaleFactor; + ruler_tab.dwidth3 *= ruler_tab.DPIScaleFactor; + ruler_tab.dwidth4 *= ruler_tab.DPIScaleFactor; + ruler_tab.textoff *= ruler_tab.DPIScaleFactor; + } + + + ImplInit( nWinStyle ); +} + +Ruler::~Ruler() +{ + disposeOnce(); +} + +void Ruler::dispose() +{ + mpSaveData.reset(); + mpDragData.reset(); + mxAccContext.clear(); + Window::dispose(); +} + +void Ruler::ImplVDrawLine(vcl::RenderContext& rRenderContext, tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2) +{ + if ( nX1 < -RULER_CLIP ) + { + nX1 = -RULER_CLIP; + if ( nX2 < -RULER_CLIP ) + return; + } + tools::Long nClip = mnVirWidth + RULER_CLIP; + if ( nX2 > nClip ) + { + nX2 = nClip; + if ( nX1 > nClip ) + return; + } + + if ( mnWinStyle & WB_HORZ ) + rRenderContext.DrawLine( Point( nX1, nY1 ), Point( nX2, nY2 ) ); + else + rRenderContext.DrawLine( Point( nY1, nX1 ), Point( nY2, nX2 ) ); +} + +void Ruler::ImplVDrawRect(vcl::RenderContext& rRenderContext, tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2) +{ + if ( nX1 < -RULER_CLIP ) + { + nX1 = -RULER_CLIP; + if ( nX2 < -RULER_CLIP ) + return; + } + tools::Long nClip = mnVirWidth + RULER_CLIP; + if ( nX2 > nClip ) + { + nX2 = nClip; + if ( nX1 > nClip ) + return; + } + + if ( mnWinStyle & WB_HORZ ) + rRenderContext.DrawRect(tools::Rectangle(nX1, nY1, nX2, nY2)); + else + rRenderContext.DrawRect(tools::Rectangle(nY1, nX1, nY2, nX2)); +} + +void Ruler::ImplVDrawText(vcl::RenderContext& rRenderContext, tools::Long nX, tools::Long nY, const OUString& rText, tools::Long nMin, tools::Long nMax) +{ + tools::Rectangle aRect; + SalLayoutGlyphs* pTextLayout + = lcl_GetRulerTextGlyphs(rRenderContext, rText, maTextGlyphs[rText]); + rRenderContext.GetTextBoundRect(aRect, rText, 0, 0, -1, 0, {}, pTextLayout); + + tools::Long nShiftX = ( aRect.GetWidth() / 2 ) + aRect.Left(); + tools::Long nShiftY = ( aRect.GetHeight() / 2 ) + aRect.Top(); + + if ( (nX > -RULER_CLIP) && (nX < mnVirWidth + RULER_CLIP) && ( nX < nMax - nShiftX ) && ( nX > nMin + nShiftX ) ) + { + if ( mnWinStyle & WB_HORZ ) + rRenderContext.DrawText(Point(nX - nShiftX, nY - nShiftY), rText, 0, -1, nullptr, + nullptr, pTextLayout); + else + rRenderContext.DrawText(Point(nY - nShiftX, nX - nShiftY), rText, 0, -1, nullptr, + nullptr, pTextLayout); + } +} + +void Ruler::ImplInvertLines(vcl::RenderContext& rRenderContext) +{ + // Position lines + if (mpData->pLines.empty() || !mbActive || mbDrag || mbFormat || (mnUpdateFlags & RULER_UPDATE_LINES) ) + return; + + tools::Long nNullWinOff = mpData->nNullVirOff + mnVirOff; + tools::Long nRulX1 = mpData->nRulVirOff + mnVirOff; + tools::Long nRulX2 = nRulX1 + mpData->nRulWidth; + tools::Long nY = (RULER_OFF * 2) + mnVirHeight - 1; + + // Calculate rectangle + tools::Rectangle aRect; + if (mnWinStyle & WB_HORZ) + aRect.SetBottom( nY ); + else + aRect.SetRight( nY ); + + // Draw lines + for (const RulerLine & rLine : mpData->pLines) + { + const tools::Long n = rLine.nPos + nNullWinOff; + if ((n >= nRulX1) && (n < nRulX2)) + { + if (mnWinStyle & WB_HORZ ) + { + aRect.SetLeft( n ); + aRect.SetRight( n ); + } + else + { + aRect.SetTop( n ); + aRect.SetBottom( n ); + } + tools::Rectangle aTempRect = aRect; + + if (mnWinStyle & WB_HORZ) + aTempRect.SetBottom( RULER_OFF - 1 ); + else + aTempRect.SetRight( RULER_OFF - 1 ); + + rRenderContext.Erase(aTempRect); + + if (mnWinStyle & WB_HORZ) + { + aTempRect.SetBottom( aRect.Bottom() ); + aTempRect.SetTop( aTempRect.Bottom() - RULER_OFF + 1 ); + } + else + { + aTempRect.SetRight( aRect.Right() ); + aTempRect.SetLeft( aTempRect.Right() - RULER_OFF + 1 ); + } + rRenderContext.Erase(aTempRect); + GetOutDev()->Invert(aRect); + } + } + mnUpdateFlags = 0; +} + +void Ruler::ImplDrawTicks(vcl::RenderContext& rRenderContext, tools::Long nMin, tools::Long nMax, tools::Long nStart, tools::Long nTop, tools::Long nBottom) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + double nCenter = nTop + ((nBottom - nTop) / 2); + + tools::Long nTickLength3 = (nBottom - nTop) * 0.5; + tools::Long nTickLength2 = nTickLength3 * 0.66; + tools::Long nTickLength1 = nTickLength2 * 0.66; + + tools::Long nScale = ruler_tab.DPIScaleFactor; + tools::Long DPIOffset = nScale - 1; + + double nTick4 = aImplRulerUnitTab[mnUnitIndex].nTick4; + double nTick2 = 0; + double nTickCount = aImplRulerUnitTab[mnUnitIndex].nTick1 / nScale; + double nTickUnit = 0; + tools::Long nTickWidth; + bool bNoTicks = false; + + Size aPixSize = rRenderContext.LogicToPixel(Size(nTick4, nTick4), maMapMode); + + if (mnUnitIndex == RULER_UNIT_CHAR) + { + if (mnCharWidth == 0) + mnCharWidth = 371; + nTick4 = mnCharWidth * 2; + nTick2 = mnCharWidth; + nTickCount = mnCharWidth; + nTickUnit = mnCharWidth; + } + else if (mnUnitIndex == RULER_UNIT_LINE) + { + if (mnLineHeight == 0) + mnLineHeight = 551; + nTick4 = mnLineHeight * 2; + nTick2 = mnLineHeight; + nTickUnit = mnLineHeight; + nTickCount = mnLineHeight; + } + + if (mnWinStyle & WB_HORZ) + { + nTickWidth = aPixSize.Width(); + } + else + { + vcl::Font aFont = rRenderContext.GetFont(); + if (mnWinStyle & WB_RIGHT_ALIGNED) + aFont.SetOrientation(2700_deg10); + else + aFont.SetOrientation(900_deg10); + rRenderContext.SetFont(aFont); + nTickWidth = aPixSize.Height(); + } + + tools::Long nMaxWidth = rRenderContext.PixelToLogic(Size(mpData->nPageWidth, 0), maMapMode).Width(); + if (nMaxWidth < 0) + nMaxWidth = -nMaxWidth; + + if ((mnUnitIndex == RULER_UNIT_CHAR) || (mnUnitIndex == RULER_UNIT_LINE)) + nMaxWidth /= nTickUnit; + else + nMaxWidth /= aImplRulerUnitTab[mnUnitIndex].nTickUnit; + + OUString aNumString = OUString::number(nMaxWidth); + tools::Long nTxtWidth = rRenderContext.GetTextWidth( aNumString ); + const tools::Long nTextOff = 4; + + // Determine the number divider for ruler drawn numbers - means which numbers + // should be shown on the ruler and which should be skipped because the ruler + // is not big enough to draw them + if (nTickWidth < nTxtWidth + nTextOff) + { + // Calculate the scale of the ruler + tools::Long nMulti = 1; + tools::Long nOrgTick4 = nTick4; + + while (nTickWidth < nTxtWidth + nTextOff) + { + tools::Long nOldMulti = nMulti; + if (nTickWidth == 0) + nMulti *= 10; + else if (nMulti < 10) + nMulti++; + else if (nMulti < 100) + nMulti += 10; + else if (nMulti < 1000) + nMulti += 100; + else + nMulti += 1000; + + // Overflow - in this case don't draw ticks and exit + if (nMulti < nOldMulti) + { + bNoTicks = true; + break; + } + + nTick4 = nOrgTick4 * nMulti; + aPixSize = rRenderContext.LogicToPixel(Size(nTick4, nTick4), maMapMode); + if (mnWinStyle & WB_HORZ) + nTickWidth = aPixSize.Width(); + else + nTickWidth = aPixSize.Height(); + } + nTickCount = nTick4; + } + else + { + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + } + + if (bNoTicks) + return; + + tools::Long n = 0; + double nTick = 0.0; + double nTick3 = 0; + + if ((mnUnitIndex != RULER_UNIT_CHAR) && (mnUnitIndex != RULER_UNIT_LINE)) + { + nTick2 = aImplRulerUnitTab[mnUnitIndex].nTick2; + nTick3 = aImplRulerUnitTab[mnUnitIndex].nTick3; + } + + Size nTickGapSize; + + nTickGapSize = rRenderContext.LogicToPixel(Size(nTickCount, nTickCount), maMapMode); + tools::Long nTickGap1 = mnWinStyle & WB_HORZ ? nTickGapSize.Width() : nTickGapSize.Height(); + nTickGapSize = rRenderContext.LogicToPixel(Size(nTick2, nTick2), maMapMode); + tools::Long nTickGap2 = mnWinStyle & WB_HORZ ? nTickGapSize.Width() : nTickGapSize.Height(); + nTickGapSize = rRenderContext.LogicToPixel(Size(nTick3, nTick3), maMapMode); + tools::Long nTickGap3 = mnWinStyle & WB_HORZ ? nTickGapSize.Width() : nTickGapSize.Height(); + + while (((nStart - n) >= nMin) || ((nStart + n) <= nMax)) + { + // Null point + if (nTick == 0.0) + { + if (nStart > nMin) + { + // 0 is only painted when Margin1 is not equal to zero + if ((mpData->nMargin1Style & RulerMarginStyle::Invisible) || (mpData->nMargin1 != 0)) + { + aNumString = "0"; + ImplVDrawText(rRenderContext, nStart, nCenter, aNumString); + } + } + } + else + { + aPixSize = rRenderContext.LogicToPixel(Size(nTick, nTick), maMapMode); + + if (mnWinStyle & WB_HORZ) + n = aPixSize.Width(); + else + n = aPixSize.Height(); + + // Tick4 - Output (Text) + double aStep = nTick / nTick4; + double aRest = std::abs(aStep - std::floor(aStep)); + double nAcceptanceDelta = 0.0001; + rRenderContext.SetFillColor(rStyleSettings.GetShadowColor()); + + if (aRest < nAcceptanceDelta) + { + if ((mnUnitIndex == RULER_UNIT_CHAR) || (mnUnitIndex == RULER_UNIT_LINE)) + aNumString = OUString::number(nTick / nTickUnit); + else + aNumString = OUString::number(nTick / aImplRulerUnitTab[mnUnitIndex].nTickUnit); + + tools::Long nHorizontalLocation = nStart + n; + ImplVDrawText(rRenderContext, nHorizontalLocation, nCenter, aNumString, nMin, nMax); + + if (nMin < nHorizontalLocation && nHorizontalLocation < nMax) + { + ImplVDrawRect(rRenderContext, nHorizontalLocation, nBottom - 1 * nScale, nHorizontalLocation + DPIOffset, nBottom); + ImplVDrawRect(rRenderContext, nHorizontalLocation, nTop, nHorizontalLocation + DPIOffset, nTop + 1 * nScale); + } + + nHorizontalLocation = nStart - n; + ImplVDrawText(rRenderContext, nHorizontalLocation, nCenter, aNumString, nMin, nMax); + + if (nMin < nHorizontalLocation && nHorizontalLocation < nMax) + { + ImplVDrawRect(rRenderContext, nHorizontalLocation, nBottom, + nHorizontalLocation + DPIOffset, nBottom - 1 * nScale); + ImplVDrawRect(rRenderContext, nHorizontalLocation, nTop, + nHorizontalLocation + DPIOffset, nTop + 1 * nScale); + } + } + // Tick/Tick2 - Output (Strokes) + else + { + tools::Long nTickLength = nTickLength1; + + aStep = (nTick / nTick2); + aRest = std::abs(aStep - std::floor(aStep)); + if (aRest < nAcceptanceDelta) + nTickLength = nTickLength2; + + aStep = (nTick / nTick3); + aRest = std::abs(aStep - std::floor(aStep)); + if (aRest < nAcceptanceDelta ) + nTickLength = nTickLength3; + + if ((nTickLength == nTickLength1 && nTickGap1 > 6) || + (nTickLength == nTickLength2 && nTickGap2 > 6) || + (nTickLength == nTickLength3 && nTickGap3 > 6)) + { + tools::Long nT1 = nCenter - (nTickLength / 2.0); + tools::Long nT2 = nT1 + nTickLength - 1; + tools::Long nT; + + nT = nStart + n; + + if (nT < nMax) + ImplVDrawRect(rRenderContext, nT, nT1, nT + DPIOffset, nT2); + nT = nStart - n; + if (nT > nMin) + ImplVDrawRect(rRenderContext, nT, nT1, nT + DPIOffset, nT2); + } + } + } + nTick += nTickCount; + } +} + +void Ruler::ImplDrawBorders(vcl::RenderContext& rRenderContext, tools::Long nMin, tools::Long nMax, tools::Long nVirTop, tools::Long nVirBottom) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + tools::Long n; + tools::Long n1; + tools::Long n2; + tools::Long nTemp1; + tools::Long nTemp2; + + for (std::vector<RulerBorder>::size_type i = 0; i < mpData->pBorders.size(); i++) + { + if (mpData->pBorders[i].nStyle & RulerBorderStyle::Invisible) + continue; + + n1 = mpData->pBorders[i].nPos + mpData->nNullVirOff; + n2 = n1 + mpData->pBorders[i].nWidth; + + if (((n1 >= nMin) && (n1 <= nMax)) || ((n2 >= nMin) && (n2 <= nMax))) + { + if ((n2 - n1) > 3) + { + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(rStyleSettings.GetFaceColor()); + ImplVDrawRect(rRenderContext, n1, nVirTop, n2, nVirBottom); + + rRenderContext.SetLineColor(rStyleSettings.GetLightColor()); + ImplVDrawLine(rRenderContext, n1 + 1, nVirTop, n1 + 1, nVirBottom); + ImplVDrawLine(rRenderContext, n1, nVirTop, n2, nVirTop); + + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + ImplVDrawLine(rRenderContext, n1, nVirTop, n1, nVirBottom); + ImplVDrawLine(rRenderContext, n1, nVirBottom, n2, nVirBottom); + ImplVDrawLine(rRenderContext, n2 - 1, nVirTop, n2 - 1, nVirBottom); + + rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor()); + ImplVDrawLine(rRenderContext, n2, nVirTop, n2, nVirBottom); + + if (mpData->pBorders[i].nStyle & RulerBorderStyle::Variable) + { + if (n2 - n1 > RULER_VAR_SIZE + 4) + { + nTemp1 = n1 + (((n2 - n1 + 1) - RULER_VAR_SIZE) / 2); + nTemp2 = nVirTop + (((nVirBottom - nVirTop + 1) - RULER_VAR_SIZE) / 2); + tools::Long nTemp3 = nTemp1 + RULER_VAR_SIZE - 1; + tools::Long nTemp4 = nTemp2 + RULER_VAR_SIZE - 1; + tools::Long nTempY = nTemp2; + + rRenderContext.SetLineColor(rStyleSettings.GetLightColor()); + while (nTempY <= nTemp4) + { + ImplVDrawLine(rRenderContext, nTemp1, nTempY, nTemp3, nTempY); + nTempY += 2; + } + + nTempY = nTemp2 + 1; + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + while (nTempY <= nTemp4) + { + ImplVDrawLine(rRenderContext, nTemp1, nTempY, nTemp3, nTempY); + nTempY += 2; + } + } + } + + if (mpData->pBorders[i].nStyle & RulerBorderStyle::Sizeable) + { + if (n2 - n1 > RULER_VAR_SIZE + 10) + { + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + ImplVDrawLine(rRenderContext, n1 + 4, nVirTop + 3, n1 + 4, nVirBottom - 3); + ImplVDrawLine(rRenderContext, n2 - 5, nVirTop + 3, n2 - 5, nVirBottom - 3); + rRenderContext.SetLineColor(rStyleSettings.GetLightColor()); + ImplVDrawLine(rRenderContext, n1 + 5, nVirTop + 3, n1 + 5, nVirBottom - 3); + ImplVDrawLine(rRenderContext, n2 - 4, nVirTop + 3, n2 - 4, nVirBottom - 3); + } + } + } + else + { + n = n1 + ((n2 - n1) / 2); + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + + ImplVDrawLine(rRenderContext, n - 1, nVirTop, n - 1, nVirBottom); + ImplVDrawLine(rRenderContext, n + 1, nVirTop, n + 1, nVirBottom); + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(rStyleSettings.GetWindowColor()); + ImplVDrawRect(rRenderContext, n, nVirTop, n, nVirBottom); + } + } + } +} + +void Ruler::ImplDrawIndent(vcl::RenderContext& rRenderContext, const tools::Polygon& rPoly, bool bIsHit) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor()); + rRenderContext.SetFillColor(bIsHit ? rStyleSettings.GetDarkShadowColor() : rStyleSettings.GetWorkspaceColor()); + tools::Polygon aPolygon(rPoly); + aPolygon.Optimize(PolyOptimizeFlags::CLOSE); + rRenderContext.DrawPolygon(aPolygon); +} + +void Ruler::ImplDrawIndents(vcl::RenderContext& rRenderContext, tools::Long nMin, tools::Long nMax, tools::Long nVirTop, tools::Long nVirBottom) +{ + tools::Long n; + tools::Long nIndentHeight = (mnVirHeight / 2) - 1; + tools::Long nIndentWidth2 = nIndentHeight-3; + + tools::Polygon aPoly(5); + + for (std::vector<RulerIndent>::size_type j = 0; j < mpData->pIndents.size(); j++) + { + if (mpData->pIndents[j].bInvisible) + continue; + + RulerIndentStyle nIndentStyle = mpData->pIndents[j].nStyle; + + n = mpData->pIndents[j].nPos+mpData->nNullVirOff; + + if ((n >= nMin) && (n <= nMax)) + { + if (nIndentStyle == RulerIndentStyle::Bottom) + { + aPoly.SetPoint(Point(n + 0, nVirBottom - nIndentHeight), 0); + aPoly.SetPoint(Point(n - nIndentWidth2, nVirBottom - 3), 1); + aPoly.SetPoint(Point(n - nIndentWidth2, nVirBottom), 2); + aPoly.SetPoint(Point(n + nIndentWidth2, nVirBottom), 3); + aPoly.SetPoint(Point(n + nIndentWidth2, nVirBottom - 3), 4); + } + else + { + aPoly.SetPoint(Point(n + 0, nVirTop + nIndentHeight), 0); + aPoly.SetPoint(Point(n - nIndentWidth2, nVirTop + 3), 1); + aPoly.SetPoint(Point(n - nIndentWidth2, nVirTop), 2); + aPoly.SetPoint(Point(n + nIndentWidth2, nVirTop), 3); + aPoly.SetPoint(Point(n + nIndentWidth2, nVirTop + 3), 4); + } + + if (0 == (mnWinStyle & WB_HORZ)) + { + Point aTmp; + for (sal_uInt16 i = 0; i < 5; i++) + { + aTmp = aPoly[i]; + Point aSet(nVirBottom - aTmp.Y(), aTmp.X()); + aPoly[i] = aSet; + } + } + bool bIsHit = false; + if (mxCurrentHitTest != nullptr && mxCurrentHitTest->eType == RulerType::Indent) + { + bIsHit = mxCurrentHitTest->nAryPos == j; + } + else if(mbDrag && meDragType == RulerType::Indent) + { + bIsHit = mnDragAryPos == j; + } + ImplDrawIndent(rRenderContext, aPoly, bIsHit); + } + } +} + +static void ImplCenterTabPos(Point& rPos, sal_uInt16 nTabStyle) +{ + bool bRTL = 0 != (nTabStyle & RULER_TAB_RTL); + nTabStyle &= RULER_TAB_STYLE; + rPos.AdjustY(ruler_tab.height/2 ); + + if ( (!bRTL && nTabStyle == RULER_TAB_LEFT) || + ( bRTL && nTabStyle == RULER_TAB_RIGHT) ) + { + rPos.AdjustX( -(ruler_tab.width / 2) ); + } + else if ( (!bRTL && nTabStyle == RULER_TAB_RIGHT) || + ( bRTL && nTabStyle == RULER_TAB_LEFT) ) + { + rPos.AdjustX(ruler_tab.width / 2 ); + } +} + +static void lcl_RotateRect_Impl(tools::Rectangle& rRect, const tools::Long nReference, bool bRightAligned) +{ + if (rRect.IsEmpty()) + return; + + tools::Rectangle aTmp(rRect); + rRect.SetTop( aTmp.Left() ); + rRect.SetBottom( aTmp.Right() ); + rRect.SetLeft( aTmp.Top() ); + rRect.SetRight( aTmp.Bottom() ); + + if (bRightAligned) + { + tools::Long nRef = 2 * nReference; + rRect.SetLeft( nRef - rRect.Left() ); + rRect.SetRight( nRef - rRect.Right() ); + } +} + +static void ImplDrawRulerTab(vcl::RenderContext& rRenderContext, const Point& rPos, + sal_uInt16 nStyle, WinBits nWinBits) +{ + if (nStyle & RULER_STYLE_INVISIBLE) + return; + + sal_uInt16 nTabStyle = nStyle & RULER_TAB_STYLE; + bool bRTL = 0 != (nStyle & RULER_TAB_RTL); + + // Scale by the screen DPI scaling factor + // However when doing this some of the rectangles + // drawn become asymmetric due to the +1 offsets + sal_uInt16 DPIOffset = rRenderContext.GetDPIScaleFactor() - 1; + + // A tabstop is drawn using three rectangles + tools::Rectangle aRect1; // A horizontal short line + tools::Rectangle aRect2; // A vertical short line + tools::Rectangle aRect3; // A small square + + aRect3.SetEmpty(); + + if (nTabStyle == RULER_TAB_DEFAULT) + { + aRect1.SetLeft( rPos.X() - ruler_tab.dwidth2 + 1 ); + aRect1.SetTop( rPos.Y() - ruler_tab.dheight2 + 1 ); + aRect1.SetRight( rPos.X() - ruler_tab.dwidth2 + ruler_tab.dwidth + DPIOffset ); + aRect1.SetBottom( rPos.Y() ); + + aRect2.SetLeft( rPos.X() - ruler_tab.dwidth2 + ruler_tab.dwidth3 ); + aRect2.SetTop( rPos.Y() - ruler_tab.dheight + 1 ); + aRect2.SetRight( rPos.X() - ruler_tab.dwidth2 + ruler_tab.dwidth3 + ruler_tab.dwidth4 - 1 ); + aRect2.SetBottom( rPos.Y() ); + + } + else if ((!bRTL && nTabStyle == RULER_TAB_LEFT) || (bRTL && nTabStyle == RULER_TAB_RIGHT)) + { + aRect1.SetLeft( rPos.X() ); + aRect1.SetTop( rPos.Y() - ruler_tab.height2 + 1 ); + aRect1.SetRight( rPos.X() + ruler_tab.width - 1 ); + aRect1.SetBottom( rPos.Y() ); + + aRect2.SetLeft( rPos.X() ); + aRect2.SetTop( rPos.Y() - ruler_tab.height + 1 ); + aRect2.SetRight( rPos.X() + ruler_tab.width2 - 1 ); + aRect2.SetBottom( rPos.Y() ); + } + else if ((!bRTL && nTabStyle == RULER_TAB_RIGHT) || (bRTL && nTabStyle == RULER_TAB_LEFT)) + { + aRect1.SetLeft( rPos.X() - ruler_tab.width + 1 ); + aRect1.SetTop( rPos.Y() - ruler_tab.height2 + 1 ); + aRect1.SetRight( rPos.X() ); + aRect1.SetBottom( rPos.Y() ); + + aRect2.SetLeft( rPos.X() - ruler_tab.width2 + 1 ); + aRect2.SetTop( rPos.Y() - ruler_tab.height + 1 ); + aRect2.SetRight( rPos.X() ); + aRect2.SetBottom( rPos.Y() ); + } + else + { + aRect1.SetLeft( rPos.X() - ruler_tab.cwidth2 + 1 ); + aRect1.SetTop( rPos.Y() - ruler_tab.height2 + 1 ); + aRect1.SetRight( rPos.X() - ruler_tab.cwidth2 + ruler_tab.cwidth + DPIOffset ); + aRect1.SetBottom( rPos.Y() ); + + aRect2.SetLeft( rPos.X() - ruler_tab.cwidth2 + ruler_tab.cwidth3 ); + aRect2.SetTop( rPos.Y() - ruler_tab.height + 1 ); + aRect2.SetRight( rPos.X() - ruler_tab.cwidth2 + ruler_tab.cwidth3 + ruler_tab.cwidth4 - 1 ); + aRect2.SetBottom( rPos.Y() ); + + if (nTabStyle == RULER_TAB_DECIMAL) + { + aRect3.SetLeft( rPos.X() - ruler_tab.cwidth2 + ruler_tab.cwidth - 1 ); + aRect3.SetTop( rPos.Y() - ruler_tab.height + 1 + 1 - DPIOffset ); + aRect3.SetRight( rPos.X() - ruler_tab.cwidth2 + ruler_tab.cwidth + DPIOffset ); + aRect3.SetBottom( rPos.Y() - ruler_tab.height + 1 + 2 ); + } + } + if (0 == (nWinBits & WB_HORZ)) + { + bool bRightAligned = 0 != (nWinBits & WB_RIGHT_ALIGNED); + lcl_RotateRect_Impl(aRect1, rPos.Y(), bRightAligned); + lcl_RotateRect_Impl(aRect2, rPos.Y(), bRightAligned); + lcl_RotateRect_Impl(aRect3, rPos.Y(), bRightAligned); + } + rRenderContext.DrawRect(aRect1); + rRenderContext.DrawRect(aRect2); + + if (!aRect3.IsEmpty()) + rRenderContext.DrawRect(aRect3); +} + +void Ruler::ImplDrawTab(vcl::RenderContext& rRenderContext, const Point& rPos, sal_uInt16 nStyle) +{ + if (nStyle & RULER_STYLE_INVISIBLE) + return; + + rRenderContext.SetLineColor(); + + if (nStyle & RULER_STYLE_DONTKNOW) + rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetFaceColor()); + else + rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetDarkShadowColor()); + + if (mpData->bTextRTL) + nStyle |= RULER_TAB_RTL; + + ImplDrawRulerTab(rRenderContext, rPos, nStyle, GetStyle()); +} + +void Ruler::ImplDrawTabs(vcl::RenderContext& rRenderContext, tools::Long nMin, tools::Long nMax, tools::Long nVirTop, tools::Long nVirBottom) +{ + for (const RulerTab & rTab : mpData->pTabs) + { + if (rTab.nStyle & RULER_STYLE_INVISIBLE) + continue; + + tools::Long aPosition; + aPosition = rTab.nPos; + aPosition += +mpData->nNullVirOff; + tools::Long nTopBottom = (GetStyle() & WB_RIGHT_ALIGNED) ? nVirTop : nVirBottom; + if (nMin <= aPosition && aPosition <= nMax) + ImplDrawTab(rRenderContext, Point( aPosition, nTopBottom ), rTab.nStyle); + } +} + +static int adjustSize(int nOrig) +{ + if (nOrig <= 0) + return 0; + + // make sure we return an odd number, that looks better in the ruler + return ( (3*nOrig) / 8) * 2 + 1; +} + +void Ruler::ApplySettings(vcl::RenderContext& rRenderContext) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + vcl::Font aFont = rStyleSettings.GetToolFont(); + // make the font a bit smaller than default + Size aSize(adjustSize(aFont.GetFontSize().Width()), adjustSize(aFont.GetFontSize().Height())); + aFont.SetFontSize(aSize); + + ApplyControlFont(rRenderContext, aFont); + + ApplyControlForeground(*GetOutDev(), rStyleSettings.GetDarkShadowColor()); + SetTextFillColor(); + + Color aColor; + svtools::ColorConfig aColorConfig; + aColor = aColorConfig.GetColorValue(svtools::APPBACKGROUND).nColor; + ApplyControlBackground(rRenderContext, aColor); + // A hack to get it to change the non-ruler application background to change immediately + if (aColor != maVirDev->GetBackground().GetColor()) + { + maVirDev->SetBackground(aColor); + Resize(); + } +} + +void Ruler::ImplInitSettings(bool bFont, bool bForeground, bool bBackground) +{ + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + + if (bFont) + { + vcl::Font aFont = rStyleSettings.GetToolFont(); + // make the font a bit smaller than default + Size aSize(adjustSize(aFont.GetFontSize().Width()), adjustSize(aFont.GetFontSize().Height())); + aFont.SetFontSize(aSize); + + ApplyControlFont(*GetOutDev(), aFont); + } + + if (bForeground || bFont) + { + ApplyControlForeground(*GetOutDev(), rStyleSettings.GetDarkShadowColor()); + SetTextFillColor(); + } + + if (bBackground) + { + Color aColor; + svtools::ColorConfig aColorConfig; + aColor = aColorConfig.GetColorValue(svtools::APPBACKGROUND).nColor; + ApplyControlBackground(*GetOutDev(), aColor); + } + + maVirDev->SetSettings( GetSettings() ); + maVirDev->SetBackground( GetBackground() ); + vcl::Font aFont = GetFont(); + + if (mnWinStyle & WB_VERT) + aFont.SetOrientation(900_deg10); + + maVirDev->SetFont(aFont); + maVirDev->SetTextColor(GetTextColor()); + maVirDev->SetTextFillColor(GetTextFillColor()); +} + +void Ruler::ImplCalc() +{ + // calculate offset + mpData->nRulVirOff = mnWinOff + mpData->nPageOff; + if ( mpData->nRulVirOff > mnVirOff ) + mpData->nRulVirOff -= mnVirOff; + else + mpData->nRulVirOff = 0; + tools::Long nRulWinOff = mpData->nRulVirOff+mnVirOff; + + // calculate non-visual part of the page + tools::Long nNotVisPageWidth; + if ( mpData->nPageOff < 0 ) + { + nNotVisPageWidth = -(mpData->nPageOff); + if ( nRulWinOff < mnWinOff ) + nNotVisPageWidth -= mnWinOff-nRulWinOff; + } + else + nNotVisPageWidth = 0; + + // calculate width + if ( mnWinStyle & WB_HORZ ) + { + if ( mbAutoWinWidth ) + mnWinWidth = mnWidth - mnVirOff; + if ( mpData->bAutoPageWidth ) + mpData->nPageWidth = mnWinWidth; + mpData->nRulWidth = std::min( mnWinWidth, mpData->nPageWidth-nNotVisPageWidth ); + if ( nRulWinOff+mpData->nRulWidth > mnWidth ) + mpData->nRulWidth = mnWidth-nRulWinOff; + } + else + { + if ( mbAutoWinWidth ) + mnWinWidth = mnHeight - mnVirOff; + if ( mpData->bAutoPageWidth ) + mpData->nPageWidth = mnWinWidth; + mpData->nRulWidth = std::min( mnWinWidth, mpData->nPageWidth-nNotVisPageWidth ); + if ( nRulWinOff+mpData->nRulWidth > mnHeight ) + mpData->nRulWidth = mnHeight-nRulWinOff; + } + + mbCalc = false; +} + +void Ruler::ImplFormat(vcl::RenderContext const & rRenderContext) +{ + // if already formatted, don't do it again + if (!mbFormat) + return; + + // don't do anything if the window still has no size + if (!mnVirWidth) + return; + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + tools::Long nP1; // pixel position of Page1 + tools::Long nP2; // pixel position of Page2 + tools::Long nM1; // pixel position of Margin1 + tools::Long nM2; // pixel position of Margin2 + tools::Long nVirTop; // top/left corner + tools::Long nVirBottom; // bottom/right corner + tools::Long nVirLeft; // left/top corner + tools::Long nVirRight; // right/bottom corner + tools::Long nNullVirOff; // for faster calculation + + // calculate values + if (mbCalc) + ImplCalc(); + + mpData->nNullVirOff = mnWinOff + mpData->nPageOff + mpData->nNullOff - mnVirOff; + + nNullVirOff = mpData->nNullVirOff; + nVirLeft = mpData->nRulVirOff; + nVirRight = nVirLeft + mpData->nRulWidth - 1; + nVirTop = 0; + nVirBottom = mnVirHeight - 1; + + if (!IsReallyVisible()) + return; + + Size aVirDevSize; + + // initialize VirtualDevice + if (mnWinStyle & WB_HORZ) + { + aVirDevSize.setWidth( mnVirWidth ); + aVirDevSize.setHeight( mnVirHeight ); + } + else + { + aVirDevSize.setHeight( mnVirWidth ); + aVirDevSize.setWidth( mnVirHeight ); + } + if (aVirDevSize != maVirDev->GetOutputSizePixel()) + maVirDev->SetOutputSizePixel(aVirDevSize); + else + maVirDev->Erase(); + + // calculate margins + if (!(mpData->nMargin1Style & RulerMarginStyle::Invisible)) + { + nM1 = mpData->nMargin1 + nNullVirOff; + if (mpData->bAutoPageWidth) + { + nP1 = nVirLeft; + if (nM1 < nVirLeft) + nP1--; + } + else + nP1 = nNullVirOff - mpData->nNullOff; + } + else + { + nM1 = nVirLeft-1; + nP1 = nM1; + } + if (!(mpData->nMargin2Style & RulerMarginStyle::Invisible)) + { + nM2 = mpData->nMargin2 + nNullVirOff; + if (mpData->bAutoPageWidth) + { + nP2 = nVirRight; + if (nM2 > nVirRight) + nP2++; + } + else + nP2 = nNullVirOff - mpData->nNullOff + mpData->nPageWidth; + if (nM2 > nP2) + nM2 = nP2; + } + else + { + nM2 = nVirRight+1; + nP2 = nM2; + } + + // top/bottom border + maVirDev->SetLineColor(rStyleSettings.GetShadowColor()); + ImplVDrawLine(*maVirDev, nVirLeft, nVirTop + 1, nM1, nVirTop + 1); //top left line + ImplVDrawLine(*maVirDev, nM2, nVirTop + 1, nP2 - 1, nVirTop + 1); //top right line + + nVirTop++; + nVirBottom--; + + // draw margin1, margin2 and in-between + maVirDev->SetLineColor(); + maVirDev->SetFillColor(rStyleSettings.GetDialogColor()); + if (nM1 > nVirLeft) + ImplVDrawRect(*maVirDev, nP1, nVirTop + 1, nM1, nVirBottom); //left gray rectangle + if (nM2 < nP2) + ImplVDrawRect(*maVirDev, nM2, nVirTop + 1, nP2, nVirBottom); //right gray rectangle + if (nM2 - nM1 > 0) + { + maVirDev->SetFillColor(rStyleSettings.GetWindowColor()); + ImplVDrawRect(*maVirDev, nM1 + 1, nVirTop, nM2 - 1, nVirBottom); //center rectangle + } + maVirDev->SetLineColor(rStyleSettings.GetShadowColor()); + if (nM1 > nVirLeft) + { + ImplVDrawLine(*maVirDev, nM1, nVirTop + 1, nM1, nVirBottom); //right line of the left rectangle + ImplVDrawLine(*maVirDev, nP1, nVirBottom, nM1, nVirBottom); //bottom line of the left rectangle + if (nP1 >= nVirLeft) + { + ImplVDrawLine(*maVirDev, nP1, nVirTop + 1, nP1, nVirBottom); //left line of the left rectangle + ImplVDrawLine(*maVirDev, nP1, nVirBottom, nP1 + 1, nVirBottom); //? + } + } + if (nM2 < nP2) + { + ImplVDrawLine(*maVirDev, nM2, nVirBottom, nP2 - 1, nVirBottom); //bottom line of the right rectangle + ImplVDrawLine(*maVirDev, nM2, nVirTop + 1, nM2, nVirBottom); //left line of the right rectangle + if (nP2 <= nVirRight + 1) + ImplVDrawLine(*maVirDev, nP2 - 1, nVirTop + 1, nP2 - 1, nVirBottom); //right line of the right rectangle + } + + tools::Long nMin = nVirLeft; + tools::Long nMax = nP2; + tools::Long nStart = 0; + + if (mpData->bTextRTL) + nStart = mpData->nRightFrameMargin + nNullVirOff; + else + nStart = mpData->nLeftFrameMargin + nNullVirOff; + + if (nP1 > nVirLeft) + nMin++; + + if (nP2 < nVirRight) + nMax--; + + // Draw captions + ImplDrawTicks(*maVirDev, nMin, nMax, nStart, nVirTop, nVirBottom); + + // Draw borders + if (!mpData->pBorders.empty()) + ImplDrawBorders(*maVirDev, nVirLeft, nP2, nVirTop, nVirBottom); + + // Draw indents + if (!mpData->pIndents.empty()) + ImplDrawIndents(*maVirDev, nVirLeft, nP2, nVirTop - 1, nVirBottom + 1); + + // Tabs + if (!mpData->pTabs.empty()) + ImplDrawTabs(*maVirDev, nVirLeft, nP2, nVirTop-1, nVirBottom + 1); + + mbFormat = false; +} + +void Ruler::ImplInitExtraField( bool bUpdate ) +{ + Size aWinSize = GetOutputSizePixel(); + + // extra field evaluate + if ( mnWinStyle & WB_EXTRAFIELD ) + { + maExtraRect.SetLeft( RULER_OFF ); + maExtraRect.SetTop( RULER_OFF ); + maExtraRect.SetRight( RULER_OFF + mnVirHeight - 1 ); + maExtraRect.SetBottom( RULER_OFF + mnVirHeight - 1 ); + if(mpData->bTextRTL) + { + if(mnWinStyle & WB_HORZ) + maExtraRect.Move(aWinSize.Width() - maExtraRect.GetWidth() - maExtraRect.Left(), 0); + else + maExtraRect.Move(0, aWinSize.Height() - maExtraRect.GetHeight() - maExtraRect.Top()); + mnVirOff = 0; + } + else + mnVirOff = maExtraRect.Right()+1; + + } + else + { + maExtraRect.SetEmpty(); + mnVirOff = 0; + } + + // mnVirWidth depends on mnVirOff + if ( (mnVirWidth > RULER_MIN_SIZE) || + ((aWinSize.Width() > RULER_MIN_SIZE) && (aWinSize.Height() > RULER_MIN_SIZE)) ) + { + if ( mnWinStyle & WB_HORZ ) + mnVirWidth = aWinSize.Width()-mnVirOff; + else + mnVirWidth = aWinSize.Height()-mnVirOff; + + if ( mnVirWidth < RULER_MIN_SIZE ) + mnVirWidth = 0; + } + + if ( bUpdate ) + { + mbCalc = true; + mbFormat = true; + Invalidate(); + } +} + +void Ruler::ImplDraw(vcl::RenderContext& rRenderContext) +{ + if (mbFormat) + { + ImplFormat(rRenderContext); + } + + if (!IsReallyVisible()) + return; + + // output the ruler to the virtual device + Point aOffPos; + Size aVirDevSize = maVirDev->GetOutputSizePixel(); + + if (mnWinStyle & WB_HORZ) + { + aOffPos.setX( mnVirOff ); + if (mpData->bTextRTL) + aVirDevSize.AdjustWidth( -(maExtraRect.GetWidth()) ); + + aOffPos.setY( RULER_OFF ); + } + else + { + aOffPos.setX( RULER_OFF ); + aOffPos.setY( mnVirOff ); + } + rRenderContext.DrawOutDev(aOffPos, aVirDevSize, Point(), aVirDevSize, *maVirDev); + + // redraw positionlines + ImplInvertLines(rRenderContext); +} + +void Ruler::ImplDrawExtra(vcl::RenderContext& rRenderContext) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + tools::Rectangle aRect = maExtraRect; + bool bEraseRect = false; + + aRect.AdjustLeft(2 ); + aRect.AdjustTop(2 ); + aRect.AdjustRight( -2 ); + aRect.AdjustBottom( -2 ); + + if (mnExtraStyle & RULER_STYLE_HIGHLIGHT) + { + rRenderContext.SetFillColor(rStyleSettings.GetCheckedColor()); + bEraseRect = true; + } + + if (bEraseRect) + { + rRenderContext.SetLineColor(); + rRenderContext.DrawRect(aRect); + } + + // output content + if (meExtraType == RulerExtra::NullOffset) + { + rRenderContext.SetLineColor(rStyleSettings.GetButtonTextColor()); + rRenderContext.DrawLine(Point(aRect.Left() + 1, aRect.Top() + 4), + Point(aRect.Right() - 1, aRect.Top() + 4)); + rRenderContext.DrawLine(Point(aRect.Left() + 4, aRect.Top() + 1), + Point(aRect.Left() + 4, aRect.Bottom() - 1)); + } + else if (meExtraType == RulerExtra::Tab) + { + sal_uInt16 nTabStyle = mnExtraStyle & RULER_TAB_STYLE; + if (mpData->bTextRTL) + nTabStyle |= RULER_TAB_RTL; + Point aCenter = aRect.Center(); + Point aDraw(aCenter); + ImplCenterTabPos(aDraw, nTabStyle); + WinBits nWinBits = GetStyle(); + if (0 == (nWinBits & WB_HORZ)) + { + if ((nWinBits & WB_RIGHT_ALIGNED) != 0) + aDraw.setY( 2 * aCenter.Y() - aDraw.Y() ); + + if (mpData->bTextRTL) + { + tools::Long nTemp = aDraw.X(); + aDraw.setX( aDraw.Y() ); + aDraw.setY( nTemp ); + } + } + ImplDrawTab(rRenderContext, aDraw, nTabStyle); + } +} + +void Ruler::ImplUpdate( bool bMustCalc ) +{ + // clear lines in this place so they aren't considered at recalculation + if (!mbFormat) + Invalidate(InvalidateFlags::NoErase); + + // set flags + if (bMustCalc) + mbCalc = true; + mbFormat = true; + + // abort if we are dragging as drag-handler will update the ruler after drag is finished + if (mbDrag) + return; + + // otherwise trigger update + if (IsReallyVisible() && IsUpdateMode()) + { + Invalidate(InvalidateFlags::NoErase); + } +} + +bool Ruler::ImplDoHitTest( const Point& rPos, RulerSelection* pHitTest, + bool bRequireStyle, RulerIndentStyle nRequiredStyle ) const +{ + sal_Int32 i; + sal_uInt16 nStyle; + tools::Long nHitBottom; + tools::Long nX; + tools::Long nY; + tools::Long n1; + + if ( !mbActive ) + return false; + + // determine positions + bool bIsHori = 0 != (mnWinStyle & WB_HORZ); + if ( bIsHori ) + { + nX = rPos.X(); + nY = rPos.Y(); + } + else + { + nX = rPos.Y(); + nY = rPos.X(); + } + nHitBottom = mnVirHeight + (RULER_OFF * 2); + + // #i32608# + pHitTest->nAryPos = 0; + pHitTest->mnDragSize = RulerDragSize::Move; + pHitTest->bSize = false; + pHitTest->bSizeBar = false; + + // so that leftover tabs and indents are taken into account + tools::Long nXExtraOff; + if ( !mpData->pTabs.empty() || !mpData->pIndents.empty() ) + nXExtraOff = (mnVirHeight / 2) - 4; + else + nXExtraOff = 0; + + // test if outside + nX -= mnVirOff; + if ( (nX < mpData->nRulVirOff - nXExtraOff) || + (nX > mpData->nRulVirOff + mpData->nRulWidth + nXExtraOff) || + (nY < 0) || + (nY > nHitBottom) ) + { + pHitTest->nPos = 0; + pHitTest->eType = RulerType::Outside; + return false; + } + + nX -= mpData->nNullVirOff; + pHitTest->nPos = nX; + pHitTest->eType = RulerType::DontKnow; + + // first test the tabs + tools::Rectangle aRect; + if ( !mpData->pTabs.empty() ) + { + aRect.SetBottom( nHitBottom ); + aRect.SetTop( aRect.Bottom() - ruler_tab.height - RULER_OFF ); + + for ( i = mpData->pTabs.size() - 1; i >= 0; i-- ) + { + nStyle = mpData->pTabs[i].nStyle; + if ( !(nStyle & RULER_STYLE_INVISIBLE) ) + { + nStyle &= RULER_TAB_STYLE; + + // default tabs are only shown (no action) + if ( nStyle != RULER_TAB_DEFAULT ) + { + n1 = mpData->pTabs[i].nPos; + + if ( nStyle == RULER_TAB_LEFT ) + { + aRect.SetLeft( n1 ); + aRect.SetRight( n1 + ruler_tab.width - 1 ); + } + else if ( nStyle == RULER_TAB_RIGHT ) + { + aRect.SetRight( n1 ); + aRect.SetLeft( n1 - ruler_tab.width - 1 ); + } + else + { + aRect.SetLeft( n1 - ruler_tab.cwidth2 + 1 ); + aRect.SetRight( n1 - ruler_tab.cwidth2 + ruler_tab.cwidth ); + } + + if ( aRect.Contains( Point( nX, nY ) ) ) + { + pHitTest->eType = RulerType::Tab; + pHitTest->nAryPos = i; + return true; + } + } + } + } + } + + // Indents + if ( !mpData->pIndents.empty() ) + { + tools::Long nIndentHeight = (mnVirHeight / 2) - 1; + tools::Long nIndentWidth2 = nIndentHeight - 3; + + for ( i = mpData->pIndents.size(); i; i-- ) + { + RulerIndentStyle nIndentStyle = mpData->pIndents[i-1].nStyle; + if ( (! bRequireStyle || nIndentStyle == nRequiredStyle) && + !mpData->pIndents[i-1].bInvisible ) + { + n1 = mpData->pIndents[i-1].nPos; + + if ( (nIndentStyle == RulerIndentStyle::Bottom) != !bIsHori ) + { + aRect.SetLeft( n1-nIndentWidth2 ); + aRect.SetRight( n1+nIndentWidth2 ); + aRect.SetTop( nHitBottom-nIndentHeight-RULER_OFF+1 ); + aRect.SetBottom( nHitBottom ); + } + else + { + aRect.SetLeft( n1-nIndentWidth2 ); + aRect.SetRight( n1+nIndentWidth2 ); + aRect.SetTop( 0 ); + aRect.SetBottom( nIndentHeight+RULER_OFF-1 ); + } + + if ( aRect.Contains( Point( nX, nY ) ) ) + { + pHitTest->eType = RulerType::Indent; + pHitTest->nAryPos = i-1; + return true; + } + } + } + } + + // test the borders + int nBorderTolerance = 1; + if(pHitTest->bExpandTest) + { + nBorderTolerance++; + } + + for ( i = mpData->pBorders.size(); i; i-- ) + { + n1 = mpData->pBorders[i-1].nPos; + tools::Long n2 = n1 + mpData->pBorders[i-1].nWidth; + + // borders have at least 3 pixel padding + if ( !mpData->pBorders[i-1].nWidth ) + { + n1 -= nBorderTolerance; + n2 += nBorderTolerance; + + } + + if ( (nX >= n1) && (nX <= n2) ) + { + RulerBorderStyle nBorderStyle = mpData->pBorders[i-1].nStyle; + if ( !(nBorderStyle & RulerBorderStyle::Invisible) ) + { + pHitTest->eType = RulerType::Border; + pHitTest->nAryPos = i-1; + + if ( !(nBorderStyle & RulerBorderStyle::Sizeable) ) + { + if ( nBorderStyle & RulerBorderStyle::Moveable ) + { + pHitTest->bSizeBar = true; + pHitTest->mnDragSize = RulerDragSize::Move; + } + } + else + { + tools::Long nMOff = RULER_MOUSE_BORDERWIDTH; + while ( nMOff*2 >= (n2-n1-RULER_MOUSE_BORDERMOVE) ) + { + if ( nMOff < 2 ) + { + nMOff = 0; + break; + } + else + nMOff--; + } + + if ( nX <= n1+nMOff ) + { + pHitTest->bSize = true; + pHitTest->mnDragSize = RulerDragSize::N1; + } + else if ( nX >= n2-nMOff ) + { + pHitTest->bSize = true; + pHitTest->mnDragSize = RulerDragSize::N2; + } + else + { + if ( nBorderStyle & RulerBorderStyle::Moveable ) + { + pHitTest->bSizeBar = true; + pHitTest->mnDragSize = RulerDragSize::Move; + } + } + } + + return true; + } + } + } + + // Margins + int nMarginTolerance = pHitTest->bExpandTest ? nBorderTolerance : RULER_MOUSE_MARGINWIDTH; + + if ( (mpData->nMargin1Style & (RulerMarginStyle::Sizeable | RulerMarginStyle::Invisible)) == RulerMarginStyle::Sizeable ) + { + n1 = mpData->nMargin1; + if ( (nX >= n1 - nMarginTolerance) && (nX <= n1 + nMarginTolerance) ) + { + pHitTest->eType = RulerType::Margin1; + pHitTest->bSize = true; + return true; + } + } + if ( (mpData->nMargin2Style & (RulerMarginStyle::Sizeable | RulerMarginStyle::Invisible)) == RulerMarginStyle::Sizeable ) + { + n1 = mpData->nMargin2; + if ( (nX >= n1 - nMarginTolerance) && (nX <= n1 + nMarginTolerance) ) + { + pHitTest->eType = RulerType::Margin2; + pHitTest->bSize = true; + return true; + } + } + + // test tabs again + if ( !mpData->pTabs.empty() ) + { + aRect.SetTop( RULER_OFF ); + aRect.SetBottom( nHitBottom ); + + for ( i = mpData->pTabs.size() - 1; i >= 0; i-- ) + { + nStyle = mpData->pTabs[i].nStyle; + if ( !(nStyle & RULER_STYLE_INVISIBLE) ) + { + nStyle &= RULER_TAB_STYLE; + + // default tabs are only shown (no action) + if ( nStyle != RULER_TAB_DEFAULT ) + { + n1 = mpData->pTabs[i].nPos; + + if ( nStyle == RULER_TAB_LEFT ) + { + aRect.SetLeft( n1 ); + aRect.SetRight( n1 + ruler_tab.width - 1 ); + } + else if ( nStyle == RULER_TAB_RIGHT ) + { + aRect.SetRight( n1 ); + aRect.SetLeft( n1 - ruler_tab.width - 1 ); + } + else + { + aRect.SetLeft( n1 - ruler_tab.cwidth2 + 1 ); + aRect.SetRight( n1 - ruler_tab.cwidth2 + ruler_tab.cwidth ); + } + + aRect.AdjustLeft( -1 ); + aRect.AdjustRight( 1 ); + + if ( aRect.Contains( Point( nX, nY ) ) ) + { + pHitTest->eType = RulerType::Tab; + pHitTest->nAryPos = i; + return true; + } + } + } + } + } + + return false; +} + +bool Ruler::ImplDocHitTest( const Point& rPos, RulerType eDragType, + RulerSelection* pHitTest ) const +{ + Point aPos = rPos; + bool bRequiredStyle = false; + RulerIndentStyle nRequiredStyle = RulerIndentStyle::Top; + + if (eDragType == RulerType::Indent) + { + bRequiredStyle = true; + nRequiredStyle = RulerIndentStyle::Bottom; + } + + if ( mnWinStyle & WB_HORZ ) + aPos.AdjustX(mnWinOff ); + else + aPos.AdjustY(mnWinOff ); + + if ( (eDragType == RulerType::Indent) || (eDragType == RulerType::DontKnow) ) + { + if ( mnWinStyle & WB_HORZ ) + aPos.setY( RULER_OFF + 1 ); + else + aPos.setX( RULER_OFF + 1 ); + + if ( ImplDoHitTest( aPos, pHitTest, bRequiredStyle, nRequiredStyle ) ) + { + if ( (pHitTest->eType == eDragType) || (eDragType == RulerType::DontKnow) ) + return true; + } + } + + if ( (eDragType == RulerType::Indent) || + (eDragType == RulerType::Tab) || + (eDragType == RulerType::DontKnow) ) + { + if ( mnWinStyle & WB_HORZ ) + aPos.setY( mnHeight - RULER_OFF - 1 ); + else + aPos.setX( mnWidth - RULER_OFF - 1 ); + + if ( ImplDoHitTest( aPos, pHitTest, bRequiredStyle, nRequiredStyle ) ) + { + if ( (pHitTest->eType == eDragType) || (eDragType == RulerType::DontKnow) ) + return true; + } + } + + if ( (eDragType == RulerType::Margin1) || (eDragType == RulerType::Margin2) || + (eDragType == RulerType::Border) || (eDragType == RulerType::DontKnow) ) + { + if ( mnWinStyle & WB_HORZ ) + aPos.setY( RULER_OFF + (mnVirHeight / 2) ); + else + aPos.setX( RULER_OFF + (mnVirHeight / 2) ); + + if ( ImplDoHitTest( aPos, pHitTest ) ) + { + if ( (pHitTest->eType == eDragType) || (eDragType == RulerType::DontKnow) ) + return true; + } + } + + pHitTest->eType = RulerType::DontKnow; + + return false; +} + +bool Ruler::ImplStartDrag( RulerSelection const * pHitTest, sal_uInt16 nModifier ) +{ + // don't trigger drag if a border that was clicked can not be changed + if ( (pHitTest->eType == RulerType::Border) && + !pHitTest->bSize && !pHitTest->bSizeBar ) + return false; + + // Set drag data + meDragType = pHitTest->eType; + mnDragPos = pHitTest->nPos; + mnDragAryPos = pHitTest->nAryPos; + mnDragSize = pHitTest->mnDragSize; + mnDragModifier = nModifier; + *mpDragData = *mpSaveData; + mpData = mpDragData.get(); + + // call handler + if (StartDrag()) + { + // if the handler allows dragging, initialize dragging + mbDrag = true; + mnStartDragPos = mnDragPos; + StartTracking(); + Invalidate(InvalidateFlags::NoErase); + return true; + } + else + { + // otherwise reset the data + meDragType = RulerType::DontKnow; + mnDragPos = 0; + mnDragAryPos = 0; + mnDragSize = RulerDragSize::Move; + mnDragModifier = 0; + mpData = mpSaveData.get(); + } + + return false; +} + +void Ruler::ImplDrag( const Point& rPos ) +{ + tools::Long nX; + tools::Long nY; + tools::Long nOutHeight; + + if ( mnWinStyle & WB_HORZ ) + { + nX = rPos.X(); + nY = rPos.Y(); + nOutHeight = mnHeight; + } + else + { + nX = rPos.Y(); + nY = rPos.X(); + nOutHeight = mnWidth; + } + + // calculate and fit X + nX -= mnVirOff; + if ( nX < mpData->nRulVirOff ) + { + nX = mpData->nRulVirOff; + } + else if ( nX > mpData->nRulVirOff+mpData->nRulWidth ) + { + nX = mpData->nRulVirOff+mpData->nRulWidth; + } + nX -= mpData->nNullVirOff; + + // if upper or left from ruler, then consider old values + mbDragDelete = false; + if ( nY < 0 ) + { + if ( !mbDragCanceled ) + { + // reset the data + mbDragCanceled = true; + ImplRulerData aTempData = *mpDragData; + *mpDragData = *mpSaveData; + mbCalc = true; + mbFormat = true; + + // call handler + mnDragPos = mnStartDragPos; + Drag(); + + // and redraw + Invalidate(InvalidateFlags::NoErase); + + // reset the data as before cancel + *mpDragData = aTempData; + } + } + else + { + mbDragCanceled = false; + + // +2, so the tabs are not cleared too quickly + if ( nY > nOutHeight + 2 ) + mbDragDelete = true; + + mnDragPos = nX; + + // call handler + Drag(); + + // redraw + if (mbFormat) + Invalidate(InvalidateFlags::NoErase); + } +} + +void Ruler::ImplEndDrag() +{ + // get values + if ( mbDragCanceled ) + *mpDragData = *mpSaveData; + else + *mpSaveData = *mpDragData; + + mpData = mpSaveData.get(); + mbDrag = false; + + // call handler + EndDrag(); + + // reset drag values + meDragType = RulerType::DontKnow; + mnDragPos = 0; + mnDragAryPos = 0; + mnDragSize = RulerDragSize::Move; + mbDragCanceled = false; + mbDragDelete = false; + mnDragModifier = 0; + mnStartDragPos = 0; + + // redraw + Invalidate(InvalidateFlags::NoErase); +} + +void Ruler::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( !rMEvt.IsLeft() || IsTracking() ) + return; + + Point aMousePos = rMEvt.GetPosPixel(); + sal_uInt16 nMouseClicks = rMEvt.GetClicks(); + sal_uInt16 nMouseModifier = rMEvt.GetModifier(); + + // update ruler + if ( mbFormat ) + { + Invalidate(InvalidateFlags::NoErase); + } + + if ( maExtraRect.Contains( aMousePos ) ) + { + ExtraDown(); + } + else + { + RulerSelection aHitTest; + bool bHitTestResult = ImplDoHitTest(aMousePos, &aHitTest); + + if ( nMouseClicks == 1 ) + { + if ( bHitTestResult ) + { + ImplStartDrag( &aHitTest, nMouseModifier ); + } + else + { + // calculate position inside of ruler area + if ( aHitTest.eType == RulerType::DontKnow ) + { + mnDragPos = aHitTest.nPos; + Click(); + mnDragPos = 0; + + // call HitTest again as a click, for example, could set a new tab + if ( ImplDoHitTest(aMousePos, &aHitTest) ) + ImplStartDrag(&aHitTest, nMouseModifier); + } + } + } + else + { + if (bHitTestResult) + { + mnDragPos = aHitTest.nPos; + mnDragAryPos = aHitTest.nAryPos; + } + meDragType = aHitTest.eType; + + DoubleClick(); + + meDragType = RulerType::DontKnow; + mnDragPos = 0; + mnDragAryPos = 0; + } + } +} + +void Ruler::MouseMove( const MouseEvent& rMEvt ) +{ + PointerStyle ePtrStyle = PointerStyle::Arrow; + + mxPreviousHitTest.swap(mxCurrentHitTest); + + mxCurrentHitTest.reset(new RulerSelection); + + maHoverSelection.eType = RulerType::DontKnow; + + if (ImplDoHitTest( rMEvt.GetPosPixel(), mxCurrentHitTest.get() )) + { + maHoverSelection = *mxCurrentHitTest; + + if (mxCurrentHitTest->bSize) + { + if (mnWinStyle & WB_HORZ) + { + if (mxCurrentHitTest->mnDragSize == RulerDragSize::N1) + ePtrStyle = PointerStyle::TabSelectW; + else if (mxCurrentHitTest->mnDragSize == RulerDragSize::N2) + ePtrStyle = PointerStyle::TabSelectE; + else + ePtrStyle = PointerStyle::ESize; + } + else + { + if (mxCurrentHitTest->mnDragSize == RulerDragSize::N1) + ePtrStyle = PointerStyle::WindowNSize; + else if (mxCurrentHitTest->mnDragSize == RulerDragSize::N2) + ePtrStyle = PointerStyle::WindowSSize; + else + ePtrStyle = PointerStyle::SSize; + } + } + else if (mxCurrentHitTest->bSizeBar) + { + if (mnWinStyle & WB_HORZ) + ePtrStyle = PointerStyle::HSizeBar; + else + ePtrStyle = PointerStyle::VSizeBar; + } + } + + if (mxPreviousHitTest != nullptr && mxPreviousHitTest->eType != mxCurrentHitTest->eType) + { + mbFormat = true; + } + + SetPointer( ePtrStyle ); + + if (mbFormat) + { + Invalidate(InvalidateFlags::NoErase); + } +} + +void Ruler::Tracking( const TrackingEvent& rTEvt ) +{ + if ( rTEvt.IsTrackingEnded() ) + { + // reset the old state at cancel + if ( rTEvt.IsTrackingCanceled() ) + { + mbDragCanceled = true; + mbFormat = true; + } + + ImplEndDrag(); + } + else + ImplDrag( rTEvt.GetMouseEvent().GetPosPixel() ); +} + +void Ruler::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + ImplDraw(rRenderContext); + + // consider extra field + if (mnWinStyle & WB_EXTRAFIELD) + ImplDrawExtra(rRenderContext); +} + +void Ruler::Resize() +{ + Size aWinSize = GetOutputSizePixel(); + + tools::Long nNewHeight; + if ( mnWinStyle & WB_HORZ ) + { + if ( aWinSize.Height() != mnHeight ) + nNewHeight = aWinSize.Height(); + else + nNewHeight = 0; + } + else + { + if ( aWinSize.Width() != mnWidth ) + nNewHeight = aWinSize.Width(); + else + nNewHeight = 0; + } + + mbFormat = true; + + // clear lines + bool bVisible = IsReallyVisible(); + if ( bVisible && !mpData->pLines.empty() ) + { + mnUpdateFlags |= RULER_UPDATE_LINES; + Invalidate(InvalidateFlags::NoErase); + } + + // recalculate some values if the height/width changes + // extra field should always be updated + ImplInitExtraField( mpData->bTextRTL ); + if ( nNewHeight ) + { + mbCalc = true; + mnVirHeight = nNewHeight - mnBorderWidth - ( RULER_OFF * 2 ); + } + else + { + if ( mpData->bAutoPageWidth ) + ImplUpdate( true ); + else if ( mbAutoWinWidth ) + mbCalc = true; + } + + // clear part of the border + if ( bVisible ) + { + if ( nNewHeight ) + Invalidate(InvalidateFlags::NoErase); + else if ( mpData->bAutoPageWidth ) + { + // only at AutoPageWidth do we need to redraw + tools::Rectangle aRect; + + if ( mnWinStyle & WB_HORZ ) + { + if ( mnWidth < aWinSize.Width() ) + aRect.SetLeft( mnWidth - RULER_RESIZE_OFF ); + else + aRect.SetLeft( aWinSize.Width() - RULER_RESIZE_OFF ); + aRect.SetRight( aRect.Left() + RULER_RESIZE_OFF ); + aRect.SetTop( RULER_OFF ); + aRect.SetBottom( RULER_OFF + mnVirHeight ); + } + else + { + if ( mnHeight < aWinSize.Height() ) + aRect.SetTop( mnHeight-RULER_RESIZE_OFF ); + else + aRect.SetTop( aWinSize.Height()-RULER_RESIZE_OFF ); + aRect.SetBottom( aRect.Top() + RULER_RESIZE_OFF ); + aRect.SetLeft( RULER_OFF ); + aRect.SetRight( RULER_OFF + mnVirHeight ); + } + + Invalidate(aRect, InvalidateFlags::NoErase); + } + } + + mnWidth = aWinSize.Width(); + mnHeight = aWinSize.Height(); +} + +void Ruler::StateChanged( StateChangedType nType ) +{ + Window::StateChanged( nType ); + + if ( nType == StateChangedType::InitShow ) + Invalidate(); + else if ( nType == StateChangedType::UpdateMode ) + { + if ( IsReallyVisible() && IsUpdateMode() ) + 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 Ruler::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::DISPLAY) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) ) + { + mbFormat = true; + ImplInitSettings( true, true, true ); + Invalidate(); + } +} + +bool Ruler::StartDrag() +{ + return false; +} + +void Ruler::Drag() +{ +} + +void Ruler::EndDrag() +{ +} + +void Ruler::Click() +{ +} + +void Ruler::DoubleClick() +{ + maDoubleClickHdl.Call( this ); +} + +void Ruler::ExtraDown() +{ +} + +void Ruler::Activate() +{ + mbActive = true; + + // update positionlines - draw is delayed + mnUpdateFlags |= RULER_UPDATE_LINES; + Invalidate(InvalidateFlags::NoErase); +} + +void Ruler::Deactivate() +{ + // clear positionlines + Invalidate(InvalidateFlags::NoErase); + + mbActive = false; +} + +bool Ruler::StartDocDrag( const MouseEvent& rMEvt, RulerType eDragType ) +{ + if ( !mbDrag ) + { + Point aMousePos = rMEvt.GetPosPixel(); + sal_uInt16 nMouseClicks = rMEvt.GetClicks(); + sal_uInt16 nMouseModifier = rMEvt.GetModifier(); + RulerSelection aHitTest; + + if(eDragType != RulerType::DontKnow) + aHitTest.bExpandTest = true; + + // update ruler + if ( mbFormat ) + { + if (!IsReallyVisible()) + { + // set mpData for ImplDocHitTest() + ImplFormat(*GetOutDev()); + } + + Invalidate(InvalidateFlags::NoErase); + } + + if ( nMouseClicks == 1 ) + { + if ( ImplDocHitTest( aMousePos, eDragType, &aHitTest ) ) + { + PointerStyle aPtr = PointerStyle::Arrow; + + if ( aHitTest.bSize ) + { + if ( mnWinStyle & WB_HORZ ) + aPtr = PointerStyle::ESize; + else + aPtr = PointerStyle::SSize; + } + else if ( aHitTest.bSizeBar ) + { + if ( mnWinStyle & WB_HORZ ) + aPtr = PointerStyle::HSizeBar; + else + aPtr = PointerStyle::VSizeBar; + } + SetPointer( aPtr ); + return ImplStartDrag( &aHitTest, nMouseModifier ); + } + } + else if ( nMouseClicks == 2 ) + { + if ( ImplDocHitTest( aMousePos, eDragType, &aHitTest ) ) + { + mnDragPos = aHitTest.nPos; + mnDragAryPos = aHitTest.nAryPos; + } + + DoubleClick(); + + mnDragPos = 0; + mnDragAryPos = 0; + + return true; + } + } + + return false; +} + +void Ruler::CancelDrag() +{ + if ( mbDrag ) + { + ImplDrag( Point( -1, -1 ) ); + ImplEndDrag(); + } +} + +RulerType Ruler::GetRulerType( const Point& rPos, sal_uInt16* pAryPos ) +{ + RulerSelection aHitTest; + + // update ruler + if ( IsReallyVisible() && mbFormat ) + { + Invalidate(InvalidateFlags::NoErase); + } + + (void)ImplDoHitTest(rPos, &aHitTest); + + // return values + if ( pAryPos ) + *pAryPos = aHitTest.nAryPos; + return aHitTest.eType; +} + +void Ruler::SetWinPos( tools::Long nNewOff, tools::Long nNewWidth ) +{ + // should widths be automatically calculated + if ( !nNewWidth ) + mbAutoWinWidth = true; + else + mbAutoWinWidth = false; + + mnWinOff = nNewOff; + mnWinWidth = nNewWidth; + ImplUpdate( true ); +} + +void Ruler::SetPagePos( tools::Long nNewOff, tools::Long nNewWidth ) +{ + // should we do anything? + if ( (mpData->nPageOff == nNewOff) && (mpData->nPageWidth == nNewWidth) ) + return; + + // should widths be automatically calculated + if ( !nNewWidth ) + mpData->bAutoPageWidth = true; + else + mpData->bAutoPageWidth = false; + + mpData->nPageOff = nNewOff; + mpData->nPageWidth = nNewWidth; + ImplUpdate( true ); +} + +void Ruler::SetBorderPos( tools::Long nOff ) +{ + if ( mnWinStyle & WB_BORDER ) + { + if ( mnBorderOff != nOff ) + { + mnBorderOff = nOff; + + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(InvalidateFlags::NoErase); + } + } +} + +void Ruler::SetUnit( FieldUnit eNewUnit ) +{ + if ( meUnit == eNewUnit ) + return; + + meUnit = eNewUnit; + switch ( meUnit ) + { + case FieldUnit::MM: + mnUnitIndex = RULER_UNIT_MM; + break; + case FieldUnit::CM: + mnUnitIndex = RULER_UNIT_CM; + break; + case FieldUnit::M: + mnUnitIndex = RULER_UNIT_M; + break; + case FieldUnit::KM: + mnUnitIndex = RULER_UNIT_KM; + break; + case FieldUnit::INCH: + mnUnitIndex = RULER_UNIT_INCH; + break; + case FieldUnit::FOOT: + mnUnitIndex = RULER_UNIT_FOOT; + break; + case FieldUnit::MILE: + mnUnitIndex = RULER_UNIT_MILE; + break; + case FieldUnit::POINT: + mnUnitIndex = RULER_UNIT_POINT; + break; + case FieldUnit::PICA: + mnUnitIndex = RULER_UNIT_PICA; + break; + case FieldUnit::CHAR: + mnUnitIndex = RULER_UNIT_CHAR; + break; + case FieldUnit::LINE: + mnUnitIndex = RULER_UNIT_LINE; + break; + default: + SAL_WARN( "svtools.control", "Ruler::SetUnit() - Wrong Unit" ); + break; + } + + maMapMode.SetMapUnit( aImplRulerUnitTab[mnUnitIndex].eMapUnit ); + ImplUpdate(); +} + +void Ruler::SetZoom( const Fraction& rNewZoom ) +{ + DBG_ASSERT( rNewZoom.GetNumerator(), "Ruler::SetZoom() with scale 0 is not allowed" ); + + if ( maZoom != rNewZoom ) + { + maZoom = rNewZoom; + maMapMode.SetScaleX( maZoom ); + maMapMode.SetScaleY( maZoom ); + ImplUpdate(); + } +} + +void Ruler::SetExtraType( RulerExtra eNewExtraType, sal_uInt16 nStyle ) +{ + if ( mnWinStyle & WB_EXTRAFIELD ) + { + meExtraType = eNewExtraType; + mnExtraStyle = nStyle; + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + } +} + +void Ruler::SetNullOffset( tools::Long nPos ) +{ + if ( mpData->nNullOff != nPos ) + { + mpData->nNullVirOff += nPos - mpData->nNullOff; + mpData->nNullOff = nPos; + ImplUpdate(); + } +} + +void Ruler::SetLeftFrameMargin( tools::Long nPos ) +{ + if ( mpData->nLeftFrameMargin != nPos ) + { + mpData->nLeftFrameMargin = nPos; + ImplUpdate(); + } +} + +void Ruler::SetRightFrameMargin( tools::Long nPos ) +{ + if ( mpData->nRightFrameMargin != nPos ) + { + mpData->nRightFrameMargin = nPos; + ImplUpdate(); + } +} + +void Ruler::SetMargin1( tools::Long nPos, RulerMarginStyle nMarginStyle ) +{ + if ( (mpData->nMargin1 != nPos) || (mpData->nMargin1Style != nMarginStyle) ) + { + mpData->nMargin1 = nPos; + mpData->nMargin1Style = nMarginStyle; + ImplUpdate(); + } +} + +void Ruler::SetMargin2( tools::Long nPos, RulerMarginStyle nMarginStyle ) +{ + DBG_ASSERT( (nPos >= mpData->nMargin1) || + (mpData->nMargin1Style & RulerMarginStyle::Invisible) || + (mpData->nMargin2Style & RulerMarginStyle::Invisible), + "Ruler::SetMargin2() - Margin2 < Margin1" ); + + if ( (mpData->nMargin2 != nPos) || (mpData->nMargin2Style != nMarginStyle) ) + { + mpData->nMargin2 = nPos; + mpData->nMargin2Style = nMarginStyle; + ImplUpdate(); + } +} + +void Ruler::SetLines( sal_uInt32 aLineArraySize, const RulerLine* pLineArray ) +{ + // To determine if what has changed + if ( mpData->pLines.size() == aLineArraySize ) + { + sal_uInt32 i = aLineArraySize; + std::vector<RulerLine>::const_iterator aItr1 = mpData->pLines.begin(); + const RulerLine* pAry2 = pLineArray; + while ( i ) + { + if ( aItr1->nPos != pAry2->nPos ) + break; + ++aItr1; + ++pAry2; + i--; + } + if ( !i ) + return; + } + + // New values and new share issue + bool bMustUpdate; + bMustUpdate = IsReallyVisible() && IsUpdateMode(); + + // Delete old lines + if ( bMustUpdate ) + Invalidate(InvalidateFlags::NoErase); + + // New data set + if ( !aLineArraySize || !pLineArray ) + { + if ( mpData->pLines.empty() ) + return; + mpData->pLines.clear(); + } + else + { + if ( mpData->pLines.size() != aLineArraySize ) + { + mpData->pLines.resize(aLineArraySize); + } + + std::copy( pLineArray, + pLineArray + aLineArraySize, + mpData->pLines.begin() ); + + if ( bMustUpdate ) + Invalidate(InvalidateFlags::NoErase); + } +} + +void Ruler::SetBorders( sal_uInt32 aBorderArraySize, const RulerBorder* pBorderArray ) +{ + if ( !aBorderArraySize || !pBorderArray ) + { + if ( mpData->pBorders.empty() ) + return; + mpData->pBorders.clear(); + } + else + { + if ( mpData->pBorders.size() != aBorderArraySize ) + { + mpData->pBorders.resize(aBorderArraySize); + } + else + { + sal_uInt32 i = aBorderArraySize; + const RulerBorder* pAry1 = mpData->pBorders.data(); + const RulerBorder* pAry2 = pBorderArray; + while ( i ) + { + if ( (pAry1->nPos != pAry2->nPos) || + (pAry1->nWidth != pAry2->nWidth) || + (pAry1->nStyle != pAry2->nStyle) ) + break; + pAry1++; + pAry2++; + i--; + } + if ( !i ) + return; + } + std::copy( pBorderArray, + pBorderArray + aBorderArraySize, + mpData->pBorders.begin() ); + } + + ImplUpdate(); +} + +void Ruler::SetIndents( sal_uInt32 aIndentArraySize, const RulerIndent* pIndentArray ) +{ + + if ( !aIndentArraySize || !pIndentArray ) + { + if ( mpData->pIndents.empty() ) + return; + mpData->pIndents.clear(); + } + else + { + if ( mpData->pIndents.size() != aIndentArraySize ) + { + mpData->pIndents.resize(aIndentArraySize); + } + else + { + sal_uInt32 i = aIndentArraySize; + const RulerIndent* pAry1 = mpData->pIndents.data(); + const RulerIndent* pAry2 = pIndentArray; + while ( i ) + { + if ( (pAry1->nPos != pAry2->nPos) || + (pAry1->nStyle != pAry2->nStyle) ) + break; + pAry1++; + pAry2++; + i--; + } + if ( !i ) + return; + } + + std::copy( pIndentArray, + pIndentArray + aIndentArraySize, + mpData->pIndents.begin() ); + } + + ImplUpdate(); +} + +void Ruler::SetTabs( sal_uInt32 aTabArraySize, const RulerTab* pTabArray ) +{ + if ( aTabArraySize == 0 || pTabArray == nullptr ) + { + if ( mpData->pTabs.empty() ) + return; + mpData->pTabs.clear(); + } + else + { + if ( mpData->pTabs.size() != aTabArraySize ) + { + mpData->pTabs.resize(aTabArraySize); + } + else + { + sal_uInt32 i = aTabArraySize; + std::vector<RulerTab>::iterator aTabIterator = mpData->pTabs.begin(); + const RulerTab* pInputArray = pTabArray; + while ( i ) + { + RulerTab& aCurrent = *aTabIterator; + if ( aCurrent.nPos != pInputArray->nPos || + aCurrent.nStyle != pInputArray->nStyle ) + { + break; + } + ++aTabIterator; + pInputArray++; + i--; + } + if ( !i ) + return; + } + std::copy(pTabArray, pTabArray + aTabArraySize, mpData->pTabs.begin()); + } + + ImplUpdate(); +} + +const std::vector<RulerTab>& Ruler::GetTabs() const +{ + return mpData->pTabs; +} + +void Ruler::SetStyle( WinBits nStyle ) +{ + if ( mnWinStyle != nStyle ) + { + mnWinStyle = nStyle; + ImplInitExtraField( true ); + } +} + +void Ruler::DrawTab(vcl::RenderContext& rRenderContext, const Color &rFillColor, const Point& rPos, sal_uInt16 nStyle) +{ + Point aPos(rPos); + sal_uInt16 nTabStyle = nStyle & (RULER_TAB_STYLE | RULER_TAB_RTL); + + rRenderContext.Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(rFillColor); + ImplCenterTabPos(aPos, nTabStyle); + ImplDrawRulerTab(rRenderContext, aPos, nTabStyle, nStyle); + rRenderContext.Pop(); +} + +void Ruler::SetTextRTL(bool bRTL) +{ + if(mpData->bTextRTL != bRTL) + { + mpData->bTextRTL = bRTL; + if ( IsReallyVisible() && IsUpdateMode() ) + ImplInitExtraField( true ); + } + +} + +tools::Long Ruler::GetPageOffset() const +{ + return mpData->nPageOff; +} + +tools::Long Ruler::GetNullOffset() const +{ + return mpData->nNullOff; +} + +tools::Long Ruler::GetMargin1() const +{ + return mpData->nMargin1; +} + +tools::Long Ruler::GetMargin2() const +{ + return mpData->nMargin2; +} + + +bool Ruler::GetTextRTL() const +{ + return mpData->bTextRTL; +} + +const RulerUnitData& Ruler::GetCurrentRulerUnit() const +{ + return aImplRulerUnitTab[mnUnitIndex]; +} + +void Ruler::DrawTicks() +{ + mbFormat = true; + Invalidate(InvalidateFlags::NoErase); +} + +uno::Reference< XAccessible > Ruler::CreateAccessible() +{ + vcl::Window* pParent = GetAccessibleParentWindow(); + OSL_ENSURE( pParent, "-SvxRuler::CreateAccessible(): No Parent!" ); + uno::Reference< XAccessible > xAccParent = pParent->GetAccessible(); + if( xAccParent.is() ) + { + // MT: Fixed compiler issue because the address from a temporary object was used. + // BUT: Should it really be a Pointer, instead of const&??? + OUString aStr; + if ( mnWinStyle & WB_HORZ ) + { + aStr = SvtResId(STR_SVT_ACC_RULER_HORZ_NAME); + } + else + { + aStr = SvtResId(STR_SVT_ACC_RULER_VERT_NAME); + } + mxAccContext = new SvtRulerAccessible( xAccParent, *this, aStr ); + SetAccessible(mxAccContext); + return mxAccContext; + } + else + return uno::Reference< XAccessible >(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/scriptedtext.cxx b/svtools/source/control/scriptedtext.cxx new file mode 100644 index 000000000..18c6445b9 --- /dev/null +++ b/svtools/source/control/scriptedtext.cxx @@ -0,0 +1,319 @@ +/* -*- 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 <svtools/scriptedtext.hxx> +#include <vector> +#include <rtl/ustring.hxx> +#include <vcl/outdev.hxx> +#include <vcl/font.hxx> +#include <tools/debug.hxx> +#include <tools/gen.hxx> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> + + +using namespace ::std; +using namespace ::com::sun::star; + + +class SvtScriptedTextHelper_Impl +{ +private: + OutputDevice& mrOutDevice; /// The output device for drawing the text. + vcl::Font maLatinFont; /// The font for latin text portions. + vcl::Font maAsianFont; /// The font for asian text portions. + vcl::Font maCmplxFont; /// The font for complex text portions. + vcl::Font maDefltFont; /// The default font of the output device. + OUString maText; /// The text. + + vector< sal_Int32 > maPosVec; /// The start position of each text portion. + vector< sal_Int16 > maScriptVec; /// The script type of each text portion. + vector< sal_Int32 > maWidthVec; /// The output width of each text portion. + Size maTextSize; /// The size the text will take in the current output device. + + /** Gets the font of the given script type. */ + const vcl::Font& GetFont( sal_uInt16 _nScript ) const; + /** Sets a font on the output device depending on the script type. */ + void SetOutDevFont( sal_uInt16 _nScript ) + { mrOutDevice.SetFont( GetFont( _nScript ) ); } + /** Fills maPosVec with positions of all changes of script type. + This method expects correctly initialized maPosVec and maScriptVec. */ + void CalculateSizes(); + /** Fills maPosVec with positions of all changes of script type and + maScriptVec with the script type of each portion. */ + void CalculateBreaks( + const uno::Reference< i18n::XBreakIterator >& _xBreakIter ); + +public: + /** This constructor sets an output device and fonts for all script types. */ + explicit SvtScriptedTextHelper_Impl( + OutputDevice& _rOutDevice ); + + /** Sets new fonts and recalculates the text width. */ + void SetFonts( vcl::Font const * _pLatinFont, vcl::Font const * _pAsianFont, vcl::Font const * _pCmplxFont ); + /** Sets a new text and calculates all script breaks and the text width. */ + void SetText( + const OUString& _rText, + const uno::Reference< i18n::XBreakIterator >& _xBreakIter ); + + /** Returns a size struct containing the width and height of the text in the current output device. */ + const Size& GetTextSize() const { return maTextSize;} + + /** Draws the text in the current output device. */ + void DrawText( const Point& _rPos ); +}; + + +SvtScriptedTextHelper_Impl::SvtScriptedTextHelper_Impl( + OutputDevice& _rOutDevice ) : + mrOutDevice( _rOutDevice ), + maLatinFont( _rOutDevice.GetFont() ), + maAsianFont( _rOutDevice.GetFont() ), + maCmplxFont( _rOutDevice.GetFont() ), + maDefltFont( _rOutDevice.GetFont() ) +{ +} + +const vcl::Font& SvtScriptedTextHelper_Impl::GetFont( sal_uInt16 _nScript ) const +{ + switch( _nScript ) + { + case i18n::ScriptType::LATIN: return maLatinFont; + case i18n::ScriptType::ASIAN: return maAsianFont; + case i18n::ScriptType::COMPLEX: return maCmplxFont; + } + return maDefltFont; +} + +void SvtScriptedTextHelper_Impl::CalculateSizes() +{ + maTextSize.setWidth(0); + maTextSize.setHeight(0); + mrOutDevice.Push(vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR); + + // calculate text portion widths and total width + maWidthVec.clear(); + if( !maPosVec.empty() ) + { + DBG_ASSERT( maPosVec.size() - 1 == maScriptVec.size(), + "SvtScriptedTextHelper_Impl::CalculateWidth - invalid vectors" ); + + sal_Int32 nThisPos = maPosVec[ 0 ]; + sal_Int32 nNextPos; + sal_Int32 nPosVecSize = maPosVec.size(); + sal_Int32 nPosVecIndex = 1; + + sal_Int16 nScript; + sal_Int32 nScriptVecIndex = 0; + + sal_Int32 nCurrWidth; + + while( nPosVecIndex < nPosVecSize ) + { + nNextPos = maPosVec[ nPosVecIndex++ ]; + nScript = maScriptVec[ nScriptVecIndex++ ]; + + SetOutDevFont( nScript ); + nCurrWidth = mrOutDevice.GetTextWidth( maText, nThisPos, nNextPos - nThisPos ); + maWidthVec.push_back( nCurrWidth ); + maTextSize.AdjustWidth(nCurrWidth ); + nThisPos = nNextPos; + } + } + + // calculate maximum font height + SetOutDevFont( i18n::ScriptType::LATIN ); + maTextSize.setHeight( std::max( maTextSize.Height(), mrOutDevice.GetTextHeight() ) ); + SetOutDevFont( i18n::ScriptType::ASIAN ); + maTextSize.setHeight( std::max( maTextSize.Height(), mrOutDevice.GetTextHeight() ) ); + SetOutDevFont( i18n::ScriptType::COMPLEX ); + maTextSize.setHeight( std::max( maTextSize.Height(), mrOutDevice.GetTextHeight() ) ); + + mrOutDevice.Pop(); +} + +void SvtScriptedTextHelper_Impl::CalculateBreaks( const uno::Reference< i18n::XBreakIterator >& _xBreakIter ) +{ + maPosVec.clear(); + maScriptVec.clear(); + + DBG_ASSERT( _xBreakIter.is(), "SvtScriptedTextHelper_Impl::CalculateBreaks - no break iterator" ); + + sal_Int32 nLen = maText.getLength(); + if( nLen ) + { + if( _xBreakIter.is() ) + { + sal_Int32 nThisPos = 0; // first position of this portion + sal_Int32 nNextPos = 0; // first position of next portion + sal_Int16 nPortScript; // script type of this portion + do + { + nPortScript = _xBreakIter->getScriptType( maText, nThisPos ); + nNextPos = _xBreakIter->endOfScript( maText, nThisPos, nPortScript ); + + switch( nPortScript ) + { + case i18n::ScriptType::LATIN: + case i18n::ScriptType::ASIAN: + case i18n::ScriptType::COMPLEX: + maPosVec.push_back( nThisPos ); + maScriptVec.push_back( nPortScript ); + break; + default: + { +/* *** handling of weak characters *** +- first portion is weak: Use OutputDevice::HasGlyphs() to find the correct font +- weak portion follows another portion: Script type of preceding portion is used */ + if( maPosVec.empty() ) + { + sal_Int32 nCharIx = 0; + sal_Int32 nNextCharIx = 0; + sal_Int16 nScript; + do + { + nScript = i18n::ScriptType::LATIN; + while( (nScript != i18n::ScriptType::WEAK) && (nCharIx == nNextCharIx) ) + { + nNextCharIx = mrOutDevice.HasGlyphs( GetFont( nScript ), maText, nCharIx, nNextPos - nCharIx ); + if( nCharIx == nNextCharIx ) + ++nScript; + } + if( nNextCharIx == nCharIx ) + ++nNextCharIx; + + maPosVec.push_back( nCharIx ); + maScriptVec.push_back( nScript ); + nCharIx = nNextCharIx; + } + while( nCharIx < nNextPos && nCharIx != -1 ); + } + // nothing to do for following portions + } + } + nThisPos = nNextPos; + } + while( (0 <= nThisPos) && (nThisPos < nLen) ); + } + else // no break iterator: whole text LATIN + { + maPosVec.push_back( 0 ); + maScriptVec.push_back( i18n::ScriptType::LATIN ); + } + + // push end position of last portion + if( !maPosVec.empty() ) + maPosVec.push_back( nLen ); + } + CalculateSizes(); +} + +void SvtScriptedTextHelper_Impl::SetFonts( vcl::Font const * _pLatinFont, vcl::Font const * _pAsianFont, vcl::Font const * _pCmplxFont ) +{ + maLatinFont = _pLatinFont ? *_pLatinFont : maDefltFont; + maAsianFont = _pAsianFont ? *_pAsianFont : maDefltFont; + maCmplxFont = _pCmplxFont ? *_pCmplxFont : maDefltFont; + CalculateSizes(); +} + +void SvtScriptedTextHelper_Impl::SetText( const OUString& _rText, const uno::Reference< i18n::XBreakIterator >& _xBreakIter ) +{ + maText = _rText; + CalculateBreaks( _xBreakIter ); +} + + +void SvtScriptedTextHelper_Impl::DrawText( const Point& _rPos ) +{ + if( maText.isEmpty() || maPosVec.empty() ) + return; + + DBG_ASSERT( maPosVec.size() - 1 == maScriptVec.size(), "SvtScriptedTextHelper_Impl::DrawText - invalid vectors" ); + DBG_ASSERT( maScriptVec.size() == maWidthVec.size(), "SvtScriptedTextHelper_Impl::DrawText - invalid vectors" ); + + mrOutDevice.Push(vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR); + + Point aCurrPos( _rPos ); + sal_Int32 nThisPos = maPosVec[ 0 ]; + sal_Int32 nNextPos; + sal_Int32 nPosVecSize = maPosVec.size(); + sal_Int32 nPosVecIndex = 1; + + sal_Int16 nScript; + sal_Int32 nVecIndex = 0; + + while( nPosVecIndex < nPosVecSize ) + { + nNextPos = maPosVec[ nPosVecIndex++ ]; + nScript = maScriptVec[ nVecIndex ]; + vcl::Font aFont = GetFont( nScript ); + mrOutDevice.SetFont( aFont ); + if (aFont.GetColor() == COL_AUTO) + mrOutDevice.SetTextColor( mrOutDevice.GetFillColor().IsDark() ? COL_WHITE : COL_BLACK); + mrOutDevice.DrawText( aCurrPos, maText, nThisPos, nNextPos - nThisPos ); + aCurrPos.AdjustX(maWidthVec[ nVecIndex++ ] ); + aCurrPos.AdjustX(mrOutDevice.GetTextHeight() / 5 ); // add 20% of font height as portion spacing + nThisPos = nNextPos; + } + + mrOutDevice.Pop(); +} + + +SvtScriptedTextHelper::SvtScriptedTextHelper( OutputDevice& _rOutDevice ) : + mpImpl( new SvtScriptedTextHelper_Impl( _rOutDevice ) ) +{ +} + +SvtScriptedTextHelper::SvtScriptedTextHelper( const SvtScriptedTextHelper& _rCopy ) : + mpImpl( new SvtScriptedTextHelper_Impl( *_rCopy.mpImpl ) ) +{ +} + +SvtScriptedTextHelper::~SvtScriptedTextHelper() +{ +} + +void SvtScriptedTextHelper::SetFonts( vcl::Font const * _pLatinFont, vcl::Font const * _pAsianFont, vcl::Font const * _pCmplxFont ) +{ + mpImpl->SetFonts( _pLatinFont, _pAsianFont, _pCmplxFont ); +} + +void SvtScriptedTextHelper::SetDefaultFont() +{ + mpImpl->SetFonts( nullptr, nullptr, nullptr ); +} + +void SvtScriptedTextHelper::SetText( const OUString& _rText, const uno::Reference< i18n::XBreakIterator >& _xBreakIter ) +{ + mpImpl->SetText( _rText, _xBreakIter ); +} + +const Size& SvtScriptedTextHelper::GetTextSize() const +{ + return mpImpl->GetTextSize(); +} + +void SvtScriptedTextHelper::DrawText( const Point& _rPos ) +{ + mpImpl->DrawText( _rPos ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/tabbar.cxx b/svtools/source/control/tabbar.cxx new file mode 100644 index 000000000..de7cd8d43 --- /dev/null +++ b/svtools/source/control/tabbar.cxx @@ -0,0 +1,2527 @@ +/* -*- 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 <svtools/tabbar.hxx> +#include <tools/time.hxx> +#include <tools/poly.hxx> +#include <vcl/InterimItemWindow.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/svapp.hxx> +#include <vcl/help.hxx> +#include <vcl/decoview.hxx> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/svtaccessiblefactory.hxx> +#include <vcl/accessiblefactory.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/weldutils.hxx> +#include <svtools/svtresid.hxx> +#include <svtools/strings.hrc> +#include <limits> +#include <memory> +#include <utility> +#include <vector> +#include <vcl/idle.hxx> +#include <bitmaps.hlst> + +namespace +{ + +constexpr sal_uInt16 TABBAR_DRAG_SCROLLOFF = 5; +constexpr sal_uInt16 TABBAR_MINSIZE = 5; + +constexpr sal_uInt16 ADDNEWPAGE_AREAWIDTH = 10; + +class TabDrawer +{ +private: + vcl::RenderContext& mrRenderContext; + const StyleSettings& mrStyleSettings; + + tools::Rectangle maRect; + tools::Rectangle maLineRect; + + Color maSelectedColor; + Color maCustomColor; + +public: + bool mbSelected:1; + bool mbCustomColored:1; + bool mbEnabled:1; + bool mbProtect:1; + + explicit TabDrawer(vcl::RenderContext& rRenderContext) + : mrRenderContext(rRenderContext) + , mrStyleSettings(rRenderContext.GetSettings().GetStyleSettings()) + , mbSelected(false) + , mbCustomColored(false) + , mbEnabled(false) + , mbProtect(false) + { + + } + + void drawOuterFrame() + { + // set correct FillInBrush depending on status + if (mbSelected) + { + // Currently selected Tab + mrRenderContext.SetFillColor(maSelectedColor); + mrRenderContext.SetLineColor(maSelectedColor); + mrRenderContext.DrawRect(maRect); + mrRenderContext.SetLineColor(mrStyleSettings.GetDarkShadowColor()); + } + else if (mbCustomColored) + { + mrRenderContext.SetFillColor(maCustomColor); + mrRenderContext.SetLineColor(maCustomColor); + mrRenderContext.DrawRect(maRect); + mrRenderContext.SetLineColor(mrStyleSettings.GetDarkShadowColor()); + } + } + + void drawText(const OUString& aText) + { + tools::Rectangle aRect = maRect; + tools::Long nTextWidth = mrRenderContext.GetTextWidth(aText); + tools::Long nTextHeight = mrRenderContext.GetTextHeight(); + Point aPos = aRect.TopLeft(); + aPos.AdjustX((aRect.getWidth() - nTextWidth) / 2 ); + aPos.AdjustY((aRect.getHeight() - nTextHeight) / 2 ); + + if (mbEnabled) + mrRenderContext.DrawText(aPos, aText); + else + mrRenderContext.DrawCtrlText(aPos, aText, 0, aText.getLength(), (DrawTextFlags::Disable | DrawTextFlags::Mnemonic)); + } + + void drawOverTopBorder() + { + Point aTopLeft = maRect.TopLeft() + Point(1, 0); + Point aTopRight = maRect.TopRight() + Point(-1, 0); + + tools::Rectangle aDelRect(aTopLeft, aTopRight); + mrRenderContext.DrawRect(aDelRect); + } + + void drawColorLine() + { + if (!mbSelected) + return; + + // tdf#141396: the color must be different from the rest of the selected tab + Color aLineColor = (mbCustomColored && maCustomColor != maSelectedColor) + ? maCustomColor + : mrStyleSettings.GetDarkShadowColor(); + mrRenderContext.SetFillColor(aLineColor); + mrRenderContext.SetLineColor(aLineColor); + mrRenderContext.DrawRect(maLineRect); + } + + void drawSeparator() + { + const tools::Long cMargin = 5; + const tools::Long aRight( maRect.Right() - 1 ); + mrRenderContext.SetLineColor(mrStyleSettings.GetShadowColor()); + mrRenderContext.DrawLine(Point(aRight, maRect.Top() + cMargin), + Point(aRight, maRect.Bottom() - cMargin)); + } + + void drawTab() + { + drawOuterFrame(); + drawColorLine(); + if (!mbSelected && !mbCustomColored) + drawSeparator(); + if (mbProtect) + { + BitmapEx aBitmap(BMP_TAB_LOCK); + Point aPosition = maRect.TopLeft(); + aPosition.AdjustX(2); + aPosition.AdjustY((maRect.getHeight() - aBitmap.GetSizePixel().Height()) / 2); + mrRenderContext.DrawBitmapEx(aPosition, aBitmap); + } + } + + void setRect(const tools::Rectangle& rRect) + { + maLineRect = tools::Rectangle(rRect.BottomLeft(), rRect.BottomRight()); + maLineRect.AdjustTop(-2); + maRect = rRect; + } + + void setSelected(bool bSelected) + { + mbSelected = bSelected; + } + + void setCustomColored(bool bCustomColored) + { + mbCustomColored = bCustomColored; + } + + void setEnabled(bool bEnabled) + { + mbEnabled = bEnabled; + } + + void setSelectedFillColor(const Color& rColor) + { + maSelectedColor = rColor; + } + + void setCustomColor(const Color& rColor) + { + maCustomColor = rColor; + } +}; + +} // anonymous namespace + +struct ImplTabBarItem +{ + sal_uInt16 mnId; + TabBarPageBits mnBits; + OUString maText; + OUString maHelpText; + OUString maAuxiliaryText; // used in LayerTabBar for real layer name + tools::Rectangle maRect; + tools::Long mnWidth; + OString maHelpId; + bool mbShort : 1; + bool mbSelect : 1; + bool mbProtect : 1; + Color maTabBgColor; + Color maTabTextColor; + + ImplTabBarItem(sal_uInt16 nItemId, const OUString& rText, TabBarPageBits nPageBits) + : mnId(nItemId) + , mnBits(nPageBits) + , maText(rText) + , mnWidth(0) + , mbShort(false) + , mbSelect(false) + , mbProtect(false) + , maTabBgColor(COL_AUTO) + , maTabTextColor(COL_AUTO) + { + } + + bool IsDefaultTabBgColor() const + { + return maTabBgColor == COL_AUTO; + } + + bool IsSelected(ImplTabBarItem const * pCurItem) const + { + return mbSelect || (pCurItem == this); + } + + OUString const & GetRenderText() const + { + return maText; + } +}; + +class ImplTabSizer : public vcl::Window +{ +public: + ImplTabSizer( TabBar* pParent, WinBits nWinStyle ); + + TabBar* GetParent() const { return static_cast<TabBar*>(Window::GetParent()); } + +private: + void ImplTrack( const Point& rScreenPos ); + + virtual void MouseButtonDown( const MouseEvent& rMEvt ) override; + virtual void Tracking( const TrackingEvent& rTEvt ) override; + virtual void Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& rRect ) override; + + Point maStartPos; + tools::Long mnStartWidth; +}; + +ImplTabSizer::ImplTabSizer( TabBar* pParent, WinBits nWinStyle ) + : Window( pParent, nWinStyle & WB_3DLOOK ) + , mnStartWidth(0) +{ + SetPointer(PointerStyle::HSizeBar); + SetSizePixel(Size(7 * GetDPIScaleFactor(), 0)); +} + +void ImplTabSizer::ImplTrack( const Point& rScreenPos ) +{ + TabBar* pParent = GetParent(); + tools::Long nDiff = rScreenPos.X() - maStartPos.X(); + pParent->mnSplitSize = mnStartWidth + (pParent->IsMirrored() ? -nDiff : nDiff); + if ( pParent->mnSplitSize < TABBAR_MINSIZE ) + pParent->mnSplitSize = TABBAR_MINSIZE; + pParent->Split(); + pParent->PaintImmediately(); +} + +void ImplTabSizer::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( GetParent()->IsInEditMode() ) + { + GetParent()->EndEditMode(); + return; + } + + if ( rMEvt.IsLeft() ) + { + maStartPos = OutputToScreenPixel( rMEvt.GetPosPixel() ); + mnStartWidth = GetParent()->GetSizePixel().Width(); + StartTracking(); + } +} + +void ImplTabSizer::Tracking( const TrackingEvent& rTEvt ) +{ + if ( rTEvt.IsTrackingEnded() ) + { + if ( rTEvt.IsTrackingCanceled() ) + ImplTrack( maStartPos ); + GetParent()->mnSplitSize = 0; + } + else + ImplTrack( OutputToScreenPixel( rTEvt.GetMouseEvent().GetPosPixel() ) ); +} + +void ImplTabSizer::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& ) +{ + DecorationView aDecoView(&rRenderContext); + tools::Rectangle aOutputRect(Point(0, 0), GetOutputSizePixel()); + aDecoView.DrawHandle(aOutputRect); +} + +namespace { + +// Is not named Impl. as it may be both instantiated and derived from +class TabBarEdit final : public InterimItemWindow +{ +private: + std::unique_ptr<weld::Entry> m_xEntry; + Idle maLoseFocusIdle; + bool mbPostEvt; + + DECL_LINK( ImplEndEditHdl, void*, void ); + DECL_LINK( ImplEndTimerHdl, Timer*, void ); + DECL_LINK( ActivatedHdl, weld::Entry&, bool ); + DECL_LINK( KeyInputHdl, const KeyEvent&, bool ); + DECL_LINK( FocusOutHdl, weld::Widget&, void ); + +public: + TabBarEdit(TabBar* pParent); + virtual void dispose() override; + + TabBar* GetParent() const { return static_cast<TabBar*>(Window::GetParent()); } + + weld::Entry& get_widget() { return *m_xEntry; } + + void SetPostEvent() { mbPostEvt = true; } + void ResetPostEvent() { mbPostEvt = false; } +}; + +} + +TabBarEdit::TabBarEdit(TabBar* pParent) + : InterimItemWindow(pParent, "svt/ui/tabbaredit.ui", "TabBarEdit") + , m_xEntry(m_xBuilder->weld_entry("entry")) + , maLoseFocusIdle( "svtools::TabBarEdit maLoseFocusIdle" ) +{ + InitControlBase(m_xEntry.get()); + + mbPostEvt = false; + maLoseFocusIdle.SetPriority( TaskPriority::REPAINT ); + maLoseFocusIdle.SetInvokeHandler( LINK( this, TabBarEdit, ImplEndTimerHdl ) ); + + m_xEntry->connect_activate(LINK(this, TabBarEdit, ActivatedHdl)); + m_xEntry->connect_key_press(LINK(this, TabBarEdit, KeyInputHdl)); + m_xEntry->connect_focus_out(LINK(this, TabBarEdit, FocusOutHdl)); +} + +void TabBarEdit::dispose() +{ + m_xEntry.reset(); + InterimItemWindow::dispose(); +} + +IMPL_LINK_NOARG(TabBarEdit, ActivatedHdl, weld::Entry&, bool) +{ + if ( !mbPostEvt ) + { + if ( PostUserEvent( LINK( this, TabBarEdit, ImplEndEditHdl ), reinterpret_cast<void*>(false), true ) ) + mbPostEvt = true; + } + return true; +} + +IMPL_LINK(TabBarEdit, KeyInputHdl, const KeyEvent&, rKEvt, bool) +{ + if (!rKEvt.GetKeyCode().GetModifier() && rKEvt.GetKeyCode().GetCode() == KEY_ESCAPE) + { + if ( !mbPostEvt ) + { + if ( PostUserEvent( LINK( this, TabBarEdit, ImplEndEditHdl ), reinterpret_cast<void*>(true), true ) ) + mbPostEvt = true; + } + return true; + } + return false; +} + +IMPL_LINK_NOARG(TabBarEdit, FocusOutHdl, weld::Widget&, void) +{ + if ( !mbPostEvt ) + { + if ( PostUserEvent( LINK( this, TabBarEdit, ImplEndEditHdl ), reinterpret_cast<void*>(false), true ) ) + mbPostEvt = true; + } +} + +IMPL_LINK( TabBarEdit, ImplEndEditHdl, void*, pCancel, void ) +{ + ResetPostEvent(); + maLoseFocusIdle.Stop(); + + // We need this query, because the edit gets a losefocus event, + // when it shows the context menu or the insert symbol dialog + if (!m_xEntry->has_focus() && m_xEntry->has_child_focus()) + maLoseFocusIdle.Start(); + else + GetParent()->EndEditMode( pCancel != nullptr ); +} + +IMPL_LINK_NOARG(TabBarEdit, ImplEndTimerHdl, Timer *, void) +{ + if (m_xEntry->has_focus()) + return; + + // We need this query, because the edit gets a losefocus event, + // when it shows the context menu or the insert symbol dialog + if (m_xEntry->has_child_focus()) + maLoseFocusIdle.Start(); + else + GetParent()->EndEditMode( true ); +} + +namespace { + +class TabButtons final : public InterimItemWindow +{ +public: + std::unique_ptr<weld::Button> m_xFirstButton; + std::unique_ptr<weld::Button> m_xPrevButton; + std::unique_ptr<weld::Button> m_xNextButton; + std::unique_ptr<weld::Button> m_xLastButton; + std::unique_ptr<weld::Button> m_xAddButton; + std::shared_ptr<weld::ButtonPressRepeater> m_xAddRepeater; + std::shared_ptr<weld::ButtonPressRepeater> m_xPrevRepeater; + std::shared_ptr<weld::ButtonPressRepeater> m_xNextRepeater; + + TabButtons(TabBar* pParent) + : InterimItemWindow(pParent, + pParent->IsMirrored() ? OUString("svt/ui/tabbuttonsmirrored.ui") + : OUString("svt/ui/tabbuttons.ui"), + "TabButtons") + , m_xFirstButton(m_xBuilder->weld_button("first")) + , m_xPrevButton(m_xBuilder->weld_button("prev")) + , m_xNextButton(m_xBuilder->weld_button("next")) + , m_xLastButton(m_xBuilder->weld_button("last")) + , m_xAddButton(m_xBuilder->weld_button("add")) + { + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + SetPaintTransparent(false); + SetBackground(rStyleSettings.GetFaceColor()); + + m_xFirstButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_MOVET0HOME)); + m_xPrevButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_MOVELEFT)); + m_xNextButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_MOVERIGHT)); + m_xLastButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_MOVETOEND)); + m_xAddButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_ADDTAB)); + } + + void AdaptToHeight(int nHeight) + { + if (m_xFirstButton->get_preferred_size() == Size(nHeight, nHeight)) + return; + m_xFirstButton->set_size_request(nHeight, nHeight); + m_xPrevButton->set_size_request(nHeight, nHeight); + m_xNextButton->set_size_request(nHeight, nHeight); + m_xLastButton->set_size_request(nHeight, nHeight); + m_xAddButton->set_size_request(nHeight, nHeight); + InvalidateChildSizeCache(); + } + + virtual void dispose() override + { + m_xNextRepeater.reset(); + m_xPrevRepeater.reset(); + m_xAddRepeater.reset(); + m_xAddButton.reset(); + m_xLastButton.reset(); + m_xNextButton.reset(); + m_xPrevButton.reset(); + m_xFirstButton.reset(); + InterimItemWindow::dispose(); + } +}; + +} + +struct TabBar_Impl +{ + ScopedVclPtr<ImplTabSizer> mpSizer; + ScopedVclPtr<TabButtons> mxButtonBox; + ScopedVclPtr<TabBarEdit> mxEdit; + std::vector<ImplTabBarItem> maItemList; + + vcl::AccessibleFactoryAccess maAccessibleFactory; + + sal_uInt16 getItemSize() const + { + return static_cast<sal_uInt16>(maItemList.size()); + } +}; + +TabBar::TabBar( vcl::Window* pParent, WinBits nWinStyle ) : + Window( pParent, (nWinStyle & WB_3DLOOK) | WB_CLIPCHILDREN ) +{ + ImplInit( nWinStyle ); + maCurrentItemList = 0; +} + +TabBar::~TabBar() +{ + disposeOnce(); +} + +void TabBar::dispose() +{ + EndEditMode( true ); + mpImpl.reset(); + Window::dispose(); +} + +const sal_uInt16 TabBar::APPEND = ::std::numeric_limits<sal_uInt16>::max(); +const sal_uInt16 TabBar::PAGE_NOT_FOUND = ::std::numeric_limits<sal_uInt16>::max(); + +void TabBar::ImplInit( WinBits nWinStyle ) +{ + mpImpl.reset(new TabBar_Impl); + + mnMaxPageWidth = 0; + mnCurMaxWidth = 0; + mnOffX = 0; + mnOffY = 0; + mnLastOffX = 0; + mnSplitSize = 0; + mnSwitchTime = 0; + mnWinStyle = nWinStyle; + mnCurPageId = 0; + mnFirstPos = 0; + mnDropPos = 0; + mnSwitchId = 0; + mnEditId = 0; + mbFormat = true; + mbFirstFormat = true; + mbSizeFormat = true; + mbAutoEditMode = false; + mbEditCanceled = false; + mbDropPos = false; + mbInSelect = false; + mbMirrored = false; + mbScrollAlwaysEnabled = false; + + ImplInitControls(); + + SetSizePixel( Size( 100, CalcWindowSizePixel().Height() ) ); + ImplInitSettings( true, true ); +} + +ImplTabBarItem* TabBar::seek( size_t i ) +{ + if ( i < mpImpl->maItemList.size() ) + { + maCurrentItemList = i; + return &mpImpl->maItemList[maCurrentItemList]; + } + return nullptr; +} + +ImplTabBarItem* TabBar::prev() +{ + if ( maCurrentItemList > 0 ) + { + return &mpImpl->maItemList[--maCurrentItemList]; + } + return nullptr; +} + +ImplTabBarItem* TabBar::next() +{ + if ( maCurrentItemList + 1 < mpImpl->maItemList.size() ) + { + return &mpImpl->maItemList[++maCurrentItemList]; + } + return nullptr; +} + +void TabBar::ImplInitSettings( bool bFont, bool bBackground ) +{ + // FIXME RenderContext + + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + + if (bFont) + { + vcl::Font aToolFont = rStyleSettings.GetToolFont(); + aToolFont.SetWeight( WEIGHT_BOLD ); + ApplyControlFont(*GetOutDev(), aToolFont); + + // Adapt font size if window too small? + while (GetTextHeight() > (GetOutputSizePixel().Height() - 1)) + { + vcl::Font aFont = GetFont(); + if (aFont.GetFontHeight() <= 6) + break; + aFont.SetFontHeight(aFont.GetFontHeight() - 1); + SetFont(aFont); + } + } + + if (bBackground) + { + ApplyControlBackground(*GetOutDev(), rStyleSettings.GetFaceColor()); + } +} + +void TabBar::ImplGetColors(const StyleSettings& rStyleSettings, + Color& rFaceColor, Color& rFaceTextColor, + Color& rSelectColor, Color& rSelectTextColor) +{ + if (IsControlBackground()) + rFaceColor = GetControlBackground(); + else + rFaceColor = rStyleSettings.GetInactiveTabColor(); + if (IsControlForeground()) + rFaceTextColor = GetControlForeground(); + else + rFaceTextColor = rStyleSettings.GetButtonTextColor(); + rSelectColor = rStyleSettings.GetActiveTabColor(); + rSelectTextColor = rStyleSettings.GetWindowTextColor(); +} + +bool TabBar::ImplCalcWidth() +{ + // Sizes should only be retrieved if the text or the font was changed + if (!mbSizeFormat) + return false; + + // retrieve width of tabs with bold font + vcl::Font aFont = GetFont(); + if (aFont.GetWeight() != WEIGHT_BOLD) + { + aFont.SetWeight(WEIGHT_BOLD); + SetFont(aFont); + } + + if (mnMaxPageWidth) + mnCurMaxWidth = mnMaxPageWidth; + else + { + mnCurMaxWidth = mnLastOffX - mnOffX; + if (mnCurMaxWidth < 1) + mnCurMaxWidth = 1; + } + + bool bChanged = false; + for (auto& rItem : mpImpl->maItemList) + { + tools::Long nNewWidth = GetTextWidth(rItem.GetRenderText()); + if (mnCurMaxWidth && (nNewWidth > mnCurMaxWidth)) + { + rItem.mbShort = true; + nNewWidth = mnCurMaxWidth; + } + else + { + rItem.mbShort = false; + } + + // Padding is dependent on font height - bigger font = bigger padding + tools::Long nFontWidth = aFont.GetFontHeight(); + if (rItem.mbProtect) + nNewWidth += 24; + nNewWidth += nFontWidth * 2; + + if (rItem.mnWidth != nNewWidth) + { + rItem.mnWidth = nNewWidth; + if (!rItem.maRect.IsEmpty()) + bChanged = true; + } + } + mbSizeFormat = false; + mbFormat = true; + return bChanged; +} + +void TabBar::ImplFormat() +{ + ImplCalcWidth(); + + if (!mbFormat) + return; + + sal_uInt16 nItemIndex = 0; + tools::Long x = mnOffX; + for (auto & rItem : mpImpl->maItemList) + { + // At all non-visible tabs an empty rectangle is set + if ((nItemIndex + 1 < mnFirstPos) || (x > mnLastOffX)) + rItem.maRect.SetEmpty(); + else + { + // Slightly before the tab before the first visible page + // should also be visible + if (nItemIndex + 1 == mnFirstPos) + { + rItem.maRect.SetLeft(x - rItem.mnWidth); + } + else + { + rItem.maRect.SetLeft(x); + x += rItem.mnWidth; + } + rItem.maRect.SetRight(x); + rItem.maRect.SetBottom(maWinSize.Height() - 1); + + if (mbMirrored) + { + tools::Long nNewLeft = mnOffX + mnLastOffX - rItem.maRect.Right(); + tools::Long nNewRight = mnOffX + mnLastOffX - rItem.maRect.Left(); + rItem.maRect.SetRight(nNewRight); + rItem.maRect.SetLeft(nNewLeft); + } + } + + nItemIndex++; + } + + mbFormat = false; + + // enable/disable button + ImplEnableControls(); +} + +sal_uInt16 TabBar::ImplGetLastFirstPos() +{ + sal_uInt16 nCount = mpImpl->getItemSize(); + if (!nCount || mbSizeFormat || mbFormat) + return 0; + + sal_uInt16 nLastFirstPos = nCount - 1; + tools::Long nWinWidth = mnLastOffX - mnOffX - ADDNEWPAGE_AREAWIDTH; + tools::Long nWidth = mpImpl->maItemList[nLastFirstPos].mnWidth; + + while (nLastFirstPos && (nWidth < nWinWidth)) + { + nLastFirstPos--; + nWidth += mpImpl->maItemList[nLastFirstPos].mnWidth; + } + if ((nLastFirstPos != static_cast<sal_uInt16>(mpImpl->maItemList.size() - 1)) && (nWidth > nWinWidth)) + nLastFirstPos++; + return nLastFirstPos; +} + +IMPL_LINK(TabBar, ContextMenuHdl, const CommandEvent&, rCommandEvent, void) +{ + maScrollAreaContextHdl.Call(rCommandEvent); +} + +IMPL_LINK(TabBar, MousePressHdl, const MouseEvent&, rMouseEvent, bool) +{ + if (rMouseEvent.IsRight()) + ContextMenuHdl(CommandEvent(rMouseEvent.GetPosPixel(), CommandEventId::ContextMenu, true)); + return false; +} + +void TabBar::ImplInitControls() +{ + if (mnWinStyle & WB_SIZEABLE) + { + if (!mpImpl->mpSizer) + { + mpImpl->mpSizer.disposeAndReset(VclPtr<ImplTabSizer>::Create( this, mnWinStyle & (WB_DRAG | WB_3DLOOK))); + } + mpImpl->mpSizer->Show(); + } + else + { + mpImpl->mpSizer.disposeAndClear(); + } + + mpImpl->mxButtonBox.disposeAndReset(VclPtr<TabButtons>::Create(this)); + + Link<const CommandEvent&, void> aContextLink = LINK( this, TabBar, ContextMenuHdl ); + + if (mnWinStyle & WB_INSERTTAB) + { + Link<weld::Button&,void> aLink = LINK(this, TabBar, ImplAddClickHandler); + mpImpl->mxButtonBox->m_xAddRepeater = std::make_shared<weld::ButtonPressRepeater>( + *mpImpl->mxButtonBox->m_xAddButton, aLink, aContextLink); + mpImpl->mxButtonBox->m_xAddButton->show(); + } + + Link<weld::Button&,void> aLink = LINK( this, TabBar, ImplClickHdl ); + + if (mnWinStyle & (WB_MINSCROLL | WB_SCROLL)) + { + mpImpl->mxButtonBox->m_xPrevRepeater = std::make_shared<weld::ButtonPressRepeater>( + *mpImpl->mxButtonBox->m_xPrevButton, aLink, aContextLink); + mpImpl->mxButtonBox->m_xPrevButton->show(); + mpImpl->mxButtonBox->m_xNextRepeater = std::make_shared<weld::ButtonPressRepeater>( + *mpImpl->mxButtonBox->m_xNextButton, aLink, aContextLink); + mpImpl->mxButtonBox->m_xNextButton->show(); + } + + if (mnWinStyle & WB_SCROLL) + { + Link<const MouseEvent&, bool> aBtnContextLink = LINK(this, TabBar, MousePressHdl); + + mpImpl->mxButtonBox->m_xFirstButton->connect_clicked(aLink); + mpImpl->mxButtonBox->m_xFirstButton->connect_mouse_press(aBtnContextLink); + mpImpl->mxButtonBox->m_xFirstButton->show(); + mpImpl->mxButtonBox->m_xLastButton->connect_clicked(aLink); + mpImpl->mxButtonBox->m_xLastButton->connect_mouse_press(aBtnContextLink); + mpImpl->mxButtonBox->m_xLastButton->show(); + } + + mpImpl->mxButtonBox->Show(); +} + +void TabBar::ImplEnableControls() +{ + if (mbSizeFormat || mbFormat) + return; + + // enable/disable buttons + bool bEnableBtn = mbScrollAlwaysEnabled || mnFirstPos > 0; + mpImpl->mxButtonBox->m_xFirstButton->set_sensitive(bEnableBtn); + mpImpl->mxButtonBox->m_xPrevButton->set_sensitive(bEnableBtn); + if (!bEnableBtn && mpImpl->mxButtonBox->m_xPrevRepeater) + mpImpl->mxButtonBox->m_xPrevRepeater->Stop(); + bEnableBtn = mbScrollAlwaysEnabled || mnFirstPos < ImplGetLastFirstPos(); + mpImpl->mxButtonBox->m_xLastButton->set_sensitive(bEnableBtn); + mpImpl->mxButtonBox->m_xNextButton->set_sensitive(bEnableBtn); + if (!bEnableBtn && mpImpl->mxButtonBox->m_xNextRepeater) + mpImpl->mxButtonBox->m_xNextRepeater->Stop(); +} + +void TabBar::SetScrollAlwaysEnabled(bool bScrollAlwaysEnabled) +{ + mbScrollAlwaysEnabled = bScrollAlwaysEnabled; + ImplEnableControls(); +} + +void TabBar::ImplShowPage( sal_uInt16 nPos ) +{ + if (nPos >= mpImpl->getItemSize()) + return; + + // calculate width + tools::Long nWidth = GetOutputSizePixel().Width(); + + auto& rItem = mpImpl->maItemList[nPos]; + if (nPos < mnFirstPos) + SetFirstPageId( rItem.mnId ); + else if (rItem.maRect.Right() > nWidth) + { + while (rItem.maRect.Right() > nWidth) + { + sal_uInt16 nNewPos = mnFirstPos + 1; + SetFirstPageId(GetPageId(nNewPos)); + ImplFormat(); + if (nNewPos != mnFirstPos) + break; + } + } +} + +IMPL_LINK( TabBar, ImplClickHdl, weld::Button&, rBtn, void ) +{ + if (&rBtn != mpImpl->mxButtonBox->m_xFirstButton.get() && &rBtn != mpImpl->mxButtonBox->m_xLastButton.get()) + { + if ((GetPointerState().mnState & (MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT)) == 0) + { + // like tdf#149482 if we didn't see a mouse up, but find that the mouse is no + // longer pressed at this point, then bail + mpImpl->mxButtonBox->m_xPrevRepeater->Stop(); + mpImpl->mxButtonBox->m_xNextRepeater->Stop(); + return; + } + } + + EndEditMode(); + + sal_uInt16 nNewPos = mnFirstPos; + + if (&rBtn == mpImpl->mxButtonBox->m_xFirstButton.get() || (&rBtn == mpImpl->mxButtonBox->m_xPrevButton.get() && + mpImpl->mxButtonBox->m_xPrevRepeater->IsModKeyPressed())) + { + nNewPos = 0; + } + else if (&rBtn == mpImpl->mxButtonBox->m_xLastButton.get() || (&rBtn == mpImpl->mxButtonBox->m_xNextButton.get() && + mpImpl->mxButtonBox->m_xNextRepeater->IsModKeyPressed())) + { + sal_uInt16 nCount = GetPageCount(); + if (nCount) + nNewPos = nCount - 1; + } + else if (&rBtn == mpImpl->mxButtonBox->m_xPrevButton.get()) + { + if (mnFirstPos) + nNewPos = mnFirstPos - 1; + } + else if (&rBtn == mpImpl->mxButtonBox->m_xNextButton.get()) + { + sal_uInt16 nCount = GetPageCount(); + if (mnFirstPos < nCount) + nNewPos = mnFirstPos+1; + } + else + { + return; + } + + if (nNewPos != mnFirstPos) + SetFirstPageId(GetPageId(nNewPos)); +} + +IMPL_LINK_NOARG(TabBar, ImplAddClickHandler, weld::Button&, void) +{ + if ((GetPointerState().mnState & (MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT)) == 0) + { + // tdf#149482 if we didn't see a mouse up, but find that the mouse is no + // longer pressed at this point, then bail + mpImpl->mxButtonBox->m_xAddRepeater->Stop(); + return; + } + + EndEditMode(); + AddTabClick(); +} + +void TabBar::MouseMove(const MouseEvent& rMEvt) +{ + if (rMEvt.IsLeaveWindow()) + mbInSelect = false; + + Window::MouseMove(rMEvt); +} + +void TabBar::MouseButtonDown(const MouseEvent& rMEvt) +{ + // Only terminate EditMode and do not execute click + // if clicked inside our window, + if (IsInEditMode()) + { + EndEditMode(); + return; + } + + sal_uInt16 nSelId = GetPageId(rMEvt.GetPosPixel()); + + if (!rMEvt.IsLeft()) + { + Window::MouseButtonDown(rMEvt); + if (nSelId > 0 && nSelId != mnCurPageId) + { + if (ImplDeactivatePage()) + { + SetCurPageId(nSelId); + PaintImmediately(); + ImplActivatePage(); + ImplSelect(); + } + mbInSelect = true; + } + return; + } + + if (rMEvt.IsMod2() && mbAutoEditMode && nSelId) + { + if (StartEditMode(nSelId)) + return; + } + + if ((rMEvt.GetMode() & (MouseEventModifiers::MULTISELECT | MouseEventModifiers::RANGESELECT)) && (rMEvt.GetClicks() == 1)) + { + if (nSelId) + { + sal_uInt16 nPos = GetPagePos(nSelId); + + bool bSelectTab = false; + + if ((rMEvt.GetMode() & MouseEventModifiers::MULTISELECT) && (mnWinStyle & WB_MULTISELECT)) + { + if (nSelId != mnCurPageId) + { + SelectPage(nSelId, !IsPageSelected(nSelId)); + bSelectTab = true; + } + } + else if (mnWinStyle & (WB_MULTISELECT | WB_RANGESELECT)) + { + bSelectTab = true; + sal_uInt16 n; + bool bSelect; + sal_uInt16 nCurPos = GetPagePos(mnCurPageId); + if (nPos <= nCurPos) + { + // Deselect all tabs till the clicked tab + // and select all tabs from the clicked tab + // 'till the actual position + n = 0; + while (n < nCurPos) + { + auto& rItem = mpImpl->maItemList[n]; + bSelect = n >= nPos; + + if (rItem.mbSelect != bSelect) + { + rItem.mbSelect = bSelect; + if (!rItem.maRect.IsEmpty()) + Invalidate(rItem.maRect); + } + + n++; + } + } + + if (nPos >= nCurPos) + { + // Select all tabs from the actual position till the clicked tab + // and deselect all tabs from the actual position + // till the last tab + sal_uInt16 nCount = mpImpl->getItemSize(); + n = nCurPos; + while (n < nCount) + { + auto& rItem = mpImpl->maItemList[n]; + + bSelect = n <= nPos; + + if (rItem.mbSelect != bSelect) + { + rItem.mbSelect = bSelect; + if (!rItem.maRect.IsEmpty()) + Invalidate(rItem.maRect); + } + + n++; + } + } + } + + // scroll the selected tab if required + if (bSelectTab) + { + ImplShowPage(nPos); + PaintImmediately(); + ImplSelect(); + } + + mbInSelect = true; + + return; + } + } + else if (rMEvt.GetClicks() == 2) + { + // call double-click-handler if required + if (!rMEvt.GetModifier() && (!nSelId || (nSelId == mnCurPageId))) + { + sal_uInt16 nOldCurId = mnCurPageId; + mnCurPageId = nSelId; + DoubleClick(); + // check, as actual page could be switched inside + // the doubleclick-handler + if (mnCurPageId == nSelId) + mnCurPageId = nOldCurId; + } + + return; + } + else + { + if (nSelId) + { + // execute Select if not actual page + if (nSelId != mnCurPageId) + { + sal_uInt16 nPos = GetPagePos(nSelId); + auto& rItem = mpImpl->maItemList[nPos]; + + if (!rItem.mbSelect) + { + // make not valid + bool bUpdate = false; + if (IsReallyVisible() && IsUpdateMode()) + bUpdate = true; + + // deselect all selected items + for (auto& xItem : mpImpl->maItemList) + { + if (xItem.mbSelect || (xItem.mnId == mnCurPageId)) + { + xItem.mbSelect = false; + if (bUpdate) + Invalidate(xItem.maRect); + } + } + } + + if (ImplDeactivatePage()) + { + SetCurPageId(nSelId); + PaintImmediately(); + ImplActivatePage(); + ImplSelect(); + } + + mbInSelect = true; + } + + return; + } + } + + Window::MouseButtonDown(rMEvt); +} + +void TabBar::MouseButtonUp(const MouseEvent& rMEvt) +{ + mbInSelect = false; + Window::MouseButtonUp(rMEvt); +} + +void TabBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rect) +{ + if (rRenderContext.IsNativeControlSupported(ControlType::WindowBackground,ControlPart::Entire)) + { + rRenderContext.DrawNativeControl(ControlType::WindowBackground,ControlPart::Entire,rect, + ControlState::ENABLED,ImplControlValue(0),OUString()); + } + // calculate items and emit + sal_uInt16 nItemCount = mpImpl->getItemSize(); + if (!nItemCount) + return; + + ImplPrePaint(); + + Color aFaceColor, aSelectColor, aFaceTextColor, aSelectTextColor; + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + ImplGetColors(rStyleSettings, aFaceColor, aFaceTextColor, aSelectColor, aSelectTextColor); + + rRenderContext.Push(vcl::PushFlags::FONT | vcl::PushFlags::CLIPREGION); + rRenderContext.SetClipRegion(vcl::Region(GetPageArea())); + + // select font + vcl::Font aFont = rRenderContext.GetFont(); + vcl::Font aLightFont = aFont; + aLightFont.SetWeight(WEIGHT_NORMAL); + + TabDrawer aDrawer(rRenderContext); + + aDrawer.setSelectedFillColor(aSelectColor); + + // Now, start drawing the tabs. + + ImplTabBarItem* pItem = ImplGetLastTabBarItem(nItemCount); + ImplTabBarItem* pCurItem = nullptr; + while (pItem) + { + // emit CurrentItem last, as it covers all others + if (!pCurItem && (pItem->mnId == mnCurPageId)) + { + pCurItem = pItem; + pItem = prev(); + if (!pItem) + pItem = pCurItem; + continue; + } + + bool bCurrent = pItem == pCurItem; + + if (!pItem->maRect.IsEmpty()) + { + tools::Rectangle aRect = pItem->maRect; + bool bSelected = pItem->IsSelected(pCurItem); + // We disable custom background color in high contrast mode. + bool bCustomBgColor = !pItem->IsDefaultTabBgColor() && !rStyleSettings.GetHighContrastMode(); + OUString aText = pItem->mbShort ? + rRenderContext.GetEllipsisString(pItem->GetRenderText(), mnCurMaxWidth) : + pItem->GetRenderText(); + + aDrawer.setRect(aRect); + aDrawer.setSelected(bSelected); + aDrawer.setCustomColored(bCustomBgColor); + aDrawer.setEnabled(true); + aDrawer.setCustomColor(pItem->maTabBgColor); + aDrawer.mbProtect = pItem->mbProtect; + aDrawer.drawTab(); + + // currently visible sheet is drawn using a bold font + if (bCurrent) + rRenderContext.SetFont(aFont); + else + rRenderContext.SetFont(aLightFont); + + // Set the correct FillInBrush depending on status + + if (bSelected) + rRenderContext.SetTextColor(aSelectTextColor); + else if (bCustomBgColor) + rRenderContext.SetTextColor(pItem->maTabTextColor); + else + rRenderContext.SetTextColor(aFaceTextColor); + + // Special display of tab name depending on page bits + + if (pItem->mnBits & TabBarPageBits::Blue) + { + rRenderContext.SetTextColor(COL_LIGHTBLUE); + } + if (pItem->mnBits & TabBarPageBits::Italic) + { + vcl::Font aSpecialFont = rRenderContext.GetFont(); + aSpecialFont.SetItalic(FontItalic::ITALIC_NORMAL); + rRenderContext.SetFont(aSpecialFont); + } + if (pItem->mnBits & TabBarPageBits::Underline) + { + vcl::Font aSpecialFont = rRenderContext.GetFont(); + aSpecialFont.SetUnderline(LINESTYLE_SINGLE); + rRenderContext.SetFont(aSpecialFont); + } + + aDrawer.drawText(aText); + + if (bCurrent) + { + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(aSelectColor); + aDrawer.drawOverTopBorder(); + break; + } + + pItem = prev(); + } + else + { + if (bCurrent) + break; + + pItem = nullptr; + } + + if (!pItem) + pItem = pCurItem; + } + rRenderContext.Pop(); +} + +void TabBar::Resize() +{ + Size aNewSize = GetOutputSizePixel(); + + tools::Long nSizerWidth = 0; + + // order the Sizer + if ( mpImpl->mpSizer ) + { + Size aSizerSize = mpImpl->mpSizer->GetSizePixel(); + Point aNewSizerPos( mbMirrored ? 0 : (aNewSize.Width()-aSizerSize.Width()), 0 ); + Size aNewSizerSize( aSizerSize.Width(), aNewSize.Height() ); + mpImpl->mpSizer->SetPosSizePixel( aNewSizerPos, aNewSizerSize ); + nSizerWidth = aSizerSize.Width(); + } + + // order the scroll buttons + tools::Long const nHeight = aNewSize.Height(); + // adapt font height? + ImplInitSettings( true, false ); + + mpImpl->mxButtonBox->AdaptToHeight(nHeight); + Size const aBtnsSize(mpImpl->mxButtonBox->get_preferred_size().Width(), nHeight); + Point aPos(mbMirrored ? (aNewSize.Width() - aBtnsSize.Width()) : 0, 0); + mpImpl->mxButtonBox->SetPosSizePixel(aPos, aBtnsSize); + auto nButtonWidth = aBtnsSize.Width(); + + // store size + maWinSize = aNewSize; + + if( mbMirrored ) + { + mnOffX = nSizerWidth; + mnLastOffX = maWinSize.Width() - nButtonWidth - 1; + } + else + { + mnOffX = nButtonWidth; + mnLastOffX = maWinSize.Width() - nSizerWidth - 1; + } + + // reformat + mbSizeFormat = true; + if ( IsReallyVisible() ) + { + if ( ImplCalcWidth() ) + Invalidate(); + + ImplFormat(); + + // Ensure as many tabs as possible are visible: + sal_uInt16 nLastFirstPos = ImplGetLastFirstPos(); + if ( mnFirstPos > nLastFirstPos ) + { + mnFirstPos = nLastFirstPos; + mbFormat = true; + Invalidate(); + } + // Ensure the currently selected page is visible + ImplShowPage(GetPagePos(mnCurPageId)); + + ImplFormat(); + } + + // enable/disable button + ImplEnableControls(); +} + +bool TabBar::PreNotify(NotifyEvent& rNEvt) +{ + if (rNEvt.GetType() == MouseNotifyEvent::COMMAND) + { + if (rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) + { + const CommandWheelData* pData = rNEvt.GetCommandEvent()->GetWheelData(); + sal_uInt16 nNewPos = mnFirstPos; + if (pData->GetNotchDelta() > 0) + { + if (mnFirstPos) + nNewPos = mnFirstPos - 1; + } + else if (pData->GetNotchDelta() < 0) + { + sal_uInt16 nCount = GetPageCount(); + if (mnFirstPos < nCount) + nNewPos = mnFirstPos + 1; + } + if (nNewPos != mnFirstPos) + SetFirstPageId(GetPageId(nNewPos)); + } + } + return Window::PreNotify(rNEvt); +} + +void TabBar::RequestHelp(const HelpEvent& rHEvt) +{ + sal_uInt16 nItemId = GetPageId(ScreenToOutputPixel(rHEvt.GetMousePosPixel())); + if (nItemId) + { + if (rHEvt.GetMode() & HelpEventMode::BALLOON) + { + OUString aStr = GetHelpText(nItemId); + if (!aStr.isEmpty()) + { + tools::Rectangle aItemRect = GetPageRect(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() ); + Help::ShowBalloon(this, aItemRect.Center(), aItemRect, aStr); + return; + } + } + + // show text for quick- or balloon-help + // if this is isolated or not fully visible + if (rHEvt.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON)) + { + sal_uInt16 nPos = GetPagePos(nItemId); + auto& rItem = mpImpl->maItemList[nPos]; + if (rItem.mbShort || (rItem.maRect.Right() - 5 > mnLastOffX)) + { + tools::Rectangle aItemRect = GetPageRect(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 = mpImpl->maItemList[nPos].maText; + 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 TabBar::StateChanged(StateChangedType nType) +{ + Window::StateChanged(nType); + + if (nType == StateChangedType::InitShow) + { + if ( (mbSizeFormat || mbFormat) && !mpImpl->maItemList.empty() ) + ImplFormat(); + } + else if (nType == StateChangedType::Zoom || + nType == StateChangedType::ControlFont) + { + ImplInitSettings(true, false); + Invalidate(); + } + else if (nType == StateChangedType::ControlForeground) + Invalidate(); + else if (nType == StateChangedType::ControlBackground) + { + ImplInitSettings(false, true); + Invalidate(); + } + else if (nType == StateChangedType::Mirroring) + { + bool bIsRTLEnabled = IsRTLEnabled(); + // reacts on calls of EnableRTL, have to mirror all child controls + if (mpImpl->mpSizer) + mpImpl->mpSizer->EnableRTL(bIsRTLEnabled); + if (mpImpl->mxButtonBox) + { + mpImpl->mxButtonBox->m_xFirstButton->set_direction(bIsRTLEnabled); + mpImpl->mxButtonBox->m_xPrevButton->set_direction(bIsRTLEnabled); + mpImpl->mxButtonBox->m_xNextButton->set_direction(bIsRTLEnabled); + mpImpl->mxButtonBox->m_xLastButton->set_direction(bIsRTLEnabled); + mpImpl->mxButtonBox->m_xAddButton->set_direction(bIsRTLEnabled); + } + if (mpImpl->mxEdit) + { + weld::Entry& rEntry = mpImpl->mxEdit->get_widget(); + rEntry.set_direction(bIsRTLEnabled); + } + } +} + +void TabBar::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); + Invalidate(); + } +} + +void TabBar::ImplSelect() +{ + Select(); + CallEventListeners(VclEventId::TabbarPageSelected, reinterpret_cast<void*>(sal::static_int_cast<sal_IntPtr>(mnCurPageId))); +} + +void TabBar::Select() +{ + maSelectHdl.Call(this); +} + +void TabBar::DoubleClick() +{ +} + +void TabBar::Split() +{ + maSplitHdl.Call(this); +} + +void TabBar::ImplActivatePage() +{ + ActivatePage(); + + CallEventListeners(VclEventId::TabbarPageActivated, reinterpret_cast<void*>(sal::static_int_cast<sal_IntPtr>(mnCurPageId))); +} + +void TabBar::ActivatePage() +{} + +bool TabBar::ImplDeactivatePage() +{ + bool bRet = DeactivatePage(); + + CallEventListeners(VclEventId::TabbarPageDeactivated, reinterpret_cast<void*>(sal::static_int_cast<sal_IntPtr>(mnCurPageId))); + + return bRet; +} + +void TabBar::ImplPrePaint() +{ + sal_uInt16 nItemCount = mpImpl->getItemSize(); + if (!nItemCount) + return; + + // tabbar should be formatted + ImplFormat(); + + // assure the actual tabpage becomes visible at first format + if (!mbFirstFormat) + return; + + mbFirstFormat = false; + + if (!mnCurPageId || (mnFirstPos != 0) || mbDropPos) + return; + + auto& rItem = mpImpl->maItemList[GetPagePos(mnCurPageId)]; + if (rItem.maRect.IsEmpty()) + { + // set mbDropPos (or misuse) to prevent Invalidate() + mbDropPos = true; + SetFirstPageId(mnCurPageId); + mbDropPos = false; + if (mnFirstPos != 0) + ImplFormat(); + } +} + +ImplTabBarItem* TabBar::ImplGetLastTabBarItem( sal_uInt16 nItemCount ) +{ + // find last visible entry + sal_uInt16 n = mnFirstPos + 1; + if (n >= nItemCount) + n = nItemCount-1; + ImplTabBarItem* pItem = seek(n); + while (pItem) + { + if (!pItem->maRect.IsEmpty()) + { + n++; + pItem = next(); + } + else + break; + } + + // draw all tabs (from back to front and actual last) + if (pItem) + n--; + else if (n >= nItemCount) + n = nItemCount-1; + pItem = seek(n); + return pItem; +} + +bool TabBar::DeactivatePage() +{ + return true; +} + +bool TabBar::StartRenaming() +{ + return true; +} + +TabBarAllowRenamingReturnCode TabBar::AllowRenaming() +{ + return TABBAR_RENAMING_YES; +} + +void TabBar::EndRenaming() +{ +} + +void TabBar::Mirror() +{ + +} + +void TabBar::AddTabClick() +{ + +} + +void TabBar::InsertPage(sal_uInt16 nPageId, const OUString& rText, + TabBarPageBits nBits, sal_uInt16 nPos) +{ + assert (nPageId && "TabBar::InsertPage(): PageId must not be 0"); + assert ((GetPagePos(nPageId) == PAGE_NOT_FOUND) && "TabBar::InsertPage(): Page already exists"); + assert ((nBits <= TPB_DISPLAY_NAME_ALLFLAGS) && "TabBar::InsertPage(): Invalid flag set in nBits"); + + // create PageItem and insert in the item list + ImplTabBarItem aItem( nPageId, rText, nBits ); + if (nPos < mpImpl->maItemList.size()) + { + auto it = mpImpl->maItemList.begin(); + it += nPos; + mpImpl->maItemList.insert(it, aItem); + } + else + { + mpImpl->maItemList.push_back(aItem); + } + mbSizeFormat = true; + + // set CurPageId if required + if (!mnCurPageId) + mnCurPageId = nPageId; + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + + CallEventListeners(VclEventId::TabbarPageInserted, reinterpret_cast<void*>(sal::static_int_cast<sal_IntPtr>(nPageId))); +} + +Color TabBar::GetTabBgColor(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + if (nPos != PAGE_NOT_FOUND) + return mpImpl->maItemList[nPos].maTabBgColor; + else + return COL_AUTO; +} + +void TabBar::SetTabBgColor(sal_uInt16 nPageId, const Color& aTabBgColor) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos == PAGE_NOT_FOUND) + return; + + auto& rItem = mpImpl->maItemList[nPos]; + if (aTabBgColor != COL_AUTO) + { + rItem.maTabBgColor = aTabBgColor; + if (aTabBgColor.GetLuminance() <= 128) //Do not use aTabBgColor.IsDark(), because that threshold is way too low... + rItem.maTabTextColor = COL_WHITE; + else + rItem.maTabTextColor = COL_BLACK; + } + else + { + rItem.maTabBgColor = COL_AUTO; + rItem.maTabTextColor = COL_AUTO; + } +} + +void TabBar::RemovePage(sal_uInt16 nPageId) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + // does item exist + if (nPos == PAGE_NOT_FOUND) + return; + + if (mnCurPageId == nPageId) + mnCurPageId = 0; + + // check if first visible page should be moved + if (mnFirstPos > nPos) + mnFirstPos--; + + // delete item data + auto it = mpImpl->maItemList.begin(); + it += nPos; + mpImpl->maItemList.erase(it); + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + + CallEventListeners(VclEventId::TabbarPageRemoved, reinterpret_cast<void*>(sal::static_int_cast<sal_IntPtr>(nPageId))); +} + +void TabBar::MovePage(sal_uInt16 nPageId, sal_uInt16 nNewPos) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + Pair aPair(nPos, nNewPos); + + if (nPos < nNewPos) + nNewPos--; + + if (nPos == nNewPos) + return; + + // does item exit + if (nPos == PAGE_NOT_FOUND) + return; + + // move tabbar item in the list + auto it = mpImpl->maItemList.begin(); + it += nPos; + ImplTabBarItem aItem = std::move(*it); + mpImpl->maItemList.erase(it); + if (nNewPos < mpImpl->maItemList.size()) + { + it = mpImpl->maItemList.begin(); + it += nNewPos; + mpImpl->maItemList.insert(it, aItem); + } + else + { + mpImpl->maItemList.push_back(aItem); + } + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + + CallEventListeners( VclEventId::TabbarPageMoved, static_cast<void*>(&aPair) ); +} + +void TabBar::Clear() +{ + // delete all items + mpImpl->maItemList.clear(); + + // remove items from the list + mbSizeFormat = true; + mnCurPageId = 0; + mnFirstPos = 0; + maCurrentItemList = 0; + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + + CallEventListeners(VclEventId::TabbarPageRemoved, reinterpret_cast<void*>(sal::static_int_cast<sal_IntPtr>(PAGE_NOT_FOUND))); +} + +bool TabBar::IsPageEnabled(sal_uInt16 nPageId) const +{ + if (isDisposed()) + return false; + sal_uInt16 nPos = GetPagePos(nPageId); + + return nPos != PAGE_NOT_FOUND; +} + +void TabBar::SetPageBits(sal_uInt16 nPageId, TabBarPageBits nBits) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + if (nPos == PAGE_NOT_FOUND) + return; + + auto& rItem = mpImpl->maItemList[nPos]; + + if (rItem.mnBits != nBits) + { + rItem.mnBits = nBits; + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(rItem.maRect); + } +} + +TabBarPageBits TabBar::GetPageBits(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + if (nPos != PAGE_NOT_FOUND) + return mpImpl->maItemList[nPos].mnBits; + else + return TabBarPageBits::NONE; +} + +sal_uInt16 TabBar::GetPageCount() const +{ + return mpImpl->getItemSize(); +} + +sal_uInt16 TabBar::GetPageId(sal_uInt16 nPos) const +{ + return nPos < mpImpl->maItemList.size() ? mpImpl->maItemList[nPos].mnId : 0; +} + +sal_uInt16 TabBar::GetPagePos(sal_uInt16 nPageId) const +{ + for (size_t i = 0; i < mpImpl->maItemList.size(); ++i) + { + if (mpImpl->maItemList[i].mnId == nPageId) + { + return static_cast<sal_uInt16>(i); + } + } + return PAGE_NOT_FOUND; +} + +sal_uInt16 TabBar::GetPageId(const Point& rPos) const +{ + for (const auto& rItem : mpImpl->maItemList) + { + if (rItem.maRect.Contains(rPos)) + return rItem.mnId; + } + + return 0; +} + +tools::Rectangle TabBar::GetPageRect(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + if (nPos != PAGE_NOT_FOUND) + return mpImpl->maItemList[nPos].maRect; + else + return tools::Rectangle(); +} + +void TabBar::SetCurPageId(sal_uInt16 nPageId) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + // do nothing if item does not exit + if (nPos == PAGE_NOT_FOUND) + return; + + // do nothing if the actual page did not change + if (nPageId == mnCurPageId) + return; + + // make invalid + bool bUpdate = false; + if (IsReallyVisible() && IsUpdateMode()) + bUpdate = true; + + auto& rItem = mpImpl->maItemList[nPos]; + ImplTabBarItem* pOldItem; + + if (mnCurPageId) + pOldItem = &mpImpl->maItemList[GetPagePos(mnCurPageId)]; + else + pOldItem = nullptr; + + // deselect previous page if page was not selected, if this is the + // only selected page + if (!rItem.mbSelect && pOldItem) + { + sal_uInt16 nSelPageCount = GetSelectPageCount(); + if (nSelPageCount == 1) + pOldItem->mbSelect = false; + rItem.mbSelect = true; + } + + mnCurPageId = nPageId; + mbFormat = true; + + // assure the actual page becomes visible + if (IsReallyVisible()) + { + if (nPos < mnFirstPos) + SetFirstPageId(nPageId); + else + { + // calculate visible width + tools::Long nWidth = mnLastOffX; + if (nWidth > ADDNEWPAGE_AREAWIDTH) + nWidth -= ADDNEWPAGE_AREAWIDTH; + + if (rItem.maRect.IsEmpty()) + ImplFormat(); + + while ((mbMirrored ? (rItem.maRect.Left() < mnOffX) : (rItem.maRect.Right() > nWidth)) || + rItem.maRect.IsEmpty()) + { + sal_uInt16 nNewPos = mnFirstPos + 1; + // assure at least the actual tabpages are visible as first tabpage + if (nNewPos >= nPos) + { + SetFirstPageId(nPageId); + break; + } + else + SetFirstPageId(GetPageId(nNewPos)); + ImplFormat(); + // abort if first page is not forwarded + if (nNewPos != mnFirstPos) + break; + } + } + } + + // redraw bar + if (bUpdate) + { + Invalidate(rItem.maRect); + if (pOldItem) + Invalidate(pOldItem->maRect); + } +} + +void TabBar::MakeVisible(sal_uInt16 nPageId) +{ + if (!IsReallyVisible()) + return; + + sal_uInt16 nPos = GetPagePos(nPageId); + + // do nothing if item does not exist + if (nPos == PAGE_NOT_FOUND) + return; + + if (nPos < mnFirstPos) + SetFirstPageId(nPageId); + else + { + auto& rItem = mpImpl->maItemList[nPos]; + + // calculate visible area + tools::Long nWidth = mnLastOffX; + + if (mbFormat || rItem.maRect.IsEmpty()) + { + mbFormat = true; + ImplFormat(); + } + + while ((rItem.maRect.Right() > nWidth) || + rItem.maRect.IsEmpty()) + { + sal_uInt16 nNewPos = mnFirstPos+1; + // assure at least the actual tabpages are visible as first tabpage + if (nNewPos >= nPos) + { + SetFirstPageId(nPageId); + break; + } + else + SetFirstPageId(GetPageId(nNewPos)); + ImplFormat(); + // abort if first page is not forwarded + if (nNewPos != mnFirstPos) + break; + } + } +} + +void TabBar::SetFirstPageId(sal_uInt16 nPageId) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + // return false if item does not exist + if (nPos == PAGE_NOT_FOUND) + return; + + if (nPos == mnFirstPos) + return; + + // assure as much pages are visible as possible + ImplFormat(); + sal_uInt16 nLastFirstPos = ImplGetLastFirstPos(); + sal_uInt16 nNewPos; + if (nPos > nLastFirstPos) + nNewPos = nLastFirstPos; + else + nNewPos = nPos; + + if (nNewPos != mnFirstPos) + { + mnFirstPos = nNewPos; + mbFormat = true; + + // redraw bar (attention: check mbDropPos, + // as if this flag was set, we do not re-paint immediately + if (IsReallyVisible() && IsUpdateMode() && !mbDropPos) + Invalidate(); + } +} + +void TabBar::SelectPage(sal_uInt16 nPageId, bool bSelect) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + if (nPos == PAGE_NOT_FOUND) + return; + + auto& rItem = mpImpl->maItemList[nPos]; + + if (rItem.mbSelect != bSelect) + { + rItem.mbSelect = bSelect; + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(rItem.maRect); + } +} + +sal_uInt16 TabBar::GetSelectPageCount() const +{ + sal_uInt16 nSelected = 0; + for (const auto& rItem : mpImpl->maItemList) + { + if (rItem.mbSelect) + nSelected++; + } + + return nSelected; +} + +bool TabBar::IsPageSelected(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos != PAGE_NOT_FOUND) + return mpImpl->maItemList[nPos].mbSelect; + else + return false; +} + +void TabBar::SetProtectionSymbol(sal_uInt16 nPageId, bool bProtection) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos != PAGE_NOT_FOUND) + { + if (mpImpl->maItemList[nPos].mbProtect != bProtection) + { + mpImpl->maItemList[nPos].mbProtect = bProtection; + mbSizeFormat = true; // render text width changes, thus bar width + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + } + } +} + +bool TabBar::StartEditMode(sal_uInt16 nPageId) +{ + sal_uInt16 nPos = GetPagePos( nPageId ); + if (mpImpl->mxEdit || (nPos == PAGE_NOT_FOUND) || (mnLastOffX < 8)) + return false; + + mnEditId = nPageId; + if (StartRenaming()) + { + ImplShowPage(nPos); + ImplFormat(); + PaintImmediately(); + + mpImpl->mxEdit.disposeAndReset(VclPtr<TabBarEdit>::Create(this)); + tools::Rectangle aRect = GetPageRect( mnEditId ); + tools::Long nX = aRect.Left(); + tools::Long nWidth = aRect.GetWidth(); + if (mnEditId != GetCurPageId()) + nX += 1; + if (nX + nWidth > mnLastOffX) + nWidth = mnLastOffX-nX; + if (nWidth < 3) + { + nX = aRect.Left(); + nWidth = aRect.GetWidth(); + } + weld::Entry& rEntry = mpImpl->mxEdit->get_widget(); + rEntry.set_text(GetPageText(mnEditId)); + mpImpl->mxEdit->SetPosSizePixel(Point(nX, aRect.Top() + mnOffY + 1), Size(nWidth, aRect.GetHeight() - 3)); + vcl::Font aFont = GetPointFont(*GetOutDev()); // FIXME RenderContext + + Color aForegroundColor; + Color aBackgroundColor; + Color aFaceColor; + Color aSelectColor; + Color aFaceTextColor; + Color aSelectTextColor; + + ImplGetColors(Application::GetSettings().GetStyleSettings(), aFaceColor, aFaceTextColor, aSelectColor, aSelectTextColor); + + if (mnEditId != GetCurPageId()) + { + aFont.SetWeight(WEIGHT_LIGHT); + } + if (IsPageSelected(mnEditId) || mnEditId == GetCurPageId()) + { + aForegroundColor = aSelectTextColor; + aBackgroundColor = aSelectColor; + } + else + { + aForegroundColor = aFaceTextColor; + aBackgroundColor = aFaceColor; + } + if (GetPageBits( mnEditId ) & TabBarPageBits::Blue) + { + aForegroundColor = COL_LIGHTBLUE; + } + rEntry.set_font(aFont); + rEntry.set_font_color(aForegroundColor); + mpImpl->mxEdit->SetControlBackground(aBackgroundColor); + rEntry.grab_focus(); + rEntry.select_region(0, -1); + mpImpl->mxEdit->Show(); + return true; + } + else + { + mnEditId = 0; + return false; + } +} + +bool TabBar::IsInEditMode() const +{ + return bool(mpImpl->mxEdit); +} + +void TabBar::EndEditMode(bool bCancel) +{ + if (!mpImpl->mxEdit) + return; + + // call hdl + bool bEnd = true; + mbEditCanceled = bCancel; + weld::Entry& rEntry = mpImpl->mxEdit->get_widget(); + maEditText = rEntry.get_text(); + mpImpl->mxEdit->SetPostEvent(); + if (!bCancel) + { + TabBarAllowRenamingReturnCode nAllowRenaming = AllowRenaming(); + if (nAllowRenaming == TABBAR_RENAMING_YES) + SetPageText(mnEditId, maEditText); + else if (nAllowRenaming == TABBAR_RENAMING_NO) + bEnd = false; + else // nAllowRenaming == TABBAR_RENAMING_CANCEL + mbEditCanceled = true; + } + + // renaming not allowed, then reset edit data + if (!bEnd) + { + mpImpl->mxEdit->ResetPostEvent(); + rEntry.grab_focus(); + } + else + { + // close edit and call end hdl + mpImpl->mxEdit.disposeAndClear(); + + EndRenaming(); + mnEditId = 0; + } + + // reset + maEditText.clear(); + mbEditCanceled = false; +} + +void TabBar::SetMirrored(bool bMirrored) +{ + if (mbMirrored != bMirrored) + { + mbMirrored = bMirrored; + mbSizeFormat = true; + ImplInitControls(); // for button images + Resize(); // recalculates control positions + Mirror(); + } +} + +void TabBar::SetEffectiveRTL(bool bRTL) +{ + SetMirrored( bRTL != AllSettings::GetLayoutRTL() ); +} + +bool TabBar::IsEffectiveRTL() const +{ + return IsMirrored() != AllSettings::GetLayoutRTL(); +} + +void TabBar::SetMaxPageWidth(tools::Long nMaxWidth) +{ + if (mnMaxPageWidth != nMaxWidth) + { + mnMaxPageWidth = nMaxWidth; + mbSizeFormat = true; + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + } +} + +void TabBar::SetPageText(sal_uInt16 nPageId, const OUString& rText) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos != PAGE_NOT_FOUND) + { + mpImpl->maItemList[nPos].maText = rText; + mbSizeFormat = true; + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + + CallEventListeners(VclEventId::TabbarPageTextChanged, reinterpret_cast<void*>(sal::static_int_cast<sal_IntPtr>(nPageId))); + } +} + +OUString TabBar::GetPageText(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos != PAGE_NOT_FOUND) + return mpImpl->maItemList[nPos].maText; + return OUString(); +} + +OUString TabBar::GetAuxiliaryText(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos != PAGE_NOT_FOUND) + return mpImpl->maItemList[nPos].maAuxiliaryText; + return OUString(); +} + +void TabBar::SetAuxiliaryText(sal_uInt16 nPageId, const OUString& rText ) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos != PAGE_NOT_FOUND) + { + mpImpl->maItemList[nPos].maAuxiliaryText = rText; + // no redraw bar, no CallEventListener, internal use in LayerTabBar + } +} + +OUString TabBar::GetHelpText(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos != PAGE_NOT_FOUND) + { + auto& rItem = mpImpl->maItemList[nPos]; + if (rItem.maHelpText.isEmpty() && !rItem.maHelpId.isEmpty()) + { + Help* pHelp = Application::GetHelp(); + if (pHelp) + rItem.maHelpText = pHelp->GetHelpText(OStringToOUString(rItem.maHelpId, RTL_TEXTENCODING_UTF8), this); + } + + return rItem.maHelpText; + } + return OUString(); +} + +bool TabBar::StartDrag(const CommandEvent& rCEvt, vcl::Region& rRegion) +{ + if (!(mnWinStyle & WB_DRAG) || (rCEvt.GetCommand() != CommandEventId::StartDrag)) + return false; + + // Check if the clicked page was selected. If this is not the case + // set it as actual entry. We check for this only at a mouse action + // if Drag and Drop can be triggered from the keyboard. + // We only do this, if Select() was not triggered, as the Select() + // could have scrolled the area + if (rCEvt.IsMouseEvent() && !mbInSelect) + { + sal_uInt16 nSelId = GetPageId(rCEvt.GetMousePosPixel()); + + // do not start dragging if no entry was clicked + if (!nSelId) + return false; + + // check if page was selected. If not set it as actual + // page and call Select() + if (!IsPageSelected(nSelId)) + { + if (ImplDeactivatePage()) + { + SetCurPageId(nSelId); + PaintImmediately(); + ImplActivatePage(); + ImplSelect(); + } + else + return false; + } + } + mbInSelect = false; + + vcl::Region aRegion; + + // assign region + rRegion = aRegion; + + return true; +} + +sal_uInt16 TabBar::ShowDropPos(const Point& rPos) +{ + sal_uInt16 nNewDropPos; + sal_uInt16 nItemCount = mpImpl->getItemSize(); + sal_Int16 nScroll = 0; + + if (rPos.X() > mnLastOffX-TABBAR_DRAG_SCROLLOFF) + { + auto& rItem = mpImpl->maItemList[mpImpl->maItemList.size() - 1]; + if (!rItem.maRect.IsEmpty() && (rPos.X() > rItem.maRect.Right())) + nNewDropPos = mpImpl->getItemSize(); + else + { + nNewDropPos = mnFirstPos + 1; + nScroll = 1; + } + } + else if ((rPos.X() <= mnOffX) || + (!mnOffX && (rPos.X() <= TABBAR_DRAG_SCROLLOFF))) + { + if (mnFirstPos) + { + nNewDropPos = mnFirstPos; + nScroll = -1; + } + else + nNewDropPos = 0; + } + else + { + sal_uInt16 nDropId = GetPageId(rPos); + if (nDropId) + { + nNewDropPos = GetPagePos(nDropId); + if (mnFirstPos && (nNewDropPos == mnFirstPos - 1)) + nScroll = -1; + } + else + nNewDropPos = nItemCount; + } + + if (mbDropPos && (nNewDropPos == mnDropPos) && !nScroll) + return mnDropPos; + + if (mbDropPos) + HideDropPos(); + mbDropPos = true; + mnDropPos = nNewDropPos; + + if (nScroll) + { + sal_uInt16 nOldFirstPos = mnFirstPos; + SetFirstPageId(GetPageId(mnFirstPos + nScroll)); + + // draw immediately, as Paint not possible during Drag and Drop + if (nOldFirstPos != mnFirstPos) + { + tools::Rectangle aRect(mnOffX, 0, mnLastOffX, maWinSize.Height()); + GetOutDev()->SetFillColor(GetBackground().GetColor()); + GetOutDev()->DrawRect(aRect); + Invalidate(aRect); + } + } + + // draw drop position arrows + Color aBlackColor(COL_BLACK); + tools::Long nX; + tools::Long nY = (maWinSize.Height() / 2) - 1; + sal_uInt16 nCurPos = GetPagePos(mnCurPageId); + + sal_Int32 nTriangleWidth = 3 * GetDPIScaleFactor(); + + if (mnDropPos < nItemCount) + { + GetOutDev()->SetLineColor(aBlackColor); + GetOutDev()->SetFillColor(aBlackColor); + + auto& rItem = mpImpl->maItemList[mnDropPos]; + nX = rItem.maRect.Left(); + if ( mnDropPos == nCurPos ) + nX--; + else + nX++; + + if (!rItem.IsDefaultTabBgColor() && !rItem.mbSelect) + { + GetOutDev()->SetLineColor(rItem.maTabTextColor); + GetOutDev()->SetFillColor(rItem.maTabTextColor); + } + + tools::Polygon aPoly(3); + aPoly.SetPoint(Point(nX, nY), 0); + aPoly.SetPoint(Point(nX + nTriangleWidth, nY - nTriangleWidth), 1); + aPoly.SetPoint(Point(nX + nTriangleWidth, nY + nTriangleWidth), 2); + GetOutDev()->DrawPolygon(aPoly); + } + if (mnDropPos > 0 && mnDropPos < nItemCount + 1) + { + GetOutDev()->SetLineColor(aBlackColor); + GetOutDev()->SetFillColor(aBlackColor); + + auto& rItem = mpImpl->maItemList[mnDropPos - 1]; + nX = rItem.maRect.Right(); + if (mnDropPos == nCurPos) + nX++; + if (!rItem.IsDefaultTabBgColor() && !rItem.mbSelect) + { + GetOutDev()->SetLineColor(rItem.maTabTextColor); + GetOutDev()->SetFillColor(rItem.maTabTextColor); + } + tools::Polygon aPoly(3); + aPoly.SetPoint(Point(nX, nY), 0); + aPoly.SetPoint(Point(nX - nTriangleWidth, nY - nTriangleWidth), 1); + aPoly.SetPoint(Point(nX - nTriangleWidth, nY + nTriangleWidth), 2); + GetOutDev()->DrawPolygon(aPoly); + } + + return mnDropPos; +} + +void TabBar::HideDropPos() +{ + if (!mbDropPos) + return; + + tools::Long nX; + tools::Long nY1 = (maWinSize.Height() / 2) - 3; + tools::Long nY2 = nY1 + 5; + sal_uInt16 nItemCount = mpImpl->getItemSize(); + + if (mnDropPos < nItemCount) + { + auto& rItem = mpImpl->maItemList[mnDropPos]; + nX = rItem.maRect.Left(); + // immediately call Paint, as it is not possible during drag and drop + tools::Rectangle aRect( nX-1, nY1, nX+3, nY2 ); + vcl::Region aRegion( aRect ); + GetOutDev()->SetClipRegion( aRegion ); + Invalidate(aRect); + GetOutDev()->SetClipRegion(); + } + if (mnDropPos > 0 && mnDropPos < nItemCount + 1) + { + auto& rItem = mpImpl->maItemList[mnDropPos - 1]; + nX = rItem.maRect.Right(); + // immediately call Paint, as it is not possible during drag and drop + tools::Rectangle aRect(nX - 2, nY1, nX + 1, nY2); + vcl::Region aRegion(aRect); + GetOutDev()->SetClipRegion(aRegion); + Invalidate(aRect); + GetOutDev()->SetClipRegion(); + } + + mbDropPos = false; + mnDropPos = 0; +} + +void TabBar::SwitchPage(const Point& rPos) +{ + sal_uInt16 nSwitchId = GetPageId(rPos); + if (!nSwitchId) + EndSwitchPage(); + else + { + if (nSwitchId != mnSwitchId) + { + mnSwitchId = nSwitchId; + mnSwitchTime = tools::Time::GetSystemTicks(); + } + else + { + // change only after 500 ms + if (mnSwitchId != GetCurPageId()) + { + if (tools::Time::GetSystemTicks() > mnSwitchTime + 500) + { + if (ImplDeactivatePage()) + { + SetCurPageId( mnSwitchId ); + PaintImmediately(); + ImplActivatePage(); + ImplSelect(); + } + } + } + } + } +} + +void TabBar::EndSwitchPage() +{ + mnSwitchTime = 0; + mnSwitchId = 0; +} + +void TabBar::SetStyle(WinBits nStyle) +{ + if (mnWinStyle == nStyle) + return; + mnWinStyle = nStyle; + ImplInitControls(); + // order possible controls + if (IsReallyVisible() && IsUpdateMode()) + Resize(); +} + +Size TabBar::CalcWindowSizePixel() const +{ + tools::Long nWidth = 0; + + if (!mpImpl->maItemList.empty()) + { + const_cast<TabBar*>(this)->ImplCalcWidth(); + for (const auto& rItem : mpImpl->maItemList) + { + nWidth += rItem.mnWidth; + } + } + + return Size(nWidth, GetSettings().GetStyleSettings().GetScrollBarSize()); +} + +tools::Rectangle TabBar::GetPageArea() const +{ + return tools::Rectangle(Point(mnOffX, mnOffY), + Size(mnLastOffX - mnOffX + 1, GetSizePixel().Height() - mnOffY)); +} + +void TabBar::SetAddButtonEnabled(bool bAddButtonEnabled) +{ + mpImpl->mxButtonBox->m_xAddButton->set_sensitive(bAddButtonEnabled); +} + +css::uno::Reference<css::accessibility::XAccessible> TabBar::CreateAccessible() +{ + return mpImpl->maAccessibleFactory.getFactory().createAccessibleTabBar(*this); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/toolbarmenu.cxx b/svtools/source/control/toolbarmenu.cxx new file mode 100644 index 000000000..4cd797bca --- /dev/null +++ b/svtools/source/control/toolbarmenu.cxx @@ -0,0 +1,217 @@ +/* -*- 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 <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> + +#include <vcl/taskpanelist.hxx> +#include <vcl/svapp.hxx> + +#include <framestatuslistener.hxx> +#include <svtools/toolbarmenu.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::accessibility; + +namespace { + +SystemWindow* GetTopMostParentSystemWindow(const vcl::Window& rWindow) +{ + // ->manually search topmost system window + // required because their might be another system window between this and the top window + vcl::Window* pWindow = rWindow.GetParent(); + SystemWindow* pTopMostSysWin = nullptr; + while ( pWindow ) + { + if ( pWindow->IsSystemWindow() ) + pTopMostSysWin = static_cast<SystemWindow*>(pWindow); + pWindow = pWindow->GetParent(); + } + return pTopMostSysWin; +} + +class ToolbarPopupStatusListener : public svt::FrameStatusListener +{ +public: + ToolbarPopupStatusListener( const css::uno::Reference< css::frame::XFrame >& xFrame, + WeldToolbarPopup& rToolbarPopup ); + + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL statusChanged( const css::frame::FeatureStateEvent& Event ) override; + + WeldToolbarPopup* mpPopup; +}; + + +ToolbarPopupStatusListener::ToolbarPopupStatusListener( + const css::uno::Reference< css::frame::XFrame >& xFrame, + WeldToolbarPopup& rToolbarPopup ) +: svt::FrameStatusListener( ::comphelper::getProcessComponentContext(), xFrame ) +, mpPopup( &rToolbarPopup ) +{ +} + + +void SAL_CALL ToolbarPopupStatusListener::dispose() +{ + mpPopup = nullptr; + svt::FrameStatusListener::dispose(); +} + + +void SAL_CALL ToolbarPopupStatusListener::statusChanged( const css::frame::FeatureStateEvent& Event ) +{ + if( mpPopup ) + mpPopup->statusChanged( Event ); +} + +} + +void WeldToolbarPopup::AddStatusListener(const OUString& rCommandURL) +{ + if (!m_xStatusListener.is()) + m_xStatusListener.set(new ToolbarPopupStatusListener(m_xFrame, *this)); + + m_xStatusListener->addStatusListener(rCommandURL); +} + +void WeldToolbarPopup::statusChanged(const css::frame::FeatureStateEvent& /*Event*/) +{ +} + +void InterimToolbarPopup::EndPopupMode() +{ + GetDockingManager()->EndPopupMode(this); +} + +WeldToolbarPopup::WeldToolbarPopup(const css::uno::Reference<css::frame::XFrame>& rFrame, + weld::Widget* pParent, const OUString& rUIFile, + const OString& rId) + : m_xBuilder(Application::CreateBuilder(pParent, rUIFile)) + , m_xTopLevel(m_xBuilder->weld_popover(rId)) + , m_xContainer(m_xBuilder->weld_container("container")) + , m_xFrame(rFrame) +{ + m_xTopLevel->connect_focus_in(LINK(this, WeldToolbarPopup, FocusHdl)); +} + +WeldToolbarPopup::~WeldToolbarPopup() +{ + if (m_xStatusListener.is()) + m_xStatusListener->dispose(); +} + +IMPL_LINK_NOARG(WeldToolbarPopup, FocusHdl, weld::Widget&, void) +{ + GrabFocus(); +} + +ToolbarPopupContainer::ToolbarPopupContainer(weld::Widget* pParent) + : m_xBuilder(Application::CreateBuilder(pParent, "svx/ui/toolbarpopover.ui")) + , m_xTopLevel(m_xBuilder->weld_container("ToolbarPopover")) + , m_xContainer(m_xBuilder->weld_container("container")) +{ + m_xTopLevel->connect_focus_in(LINK(this, ToolbarPopupContainer, FocusHdl)); +} + +void ToolbarPopupContainer::setPopover(std::unique_ptr<WeldToolbarPopup> xPopup) +{ + m_xPopup = std::move(xPopup); + // move the WeldToolbarPopup contents into this toolbar so on-demand contents can appear inside a preexisting gtk popover + // because the arrow for the popover is only enabled if there's a popover set + m_xPopup->getTopLevel()->move(m_xPopup->getContainer(), m_xContainer.get()); + + // in online LoseFocus event is fired due to this line and popup is closed + // when first time opened any popup from not focused sidebar + if (!comphelper::LibreOfficeKit::isActive()) + m_xPopup->GrabFocus(); +} + +void ToolbarPopupContainer::unsetPopover() +{ + if (!m_xPopup) + return; + m_xContainer->move(m_xPopup->getContainer(), m_xPopup->getTopLevel()); + m_xPopup.reset(); +} + +ToolbarPopupContainer::~ToolbarPopupContainer() +{ + unsetPopover(); +} + +IMPL_LINK_NOARG(ToolbarPopupContainer, FocusHdl, weld::Widget&, void) +{ + if (m_xPopup) + m_xPopup->GrabFocus(); +} + +InterimToolbarPopup::InterimToolbarPopup(const css::uno::Reference<css::frame::XFrame>& rFrame, vcl::Window* pParent, + std::unique_ptr<WeldToolbarPopup> xPopup, bool bTearable) + : DropdownDockingWindow(pParent, rFrame, bTearable) + , m_xFrame(rFrame) + , m_xBuilder(Application::CreateInterimBuilder(m_xBox.get(), "svt/ui/interimparent.ui", false)) + , m_xContainer(m_xBuilder->weld_container("container")) + , m_xPopup(std::move(xPopup)) +{ + if (SystemWindow* pWindow = GetTopMostParentSystemWindow(*this)) + pWindow->GetTaskPaneList()->AddWindow(this); + + // move the WeldToolbarPopup contents into this interim toolbar so welded contents can appear as a dropdown in an unwelded toolbar + m_xPopup->getTopLevel()->move(m_xPopup->getContainer(), m_xContainer.get()); +} + +void InterimToolbarPopup::GetFocus() +{ + DropdownDockingWindow::GetFocus(); + if (!m_xPopup) + return; + m_xPopup->GrabFocus(); +} + +void InterimToolbarPopup::dispose() +{ + if (SystemWindow* pWindow = GetTopMostParentSystemWindow(*this)) + pWindow->GetTaskPaneList()->RemoveWindow(this); + + // if we have focus when disposed, pick the document window as destination + // for focus rather than let it go to an arbitrary windows + if (HasFocus()) + { + if (auto xWindow = m_xFrame->getContainerWindow()) + xWindow->setFocus(); + } + // move the contents back where it belongs + m_xContainer->move(m_xPopup->getContainer(), m_xPopup->getTopLevel()); + m_xPopup.reset(); + m_xContainer.reset(); + m_xBuilder.reset(); + m_xFrame.clear(); + DropdownDockingWindow::dispose(); +} + +InterimToolbarPopup::~InterimToolbarPopup() +{ + disposeOnce(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/valueacc.cxx b/svtools/source/control/valueacc.cxx new file mode 100644 index 000000000..23d3dcde9 --- /dev/null +++ b/svtools/source/control/valueacc.cxx @@ -0,0 +1,993 @@ +/* -*- 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 <unotools/accessiblestatesethelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <svtools/valueset.hxx> +#include "valueimp.hxx" +#include <comphelper/servicehelper.hxx> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> + +using namespace ::com::sun::star; + + +ValueSetItem::ValueSetItem( ValueSet& rParent ) + : mrParent(rParent) + , mpData(nullptr) + , mxAcc() + , mnId(0) + , meType(VALUESETITEM_NONE) + , mbVisible(true) +{ +} + + +ValueSetItem::~ValueSetItem() +{ + if( mxAcc.is() ) + { + mxAcc->ParentDestroyed(); + } +} + +uno::Reference< accessibility::XAccessible > ValueSetItem::GetAccessible( bool bIsTransientChildrenDisabled ) +{ + if( !mxAcc.is() ) + mxAcc = new ValueItemAcc( this, bIsTransientChildrenDisabled ); + + return mxAcc; +} + +ValueItemAcc::ValueItemAcc( ValueSetItem* pParent, bool bIsTransientChildrenDisabled ) : + mpParent( pParent ), + mbIsTransientChildrenDisabled( bIsTransientChildrenDisabled ) +{ +} + +ValueItemAcc::~ValueItemAcc() +{ +} + +void ValueItemAcc::ParentDestroyed() +{ + std::scoped_lock aGuard( maMutex ); + mpParent = nullptr; +} + +const uno::Sequence< sal_Int8 >& ValueItemAcc::getUnoTunnelId() +{ + static const comphelper::UnoIdInit theValueItemAccUnoTunnelId; + return theValueItemAccUnoTunnelId.getSeq(); +} + + +ValueItemAcc* ValueItemAcc::getImplementation( const uno::Reference< uno::XInterface >& rxData ) + noexcept +{ + try + { + return comphelper::getFromUnoTunnel<ValueItemAcc>(rxData); + } + catch(const css::uno::Exception&) + { + return nullptr; + } +} + + +uno::Reference< accessibility::XAccessibleContext > SAL_CALL ValueItemAcc::getAccessibleContext() +{ + return this; +} + + +sal_Int32 SAL_CALL ValueItemAcc::getAccessibleChildCount() +{ + return 0; +} + + +uno::Reference< accessibility::XAccessible > SAL_CALL ValueItemAcc::getAccessibleChild( sal_Int32 ) +{ + throw lang::IndexOutOfBoundsException(); +} + + +uno::Reference< accessibility::XAccessible > SAL_CALL ValueItemAcc::getAccessibleParent() +{ + const SolarMutexGuard aSolarGuard; + uno::Reference< accessibility::XAccessible > xRet; + + if( mpParent ) + xRet = mpParent->mrParent.mxAccessible; + + return xRet; +} + + +sal_Int32 SAL_CALL ValueItemAcc::getAccessibleIndexInParent() +{ + const SolarMutexGuard aSolarGuard; + // The index defaults to -1 to indicate the child does not belong to its + // parent. + sal_Int32 nIndexInParent = -1; + + if( mpParent ) + { + bool bDone = false; + + sal_uInt16 nCount = mpParent->mrParent.ImplGetVisibleItemCount(); + ValueSetItem* pItem; + for (sal_uInt16 i=0; i<nCount && !bDone; i++) + { + // Guard the retrieval of the i-th child with a try/catch block + // just in case the number of children changes in the meantime. + try + { + pItem = mpParent->mrParent.ImplGetItem(i); + } + catch (const lang::IndexOutOfBoundsException&) + { + pItem = nullptr; + } + + // Do not create an accessible object for the test. + if (pItem != nullptr && pItem->mxAcc.is()) + if (pItem->GetAccessible( mbIsTransientChildrenDisabled ).get() == this ) + { + nIndexInParent = i; + bDone = true; + } + } + } + + //if this valueset contain a none field(common value is default), then we should increase the real index and set the noitem index value equal 0. + if ( mpParent && ( (mpParent->mrParent.GetStyle() & WB_NONEFIELD) != 0 ) ) + { + ValueSetItem* pFirstItem = mpParent->mrParent.ImplGetItem (VALUESET_ITEM_NONEITEM); + if( pFirstItem && pFirstItem ->GetAccessible(mbIsTransientChildrenDisabled).get() == this ) + nIndexInParent = 0; + else + nIndexInParent++; + } + return nIndexInParent; +} + + +sal_Int16 SAL_CALL ValueItemAcc::getAccessibleRole() +{ + return accessibility::AccessibleRole::LIST_ITEM; +} + + +OUString SAL_CALL ValueItemAcc::getAccessibleDescription() +{ + return OUString(); +} + + +OUString SAL_CALL ValueItemAcc::getAccessibleName() +{ + const SolarMutexGuard aSolarGuard; + + if( mpParent ) + { + if (mpParent->maText.isEmpty()) + return "Item " + OUString::number(static_cast<sal_Int32>(mpParent->mnId)); + else + return mpParent->maText; + } + + return OUString(); +} + + +uno::Reference< accessibility::XAccessibleRelationSet > SAL_CALL ValueItemAcc::getAccessibleRelationSet() +{ + return uno::Reference< accessibility::XAccessibleRelationSet >(); +} + + +uno::Reference< accessibility::XAccessibleStateSet > SAL_CALL ValueItemAcc::getAccessibleStateSet() +{ + const SolarMutexGuard aSolarGuard; + rtl::Reference<::utl::AccessibleStateSetHelper> pStateSet = new ::utl::AccessibleStateSetHelper; + + if( mpParent ) + { + pStateSet->AddState (accessibility::AccessibleStateType::ENABLED); + pStateSet->AddState (accessibility::AccessibleStateType::SENSITIVE); + pStateSet->AddState (accessibility::AccessibleStateType::SHOWING); + pStateSet->AddState (accessibility::AccessibleStateType::VISIBLE); + if ( !mbIsTransientChildrenDisabled ) + pStateSet->AddState (accessibility::AccessibleStateType::TRANSIENT); + + // SELECTABLE + pStateSet->AddState( accessibility::AccessibleStateType::SELECTABLE ); + // pStateSet->AddState( accessibility::AccessibleStateType::FOCUSABLE ); + + // SELECTED + if( mpParent->mrParent.GetSelectedItemId() == mpParent->mnId ) + { + pStateSet->AddState( accessibility::AccessibleStateType::SELECTED ); + // pStateSet->AddState( accessibility::AccessibleStateType::FOCUSED ); + } + } + + return pStateSet; +} + + +lang::Locale SAL_CALL ValueItemAcc::getLocale() +{ + const SolarMutexGuard aSolarGuard; + uno::Reference< accessibility::XAccessible > xParent( getAccessibleParent() ); + lang::Locale aRet( "", "", "" ); + + if( xParent.is() ) + { + uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() ); + + if( xParentContext.is() ) + aRet = xParentContext->getLocale(); + } + + return aRet; +} + + +void SAL_CALL ValueItemAcc::addAccessibleEventListener( const uno::Reference< accessibility::XAccessibleEventListener >& rxListener ) +{ + std::scoped_lock aGuard( maMutex ); + + if( !rxListener.is() ) + return; + + bool bFound = false; + + for (auto const& eventListener : mxEventListeners) + { + if(eventListener == rxListener) + { + bFound = true; + break; + } + } + + if (!bFound) + mxEventListeners.push_back( rxListener ); +} + + +void SAL_CALL ValueItemAcc::removeAccessibleEventListener( const uno::Reference< accessibility::XAccessibleEventListener >& rxListener ) +{ + std::scoped_lock aGuard( maMutex ); + + if( rxListener.is() ) + { + ::std::vector< uno::Reference< accessibility::XAccessibleEventListener > >::iterator aIter = + std::find(mxEventListeners.begin(), mxEventListeners.end(), rxListener); + + if (aIter != mxEventListeners.end()) + mxEventListeners.erase(aIter); + } +} + + +sal_Bool SAL_CALL ValueItemAcc::containsPoint( const awt::Point& aPoint ) +{ + const awt::Rectangle aRect( getBounds() ); + const Point aSize( aRect.Width, aRect.Height ); + const Point aNullPoint, aTestPoint( aPoint.X, aPoint.Y ); + + return tools::Rectangle( aNullPoint, aSize ).Contains( aTestPoint ); +} + +uno::Reference< accessibility::XAccessible > SAL_CALL ValueItemAcc::getAccessibleAtPoint( const awt::Point& ) +{ + uno::Reference< accessibility::XAccessible > xRet; + return xRet; +} + +awt::Rectangle SAL_CALL ValueItemAcc::getBounds() +{ + const SolarMutexGuard aSolarGuard; + awt::Rectangle aRet; + + if( mpParent ) + { + tools::Rectangle aRect( mpParent->mrParent.GetItemRect(mpParent->mnId) ); + tools::Rectangle aParentRect( Point(), mpParent->mrParent.GetOutputSizePixel() ); + + aRect.Intersection( aParentRect ); + + aRet.X = aRect.Left(); + aRet.Y = aRect.Top(); + aRet.Width = aRect.GetWidth(); + aRet.Height = aRect.GetHeight(); + } + + return aRet; +} + +awt::Point SAL_CALL ValueItemAcc::getLocation() +{ + const awt::Rectangle aRect( getBounds() ); + awt::Point aRet; + + aRet.X = aRect.X; + aRet.Y = aRect.Y; + + return aRet; +} + +awt::Point SAL_CALL ValueItemAcc::getLocationOnScreen() +{ + const SolarMutexGuard aSolarGuard; + awt::Point aRet; + + if( mpParent ) + { + const Point aPos = mpParent->mrParent.GetItemRect(mpParent->mnId).TopLeft(); + const Point aScreenPos(mpParent->mrParent.GetDrawingArea()->get_accessible_location_on_screen()); + + aRet.X = aPos.X() + aScreenPos.X(); + aRet.Y = aPos.Y() + aScreenPos.Y(); + } + + return aRet; +} + +awt::Size SAL_CALL ValueItemAcc::getSize() +{ + const awt::Rectangle aRect( getBounds() ); + awt::Size aRet; + + aRet.Width = aRect.Width; + aRet.Height = aRect.Height; + + return aRet; +} + +void SAL_CALL ValueItemAcc::grabFocus() +{ + // nothing to do +} + +sal_Int32 SAL_CALL ValueItemAcc::getForeground( ) +{ + Color nColor = Application::GetSettings().GetStyleSettings().GetWindowTextColor(); + return static_cast<sal_Int32>(nColor); +} + +sal_Int32 SAL_CALL ValueItemAcc::getBackground( ) +{ + Color nColor; + if (mpParent && mpParent->meType == VALUESETITEM_COLOR) + nColor = mpParent->maColor; + else + nColor = Application::GetSettings().GetStyleSettings().GetWindowColor(); + return static_cast<sal_Int32>(nColor); +} + +sal_Int64 SAL_CALL ValueItemAcc::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + return comphelper::getSomethingImpl(rId, this); +} + +void ValueItemAcc::FireAccessibleEvent( short nEventId, const uno::Any& rOldValue, const uno::Any& rNewValue ) +{ + if( !nEventId ) + return; + + ::std::vector< uno::Reference< accessibility::XAccessibleEventListener > > aTmpListeners( mxEventListeners ); + accessibility::AccessibleEventObject aEvtObject; + + aEvtObject.EventId = nEventId; + aEvtObject.Source = static_cast<uno::XWeak*>(this); + aEvtObject.NewValue = rNewValue; + aEvtObject.OldValue = rOldValue; + + for (auto const& tmpListener : aTmpListeners) + { + tmpListener->notifyEvent( aEvtObject ); + } +} + +ValueSetAcc::ValueSetAcc( ValueSet* pParent ) : + mpParent( pParent ), + mbIsFocused(false) +{ +} + + +ValueSetAcc::~ValueSetAcc() +{ +} + + +void ValueSetAcc::FireAccessibleEvent( short nEventId, const uno::Any& rOldValue, const uno::Any& rNewValue ) +{ + if( !nEventId ) + return; + + ::std::vector< uno::Reference< accessibility::XAccessibleEventListener > > aTmpListeners( mxEventListeners ); + accessibility::AccessibleEventObject aEvtObject; + + aEvtObject.EventId = nEventId; + aEvtObject.Source = static_cast<uno::XWeak*>(this); + aEvtObject.NewValue = rNewValue; + aEvtObject.OldValue = rOldValue; + + for (auto const& tmpListener : aTmpListeners) + { + try + { + tmpListener->notifyEvent( aEvtObject ); + } + catch(const uno::Exception&) + { + } + } +} + +const uno::Sequence< sal_Int8 >& ValueSetAcc::getUnoTunnelId() +{ + static const comphelper::UnoIdInit theValueSetAccUnoTunnelId; + return theValueSetAccUnoTunnelId.getSeq(); +} + + +ValueSetAcc* ValueSetAcc::getImplementation( const uno::Reference< uno::XInterface >& rxData ) + noexcept +{ + try + { + return comphelper::getFromUnoTunnel<ValueSetAcc>(rxData); + } + catch(const css::uno::Exception&) + { + return nullptr; + } +} + + +void ValueSetAcc::GetFocus() +{ + mbIsFocused = true; + + // Broadcast the state change. + css::uno::Any aOldState, aNewState; + aNewState <<= css::accessibility::AccessibleStateType::FOCUSED; + FireAccessibleEvent( + css::accessibility::AccessibleEventId::STATE_CHANGED, + aOldState, aNewState); +} + + +void ValueSetAcc::LoseFocus() +{ + mbIsFocused = false; + + // Broadcast the state change. + css::uno::Any aOldState, aNewState; + aOldState <<= css::accessibility::AccessibleStateType::FOCUSED; + FireAccessibleEvent( + css::accessibility::AccessibleEventId::STATE_CHANGED, + aOldState, aNewState); +} + + +uno::Reference< accessibility::XAccessibleContext > SAL_CALL ValueSetAcc::getAccessibleContext() +{ + ThrowIfDisposed(); + return this; +} + + +sal_Int32 SAL_CALL ValueSetAcc::getAccessibleChildCount() +{ + const SolarMutexGuard aSolarGuard; + ThrowIfDisposed(); + + sal_Int32 nCount = mpParent->ImplGetVisibleItemCount(); + if (HasNoneField()) + nCount += 1; + return nCount; +} + + +uno::Reference< accessibility::XAccessible > SAL_CALL ValueSetAcc::getAccessibleChild( sal_Int32 i ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + ValueSetItem* pItem = getItem (sal::static_int_cast< sal_uInt16 >(i)); + + if( !pItem ) + throw lang::IndexOutOfBoundsException(); + + uno::Reference< accessibility::XAccessible > xRet = pItem->GetAccessible( false/*bIsTransientChildrenDisabled*/ ); + return xRet; +} + +uno::Reference< accessibility::XAccessible > SAL_CALL ValueSetAcc::getAccessibleParent() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + return mpParent->GetDrawingArea()->get_accessible_parent(); +} + +sal_Int32 SAL_CALL ValueSetAcc::getAccessibleIndexInParent() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + + // -1 for child not found/no parent (according to specification) + sal_Int32 nRet = -1; + + uno::Reference<accessibility::XAccessible> xParent(getAccessibleParent()); + if (!xParent) + return nRet; + + try + { + uno::Reference<accessibility::XAccessibleContext> xParentContext(xParent->getAccessibleContext()); + + // iterate over parent's children and search for this object + if ( xParentContext.is() ) + { + sal_Int32 nChildCount = xParentContext->getAccessibleChildCount(); + for ( sal_Int32 nChild = 0; ( nChild < nChildCount ) && ( -1 == nRet ); ++nChild ) + { + uno::Reference<XAccessible> xChild(xParentContext->getAccessibleChild(nChild)); + if ( xChild.get() == this ) + nRet = nChild; + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "svtools", "OAccessibleContextHelper::getAccessibleIndexInParent" ); + } + + return nRet; +} + +sal_Int16 SAL_CALL ValueSetAcc::getAccessibleRole() +{ + ThrowIfDisposed(); + return accessibility::AccessibleRole::LIST; +} + + +OUString SAL_CALL ValueSetAcc::getAccessibleDescription() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + OUString aRet; + + if (mpParent) + { + aRet = mpParent->GetAccessibleDescription(); + } + + return aRet; +} + + +OUString SAL_CALL ValueSetAcc::getAccessibleName() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + OUString aRet; + + if (mpParent) + { + aRet = mpParent->GetAccessibleName(); + } + + return aRet; +} + +uno::Reference< accessibility::XAccessibleRelationSet > SAL_CALL ValueSetAcc::getAccessibleRelationSet() +{ + ThrowIfDisposed(); + SolarMutexGuard g; + return mpParent->GetDrawingArea()->get_accessible_relation_set(); +} + +uno::Reference< accessibility::XAccessibleStateSet > SAL_CALL ValueSetAcc::getAccessibleStateSet() +{ + ThrowIfDisposed(); + rtl::Reference<::utl::AccessibleStateSetHelper> pStateSet = new ::utl::AccessibleStateSetHelper(); + + // Set some states. + pStateSet->AddState (accessibility::AccessibleStateType::ENABLED); + pStateSet->AddState (accessibility::AccessibleStateType::SENSITIVE); + pStateSet->AddState (accessibility::AccessibleStateType::SHOWING); + pStateSet->AddState (accessibility::AccessibleStateType::VISIBLE); + pStateSet->AddState (accessibility::AccessibleStateType::MANAGES_DESCENDANTS); + pStateSet->AddState (accessibility::AccessibleStateType::FOCUSABLE); + if (mbIsFocused) + pStateSet->AddState (accessibility::AccessibleStateType::FOCUSED); + + return pStateSet; +} + + +lang::Locale SAL_CALL ValueSetAcc::getLocale() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + uno::Reference< accessibility::XAccessible > xParent( getAccessibleParent() ); + lang::Locale aRet( "", "", "" ); + + if( xParent.is() ) + { + uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() ); + + if( xParentContext.is() ) + aRet = xParentContext->getLocale (); + } + + return aRet; +} + + +void SAL_CALL ValueSetAcc::addAccessibleEventListener( const uno::Reference< accessibility::XAccessibleEventListener >& rxListener ) +{ + ThrowIfDisposed(); + std::unique_lock aGuard (m_aMutex); + + if( !rxListener.is() ) + return; + + bool bFound = false; + + for (auto const& eventListener : mxEventListeners) + { + if(eventListener == rxListener) + { + bFound = true; + break; + } + } + + if (!bFound) + mxEventListeners.push_back( rxListener ); +} + + +void SAL_CALL ValueSetAcc::removeAccessibleEventListener( const uno::Reference< accessibility::XAccessibleEventListener >& rxListener ) +{ + ThrowIfDisposed(); + std::unique_lock aGuard (m_aMutex); + + if( rxListener.is() ) + { + ::std::vector< uno::Reference< accessibility::XAccessibleEventListener > >::iterator aIter = + std::find(mxEventListeners.begin(), mxEventListeners.end(), rxListener); + + if (aIter != mxEventListeners.end()) + mxEventListeners.erase(aIter); + } +} + + +sal_Bool SAL_CALL ValueSetAcc::containsPoint( const awt::Point& aPoint ) +{ + ThrowIfDisposed(); + const awt::Rectangle aRect( getBounds() ); + const Point aSize( aRect.Width, aRect.Height ); + const Point aNullPoint, aTestPoint( aPoint.X, aPoint.Y ); + + return tools::Rectangle( aNullPoint, aSize ).Contains( aTestPoint ); +} + + +uno::Reference< accessibility::XAccessible > SAL_CALL ValueSetAcc::getAccessibleAtPoint( const awt::Point& aPoint ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + const sal_uInt16 nItemId = mpParent->GetItemId( Point( aPoint.X, aPoint.Y ) ); + uno::Reference< accessibility::XAccessible > xRet; + + if ( nItemId ) + { + const size_t nItemPos = mpParent->GetItemPos( nItemId ); + + if( VALUESET_ITEM_NONEITEM != nItemPos ) + { + ValueSetItem *const pItem = mpParent->mItemList[nItemPos].get(); + xRet = pItem->GetAccessible( false/*bIsTransientChildrenDisabled*/ ); + } + } + + return xRet; +} + + +awt::Rectangle SAL_CALL ValueSetAcc::getBounds() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + const Point aOutPos; + const Size aOutSize( mpParent->GetOutputSizePixel() ); + awt::Rectangle aRet; + + aRet.X = aOutPos.X(); + aRet.Y = aOutPos.Y(); + aRet.Width = aOutSize.Width(); + aRet.Height = aOutSize.Height(); + + return aRet; +} + +awt::Point SAL_CALL ValueSetAcc::getLocation() +{ + ThrowIfDisposed(); + const awt::Rectangle aRect( getBounds() ); + awt::Point aRet; + + aRet.X = aRect.X; + aRet.Y = aRect.Y; + + return aRet; +} + +awt::Point SAL_CALL ValueSetAcc::getLocationOnScreen() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + awt::Point aScreenLoc(0, 0); + + uno::Reference<accessibility::XAccessible> xParent(getAccessibleParent()); + if (xParent) + { + uno::Reference<accessibility::XAccessibleContext> xParentContext(xParent->getAccessibleContext()); + uno::Reference<accessibility::XAccessibleComponent> xParentComponent(xParentContext, css::uno::UNO_QUERY); + OSL_ENSURE( xParentComponent.is(), "ValueSetAcc::getLocationOnScreen: no parent component!" ); + if ( xParentComponent.is() ) + { + awt::Point aParentScreenLoc( xParentComponent->getLocationOnScreen() ); + awt::Point aOwnRelativeLoc( getLocation() ); + aScreenLoc.X = aParentScreenLoc.X + aOwnRelativeLoc.X; + aScreenLoc.Y = aParentScreenLoc.Y + aOwnRelativeLoc.Y; + } + } + + return aScreenLoc; +} + +awt::Size SAL_CALL ValueSetAcc::getSize() +{ + ThrowIfDisposed(); + const awt::Rectangle aRect( getBounds() ); + awt::Size aRet; + + aRet.Width = aRect.Width; + aRet.Height = aRect.Height; + + return aRet; +} + +void SAL_CALL ValueSetAcc::grabFocus() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + mpParent->GrabFocus(); +} + +sal_Int32 SAL_CALL ValueSetAcc::getForeground( ) +{ + ThrowIfDisposed(); + Color nColor = Application::GetSettings().GetStyleSettings().GetWindowTextColor(); + return static_cast<sal_Int32>(nColor); +} + +sal_Int32 SAL_CALL ValueSetAcc::getBackground( ) +{ + ThrowIfDisposed(); + Color nColor = Application::GetSettings().GetStyleSettings().GetWindowColor(); + return static_cast<sal_Int32>(nColor); +} + +void SAL_CALL ValueSetAcc::selectAccessibleChild( sal_Int32 nChildIndex ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + ValueSetItem* pItem = getItem (sal::static_int_cast< sal_uInt16 >(nChildIndex)); + + if(pItem == nullptr) + throw lang::IndexOutOfBoundsException(); + + mpParent->SelectItem( pItem->mnId ); +} + + +sal_Bool SAL_CALL ValueSetAcc::isAccessibleChildSelected( sal_Int32 nChildIndex ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + ValueSetItem* pItem = getItem (sal::static_int_cast< sal_uInt16 >(nChildIndex)); + + if (pItem == nullptr) + throw lang::IndexOutOfBoundsException(); + + bool bRet = mpParent->IsItemSelected( pItem->mnId ); + return bRet; +} + + +void SAL_CALL ValueSetAcc::clearAccessibleSelection() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + mpParent->SetNoSelection(); +} + + +void SAL_CALL ValueSetAcc::selectAllAccessibleChildren() +{ + ThrowIfDisposed(); + // unsupported due to single selection only +} + + +sal_Int32 SAL_CALL ValueSetAcc::getSelectedAccessibleChildCount() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + sal_Int32 nRet = 0; + + for( sal_uInt16 i = 0, nCount = getItemCount(); i < nCount; i++ ) + { + ValueSetItem* pItem = getItem (i); + + if( pItem && mpParent->IsItemSelected( pItem->mnId ) ) + ++nRet; + } + + return nRet; +} + + +uno::Reference< accessibility::XAccessible > SAL_CALL ValueSetAcc::getSelectedAccessibleChild( sal_Int32 nSelectedChildIndex ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + uno::Reference< accessibility::XAccessible > xRet; + + for( sal_uInt16 i = 0, nCount = getItemCount(), nSel = 0; ( i < nCount ) && !xRet.is(); i++ ) + { + ValueSetItem* pItem = getItem(i); + + if( pItem && mpParent->IsItemSelected( pItem->mnId ) && ( nSelectedChildIndex == static_cast< sal_Int32 >( nSel++ ) ) ) + xRet = pItem->GetAccessible( false/*bIsTransientChildrenDisabled*/ ); + } + + return xRet; +} + + +void SAL_CALL ValueSetAcc::deselectAccessibleChild( sal_Int32 nChildIndex ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + // Because of the single selection we can reset the whole selection when + // the specified child is currently selected. + if (isAccessibleChildSelected(nChildIndex)) + mpParent->SetNoSelection(); +} + + +sal_Int64 SAL_CALL ValueSetAcc::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + return comphelper::getSomethingImpl(rId, this); +} + + +void ValueSetAcc::disposing(std::unique_lock<std::mutex>& rGuard) +{ + // Make a copy of the list and clear the original. + ::std::vector<uno::Reference<accessibility::XAccessibleEventListener> > aListenerListCopy = std::move(mxEventListeners); + + // Reset the pointer to the parent. It has to be the one who has + // disposed us because he is dying. + mpParent = nullptr; + + if (aListenerListCopy.empty()) + return; + + rGuard.unlock(); + // Inform all listeners that this objects is disposing. + lang::EventObject aEvent (static_cast<accessibility::XAccessible*>(this)); + for (auto const& listenerCopy : aListenerListCopy) + { + try + { + listenerCopy->disposing (aEvent); + } + catch(const uno::Exception&) + { + // Ignore exceptions. + } + } +} + + +sal_uInt16 ValueSetAcc::getItemCount() const +{ + sal_uInt16 nCount = mpParent->ImplGetVisibleItemCount(); + // When the None-Item is visible then increase the number of items by + // one. + if (HasNoneField()) + nCount += 1; + return nCount; +} + +ValueSetItem* ValueSetAcc::getItem (sal_uInt16 nIndex) const +{ + ValueSetItem* pItem = nullptr; + + if (HasNoneField()) + { + if (nIndex == 0) + // When present the first item is the then always visible none field. + pItem = mpParent->ImplGetItem (VALUESET_ITEM_NONEITEM); + else + // Shift down the index to compensate for the none field. + nIndex -= 1; + } + if (pItem == nullptr) + pItem = mpParent->ImplGetItem (nIndex); + + return pItem; +} + + +void ValueSetAcc::ThrowIfDisposed() +{ + if (m_bDisposed) + { + SAL_WARN("svx", "Calling disposed object. Throwing exception:"); + throw lang::DisposedException ( + "object has been already disposed", + static_cast<uno::XWeak*>(this)); + } + else + { + DBG_ASSERT (mpParent!=nullptr, "ValueSetAcc not disposed but mpParent == NULL"); + } +} + +bool ValueSetAcc::HasNoneField() const +{ + assert(mpParent && "ValueSetAcc::HasNoneField called with mpParent==NULL"); + return ((mpParent->GetStyle() & WB_NONEFIELD) != 0); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/valueimp.hxx b/svtools/source/control/valueimp.hxx new file mode 100644 index 000000000..1eb77b763 --- /dev/null +++ b/svtools/source/control/valueimp.hxx @@ -0,0 +1,252 @@ +/* -*- 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 <tools/color.hxx> +#include <vcl/image.hxx> +#include <cppuhelper/implbase.hxx> +#include <comphelper/compbase.hxx> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> + +#include <mutex> +#include <vector> + +#define VALUESET_ITEM_NONEITEM 0xFFFE + +enum ValueSetItemType +{ + VALUESETITEM_NONE, + VALUESETITEM_IMAGE, + VALUESETITEM_IMAGE_AND_TEXT, + VALUESETITEM_COLOR, + VALUESETITEM_USERDRAW +}; + +class ValueItemAcc; +class ValueSet; + +struct ValueSetItem +{ + ValueSet& mrParent; + OUString maText; + void* mpData; + rtl::Reference< ValueItemAcc > mxAcc; + Image maImage; + Color maColor; + sal_uInt16 mnId; + sal_uInt8 meType; + bool mbVisible; + + explicit ValueSetItem( ValueSet& rParent ); + ~ValueSetItem(); + + css::uno::Reference< css::accessibility::XAccessible > + GetAccessible( bool bIsTransientChildrenDisabled ); +}; + +typedef comphelper::WeakComponentImplHelper< + css::accessibility::XAccessible, + css::accessibility::XAccessibleEventBroadcaster, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleComponent, + css::accessibility::XAccessibleSelection, + css::lang::XUnoTunnel > + ValueSetAccComponentBase; + +class ValueSetAcc final : public ValueSetAccComponentBase +{ +public: + + explicit ValueSetAcc(ValueSet* pParent); + virtual ~ValueSetAcc() override; + + void FireAccessibleEvent( short nEventId, const css::uno::Any& rOldValue, const css::uno::Any& rNewValue ); + bool HasAccessibleListeners() const { return( mxEventListeners.size() > 0 ); } + + static ValueSetAcc* getImplementation( const css::uno::Reference< css::uno::XInterface >& rxData ) noexcept; + +public: + + /** Called by the corresponding ValueSet when it gets the focus. + Stores the new focus state and broadcasts a state change event. + */ + void GetFocus(); + + /** Called by the corresponding ValueSet when it loses the focus. + Stores the new focus state and broadcasts a state change event. + */ + void LoseFocus(); + + // XAccessible + virtual css::uno::Reference< css::accessibility::XAccessibleContext > SAL_CALL getAccessibleContext( ) override; + + // XAccessibleEventBroadcaster + virtual void SAL_CALL addAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + virtual void SAL_CALL removeAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + // XAccessibleContext + virtual sal_Int32 SAL_CALL getAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleChild( sal_Int32 i ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleParent( ) override; + virtual sal_Int32 SAL_CALL getAccessibleIndexInParent( ) override; + virtual sal_Int16 SAL_CALL getAccessibleRole( ) override; + virtual OUString SAL_CALL getAccessibleDescription( ) override; + virtual OUString SAL_CALL getAccessibleName( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL getAccessibleRelationSet( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleStateSet > SAL_CALL getAccessibleStateSet( ) override; + virtual css::lang::Locale SAL_CALL getLocale( ) override; + + // XAccessibleComponent + virtual sal_Bool SAL_CALL containsPoint( const css::awt::Point& aPoint ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleAtPoint( const css::awt::Point& aPoint ) override; + virtual css::awt::Rectangle SAL_CALL getBounds( ) override; + virtual css::awt::Point SAL_CALL getLocation( ) override; + virtual css::awt::Point SAL_CALL getLocationOnScreen( ) override; + virtual css::awt::Size SAL_CALL getSize( ) override; + virtual void SAL_CALL grabFocus( ) override; + virtual sal_Int32 SAL_CALL getForeground( ) override; + virtual sal_Int32 SAL_CALL getBackground( ) override; + + // XAccessibleSelection + virtual void SAL_CALL selectAccessibleChild( sal_Int32 nChildIndex ) override; + virtual sal_Bool SAL_CALL isAccessibleChildSelected( sal_Int32 nChildIndex ) override; + virtual void SAL_CALL clearAccessibleSelection( ) override; + virtual void SAL_CALL selectAllAccessibleChildren( ) override; + virtual sal_Int32 SAL_CALL getSelectedAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getSelectedAccessibleChild( sal_Int32 nSelectedChildIndex ) override; + virtual void SAL_CALL deselectAccessibleChild( sal_Int32 nSelectedChildIndex ) override; + + // XUnoTunnel + static const css::uno::Sequence< sal_Int8 >& getUnoTunnelId(); + virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& rId ) override; + +private: + ::std::vector< css::uno::Reference< + css::accessibility::XAccessibleEventListener > > mxEventListeners; + ValueSet* mpParent; + /// The current FOCUSED state. + bool mbIsFocused; + + /** Tell all listeners that the object is dying. This callback is + usually called from the WeakComponentImplHelper class. + */ + virtual void disposing(std::unique_lock<std::mutex>&) override; + + /** Return the number of items. This takes the None-Item into account. + */ + sal_uInt16 getItemCount() const; + + /** Return the item associated with the given index. The None-Item is + taken into account which, when present, is taken to be the first + (with index 0) item. + @param nIndex + Index of the item to return. The index 0 denotes the None-Item + when present. + @return + Returns NULL when the given index is out of range. + */ + ValueSetItem* getItem (sal_uInt16 nIndex) const; + + /** Check whether or not the object has been disposed (or is in the + state of being disposed). If that is the case then + DisposedException is thrown to inform the (indirect) caller of the + foul deed. + @throws css::lang::DisposedException + */ + void ThrowIfDisposed(); + + /** Check whether the value set has a 'none' field, i.e. a field (button) + that deselects any items (selects none of them). + @return + Returns <true/> if there is a 'none' field and <false/> if it is + missing. + */ + bool HasNoneField() const; +}; + +class ValueItemAcc : public ::cppu::WeakImplHelper< css::accessibility::XAccessible, + css::accessibility::XAccessibleEventBroadcaster, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleComponent, + css::lang::XUnoTunnel > +{ +private: + + ::std::vector< css::uno::Reference< + css::accessibility::XAccessibleEventListener > > mxEventListeners; + std::mutex maMutex; + ValueSetItem* mpParent; + bool mbIsTransientChildrenDisabled; + +public: + + ValueItemAcc(ValueSetItem* pParent, bool bIsTransientChildrenDisabled); + virtual ~ValueItemAcc() override; + + void ParentDestroyed(); + + void FireAccessibleEvent( short nEventId, const css::uno::Any& rOldValue, const css::uno::Any& rNewValue ); + + static ValueItemAcc* getImplementation( const css::uno::Reference< css::uno::XInterface >& rxData ) noexcept; + +public: + + // XAccessible + virtual css::uno::Reference< css::accessibility::XAccessibleContext > SAL_CALL getAccessibleContext( ) override; + + // XAccessibleEventBroadcaster + virtual void SAL_CALL addAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + virtual void SAL_CALL removeAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + // XAccessibleContext + virtual sal_Int32 SAL_CALL getAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleChild( sal_Int32 i ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleParent( ) override; + virtual sal_Int32 SAL_CALL getAccessibleIndexInParent( ) override; + virtual sal_Int16 SAL_CALL getAccessibleRole( ) override; + virtual OUString SAL_CALL getAccessibleDescription( ) override; + virtual OUString SAL_CALL getAccessibleName( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL getAccessibleRelationSet( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleStateSet > SAL_CALL getAccessibleStateSet( ) override; + virtual css::lang::Locale SAL_CALL getLocale( ) override; + + // XAccessibleComponent + virtual sal_Bool SAL_CALL containsPoint( const css::awt::Point& aPoint ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleAtPoint( const css::awt::Point& aPoint ) override; + virtual css::awt::Rectangle SAL_CALL getBounds( ) override; + virtual css::awt::Point SAL_CALL getLocation( ) override; + virtual css::awt::Point SAL_CALL getLocationOnScreen( ) override; + virtual css::awt::Size SAL_CALL getSize( ) override; + virtual void SAL_CALL grabFocus( ) override; + virtual sal_Int32 SAL_CALL getForeground( ) override; + virtual sal_Int32 SAL_CALL getBackground( ) override; + + // XUnoTunnel + static const css::uno::Sequence< sal_Int8 >& getUnoTunnelId(); + virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& rId ) override; +}; + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/valueset.cxx b/svtools/source/control/valueset.cxx new file mode 100644 index 000000000..069379779 --- /dev/null +++ b/svtools/source/control/valueset.cxx @@ -0,0 +1,1999 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <tools/debug.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/decoview.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> + +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include "valueimp.hxx" + +#include <svtools/valueset.hxx> + +#include <uiobject.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> + +using namespace css::uno; +using namespace css::lang; +using namespace css::accessibility; + +namespace +{ +void collectUIInformation( const OUString& aID , const OUString& aParentID , const OUString& aPos ) +{ + EventDescription aDescription; + aDescription.aID = aID ; + aDescription.aParameters = {{"POS", aPos }}; + aDescription.aAction = "SELECT"; + aDescription.aKeyWord = "ValueSet"; + aDescription.aParent = aParentID; + UITestLogger::getInstance().logEvent(aDescription); +} + +enum +{ + ITEM_OFFSET = 4, + ITEM_OFFSET_DOUBLE = 6, + NAME_LINE_OFF_X = 2, + NAME_LINE_OFF_Y = 2, + NAME_LINE_HEIGHT = 2, + NAME_OFFSET = 2, +}; + +} + +ValueSet::ValueSet(std::unique_ptr<weld::ScrolledWindow> pScrolledWindow) + : maVirDev( VclPtr<VirtualDevice>::Create()) + , mxScrolledWindow(std::move(pScrolledWindow)) + , mnHighItemId(0) + , maColor(COL_TRANSPARENT) + , mnStyle(0) + , mbFormat(true) + , mbHighlight(false) +{ + maVirDev->SetBackground(Application::GetSettings().GetStyleSettings().GetFaceColor()); + + mnItemWidth = 0; + mnItemHeight = 0; + mnTextOffset = 0; + mnVisLines = 0; + mnLines = 0; + mnUserItemWidth = 0; + mnUserItemHeight = 0; + mnFirstLine = 0; + mnSelItemId = 0; + mnSavedItemId = -1; + mnCols = 0; + mnCurCol = 0; + mnUserCols = 0; + mnUserVisLines = 0; + mnSpacing = 0; + mnFrameStyle = DrawFrameStyle::NONE; + mbNoSelection = true; + mbDoubleSel = false; + mbScroll = false; + mbFullMode = true; + mbEdgeBlending = false; + mbHasVisibleItems = false; + + if (mxScrolledWindow) + mxScrolledWindow->connect_vadjustment_changed(LINK(this, ValueSet, ImplScrollHdl)); +} + +void ValueSet::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + CustomWidgetController::SetDrawingArea(pDrawingArea); + // #106446#, #106601# force mirroring of virtual device + maVirDev->EnableRTL(pDrawingArea->get_direction()); +} + +Reference<XAccessible> ValueSet::CreateAccessible() +{ + if (!mxAccessible) + mxAccessible.set(new ValueSetAcc(this)); + return mxAccessible; +} + +ValueSet::~ValueSet() +{ + Reference<XComponent> xComponent(mxAccessible, UNO_QUERY); + if (xComponent.is()) + xComponent->dispose(); + + ImplDeleteItems(); +} + +void ValueSet::ImplDeleteItems() +{ + const size_t n = mItemList.size(); + + for ( size_t i = 0; i < n; ++i ) + { + ValueSetItem* pItem = mItemList[i].get(); + if ( pItem->mbVisible && ImplHasAccessibleListeners() ) + { + Any aOldAny; + Any aNewAny; + + aOldAny <<= pItem->GetAccessible( false/*bIsTransientChildrenDisabled*/ ); + ImplFireAccessibleEvent(AccessibleEventId::CHILD, aOldAny, aNewAny); + } + + mItemList[i].reset(); + } + + mItemList.clear(); +} + +void ValueSet::Select() +{ + collectUIInformation(OStringToOUString(GetDrawingArea()->get_buildable_name(),RTL_TEXTENCODING_UTF8) , OStringToOUString(GetDrawingArea()->get_help_id(),RTL_TEXTENCODING_UTF8) , OUString::number(GetSelectedItemId())); + maSelectHdl.Call( this ); +} + +void ValueSet::UserDraw( const UserDrawEvent& ) +{ +} + +size_t ValueSet::ImplGetItem( const Point& rPos ) const +{ + if (!mbHasVisibleItems) + { + return VALUESET_ITEM_NOTFOUND; + } + + if (mpNoneItem && maNoneItemRect.Contains(rPos)) + { + return VALUESET_ITEM_NONEITEM; + } + + if (maItemListRect.Contains(rPos)) + { + const int xc = rPos.X() - maItemListRect.Left(); + const int yc = rPos.Y() - maItemListRect.Top(); + // The point is inside the area of item list, + // let's find the containing item. + const int col = xc / (mnItemWidth + mnSpacing); + const int x = xc % (mnItemWidth + mnSpacing); + const int row = yc / (mnItemHeight + mnSpacing); + const int y = yc % (mnItemHeight + mnSpacing); + + if (x < mnItemWidth && y < mnItemHeight) + { + // the point is inside item rect and not inside spacing + const size_t item = (mnFirstLine + row) * static_cast<size_t>(mnCols) + col; + if (item < mItemList.size()) + { + return item; + } + } + } + + return VALUESET_ITEM_NOTFOUND; +} + +ValueSetItem* ValueSet::ImplGetItem( size_t nPos ) +{ + if (nPos == VALUESET_ITEM_NONEITEM) + return mpNoneItem.get(); + else + return (nPos < mItemList.size()) ? mItemList[nPos].get() : nullptr; +} + +ValueSetItem* ValueSet::ImplGetFirstItem() +{ + return !mItemList.empty() ? mItemList[0].get() : nullptr; +} + +sal_uInt16 ValueSet::ImplGetVisibleItemCount() const +{ + sal_uInt16 nRet = 0; + const size_t nItemCount = mItemList.size(); + + for ( size_t n = 0; n < nItemCount; ++n ) + { + if ( mItemList[n]->mbVisible ) + ++nRet; + } + + return nRet; +} + +void ValueSet::ImplFireAccessibleEvent( short nEventId, const Any& rOldValue, const Any& rNewValue ) +{ + ValueSetAcc* pAcc = ValueSetAcc::getImplementation(mxAccessible); + + if( pAcc ) + pAcc->FireAccessibleEvent( nEventId, rOldValue, rNewValue ); +} + +bool ValueSet::ImplHasAccessibleListeners() const +{ + ValueSetAcc* pAcc = ValueSetAcc::getImplementation(mxAccessible); + return( pAcc && pAcc->HasAccessibleListeners() ); +} + +IMPL_LINK(ValueSet, ImplScrollHdl, weld::ScrolledWindow&, rScrollWin, void) +{ + auto nNewFirstLine = rScrollWin.vadjustment_get_value(); + if ( nNewFirstLine != mnFirstLine ) + { + mnFirstLine = nNewFirstLine; + mbFormat = true; + Invalidate(); + } +} + +void ValueSet::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + if (GetStyle() & WB_FLATVALUESET) + { + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(rStyleSettings.GetFaceColor()); + tools::Long nOffY = maVirDev->GetOutputSizePixel().Height(); + Size aWinSize(GetOutputSizePixel()); + rRenderContext.DrawRect(tools::Rectangle(Point(0, nOffY ), Point( aWinSize.Width(), aWinSize.Height()))); + } + + ImplDraw(rRenderContext); +} + +void ValueSet::GetFocus() +{ + SAL_INFO("svtools", "value set getting focus"); + Invalidate(); + CustomWidgetController::GetFocus(); + + // Tell the accessible object that we got the focus. + ValueSetAcc* pAcc = ValueSetAcc::getImplementation(mxAccessible); + if (pAcc) + pAcc->GetFocus(); +} + +void ValueSet::LoseFocus() +{ + SAL_INFO("svtools", "value set losing focus"); + Invalidate(); + CustomWidgetController::LoseFocus(); + + // Tell the accessible object that we lost the focus. + ValueSetAcc* pAcc = ValueSetAcc::getImplementation(mxAccessible); + if( pAcc ) + pAcc->LoseFocus(); +} + +void ValueSet::Resize() +{ + mbFormat = true; + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(); + CustomWidgetController::Resize(); +} + +bool ValueSet::KeyInput( const KeyEvent& rKeyEvent ) +{ + size_t nLastItem = mItemList.size(); + + if ( !nLastItem || !ImplGetFirstItem() ) + return CustomWidgetController::KeyInput(rKeyEvent); + + if (mbFormat) + Invalidate(); + + --nLastItem; + + const size_t nCurPos + = mnSelItemId ? GetItemPos(mnSelItemId) : (mpNoneItem ? VALUESET_ITEM_NONEITEM : 0); + size_t nItemPos = VALUESET_ITEM_NOTFOUND; + size_t nVStep = mnCols; + + switch (rKeyEvent.GetKeyCode().GetCode()) + { + case KEY_HOME: + nItemPos = mpNoneItem ? VALUESET_ITEM_NONEITEM : 0; + break; + + case KEY_END: + nItemPos = nLastItem; + break; + + case KEY_LEFT: + if (nCurPos != VALUESET_ITEM_NONEITEM) + { + if (nCurPos) + { + nItemPos = nCurPos-1; + } + else if (mpNoneItem) + { + nItemPos = VALUESET_ITEM_NONEITEM; + } + } + break; + + case KEY_RIGHT: + if (nCurPos < nLastItem) + { + if (nCurPos == VALUESET_ITEM_NONEITEM) + { + nItemPos = 0; + } + else + { + nItemPos = nCurPos+1; + } + } + break; + + case KEY_PAGEUP: + if (rKeyEvent.GetKeyCode().IsShift() || rKeyEvent.GetKeyCode().IsMod1() || rKeyEvent.GetKeyCode().IsMod2()) + { + return CustomWidgetController::KeyInput(rKeyEvent); + } + nVStep *= mnVisLines; + [[fallthrough]]; + case KEY_UP: + if (nCurPos != VALUESET_ITEM_NONEITEM) + { + if (nCurPos == nLastItem) + { + const size_t nCol = mnCols ? nLastItem % mnCols : 0; + if (nCol < mnCurCol) + { + // Move to previous row/page, keeping the old column + nVStep -= mnCurCol - nCol; + } + } + if (nCurPos >= nVStep) + { + // Go up of a whole page + nItemPos = nCurPos-nVStep; + } + else if (mpNoneItem) + { + nItemPos = VALUESET_ITEM_NONEITEM; + } + else if (nCurPos > mnCols) + { + // Go to same column in first row + nItemPos = nCurPos % mnCols; + } + } + break; + + case KEY_PAGEDOWN: + if (rKeyEvent.GetKeyCode().IsShift() || rKeyEvent.GetKeyCode().IsMod1() || rKeyEvent.GetKeyCode().IsMod2()) + { + return CustomWidgetController::KeyInput(rKeyEvent); + } + nVStep *= mnVisLines; + [[fallthrough]]; + case KEY_DOWN: + if (nCurPos != nLastItem) + { + if (nCurPos == VALUESET_ITEM_NONEITEM) + { + nItemPos = nVStep-mnCols+mnCurCol; + } + else + { + nItemPos = nCurPos+nVStep; + } + if (nItemPos > nLastItem) + { + nItemPos = nLastItem; + } + } + break; + + case KEY_RETURN: + if (GetStyle() & WB_NO_DIRECTSELECT) + { + // tdf#142479 on return select the entry the cursor is in + // before calling Select + if (nCurPos != VALUESET_ITEM_NONEITEM) + { + const sal_uInt16 nItemId = GetItemId(nCurPos); + if (nItemId != mnSelItemId) + SelectItem(nItemId); + } + Select(); + break; + } + [[fallthrough]]; + default: + return CustomWidgetController::KeyInput(rKeyEvent); + } + + if ( nItemPos == VALUESET_ITEM_NOTFOUND ) + return true; + + if ( nItemPos!=VALUESET_ITEM_NONEITEM && nItemPos<nLastItem ) + { + // update current column only in case of a new position + // which is also not a "specially" handled one. + mnCurCol = mnCols ? nItemPos % mnCols : 0; + } + const sal_uInt16 nItemId = (nItemPos != VALUESET_ITEM_NONEITEM) ? GetItemId( nItemPos ) : 0; + if ( nItemId != mnSelItemId ) + { + SelectItem( nItemId ); + if (!(GetStyle() & WB_NO_DIRECTSELECT)) + { + // select only if WB_NO_DIRECTSELECT is not set + Select(); + } + } + + return true; +} + +void ValueSet::ImplTracking(bool bLeaveWindow, const Point& rPos) +{ + ValueSetItem* pItem = bLeaveWindow ? nullptr : ImplGetItem(ImplGetItem(rPos)); + if ( pItem ) + { + if( GetStyle() & WB_MENUSTYLEVALUESET || GetStyle() & WB_FLATVALUESET ) + mbHighlight = true; + + ImplHighlightItem(pItem->mnId); + } + else + { + if( GetStyle() & WB_MENUSTYLEVALUESET || GetStyle() & WB_FLATVALUESET ) + mbHighlight = true; + + ImplHighlightItem(0); + } +} + +bool ValueSet::MouseButtonDown( const MouseEvent& rMouseEvent ) +{ + if (rMouseEvent.IsLeft() && !rMouseEvent.IsMod2()) + { + bool bConsumed = false; + ValueSetItem* pItem = ImplGetItem( ImplGetItem( rMouseEvent.GetPosPixel() ) ); + if (rMouseEvent.GetClicks() == 1) + { + if (pItem) + SelectItem(pItem->mnId); + GrabFocus(); + bConsumed = true; + } + else if (pItem && rMouseEvent.GetClicks() == 2) + { + maDoubleClickHdl.Call(this); + bConsumed = true; + } + return bConsumed; + } + + return CustomWidgetController::MouseButtonDown( rMouseEvent ); +} + +bool ValueSet::MouseButtonUp( const MouseEvent& rMouseEvent ) +{ + if (rMouseEvent.IsLeft() && !rMouseEvent.IsMod2()) + { + // tdf#142150 MouseUp seen without previous MouseDown + if (mnSelItemId) + Select(); + return true; + } + + return CustomWidgetController::MouseButtonUp( rMouseEvent ); +} + +bool ValueSet::MouseMove(const MouseEvent& rMouseEvent) +{ + // because of SelectionMode + if ((GetStyle() & WB_MENUSTYLEVALUESET) || (GetStyle() & WB_FLATVALUESET)) + ImplTracking(rMouseEvent.IsLeaveWindow(), rMouseEvent.GetPosPixel()); + return CustomWidgetController::MouseMove(rMouseEvent); +} + +void ValueSet::QueueReformat() +{ + queue_resize(); + RecalcScrollBar(); + mbFormat = true; + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(); +} + +void ValueSet::RemoveItem( sal_uInt16 nItemId ) +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos == VALUESET_ITEM_NOTFOUND ) + return; + + if ( nPos < mItemList.size() ) { + mItemList.erase( mItemList.begin() + nPos ); + } + + // reset variables + if (mnHighItemId == nItemId || mnSelItemId == nItemId) + { + mnCurCol = 0; + mnHighItemId = 0; + mnSelItemId = 0; + mbNoSelection = true; + } + + QueueReformat(); +} + +bool ValueSet::TurnOffScrollBar() +{ + if (mxScrolledWindow->get_vpolicy() == VclPolicyType::NEVER) + return false; + mxScrolledWindow->set_vpolicy(VclPolicyType::NEVER); + weld::DrawingArea* pDrawingArea = GetDrawingArea(); + Size aPrefSize(pDrawingArea->get_preferred_size()); + pDrawingArea->set_size_request(aPrefSize.Width() + GetScrollWidth(), aPrefSize.Height()); + return true; +} + +void ValueSet::TurnOnScrollBar() +{ + if (mxScrolledWindow->get_vpolicy() == VclPolicyType::ALWAYS) + return; + mxScrolledWindow->set_vpolicy(VclPolicyType::ALWAYS); + weld::DrawingArea* pDrawingArea = GetDrawingArea(); + Size aPrefSize(pDrawingArea->get_preferred_size()); + pDrawingArea->set_size_request(aPrefSize.Width() - GetScrollWidth(), aPrefSize.Height()); +} + +void ValueSet::RecalcScrollBar() +{ + if (!mxScrolledWindow) + return; + const bool bScrollAllowed = GetStyle() & WB_VSCROLL; + if (!bScrollAllowed) + return; + // reset scrolled window state to initial value so it will get configured + // to the right adjustment on the next format which we toggle on to happen + // if the scrolledwindow wasn't in its initial state already + if (TurnOffScrollBar()) + mbFormat = true; +} + +void ValueSet::Clear() +{ + ImplDeleteItems(); + + // reset variables + mnFirstLine = 0; + mnCurCol = 0; + mnHighItemId = 0; + mnSelItemId = 0; + mbNoSelection = true; + + RecalcScrollBar(); + + mbFormat = true; + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(); +} + +size_t ValueSet::GetItemCount() const +{ + return mItemList.size(); +} + +size_t ValueSet::GetItemPos( sal_uInt16 nItemId ) const +{ + for ( size_t i = 0, n = mItemList.size(); i < n; ++i ) { + if ( mItemList[i]->mnId == nItemId ) { + return i; + } + } + return VALUESET_ITEM_NOTFOUND; +} + +sal_uInt16 ValueSet::GetItemId( size_t nPos ) const +{ + return ( nPos < mItemList.size() ) ? mItemList[nPos]->mnId : 0 ; +} + +sal_uInt16 ValueSet::GetItemId( const Point& rPos ) const +{ + size_t nItemPos = ImplGetItem( rPos ); + if ( nItemPos != VALUESET_ITEM_NOTFOUND ) + return GetItemId( nItemPos ); + + return 0; +} + +tools::Rectangle ValueSet::GetItemRect( sal_uInt16 nItemId ) const +{ + const size_t nPos = GetItemPos( nItemId ); + + if ( nPos!=VALUESET_ITEM_NOTFOUND && mItemList[nPos]->mbVisible ) + return ImplGetItemRect( nPos ); + + return tools::Rectangle(); +} + +tools::Rectangle ValueSet::ImplGetItemRect( size_t nPos ) const +{ + const size_t nVisibleBegin = static_cast<size_t>(mnFirstLine)*mnCols; + const size_t nVisibleEnd = nVisibleBegin + static_cast<size_t>(mnVisLines)*mnCols; + + // Check if the item is inside the range of the displayed ones, + // taking into account that last row could be incomplete + if ( nPos<nVisibleBegin || nPos>=nVisibleEnd || nPos>=mItemList.size() ) + return tools::Rectangle(); + + nPos -= nVisibleBegin; + + const size_t row = mnCols ? nPos/mnCols : 0; + const size_t col = mnCols ? nPos%mnCols : 0; + const tools::Long x = maItemListRect.Left()+col*(mnItemWidth+mnSpacing); + const tools::Long y = maItemListRect.Top()+row*(mnItemHeight+mnSpacing); + + return tools::Rectangle( Point(x, y), Size(mnItemWidth, mnItemHeight) ); +} + +void ValueSet::ImplHighlightItem(sal_uInt16 nItemId) +{ + if ( mnHighItemId == nItemId ) + return; + + // remember the old item to delete the previous selection + mnHighItemId = nItemId; + + // remove the old selection and draw the new one + Invalidate(); +} + +void ValueSet::ImplDraw(vcl::RenderContext& rRenderContext) +{ + if (mbFormat) + Format(rRenderContext); + + Point aDefPos; + Size aSize = maVirDev->GetOutputSizePixel(); + + rRenderContext.DrawOutDev(aDefPos, aSize, aDefPos, aSize, *maVirDev); + + // draw parting line to the Namefield + if (GetStyle() & WB_NAMEFIELD) + { + if (!(GetStyle() & WB_FLATVALUESET)) + { + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + Size aWinSize(GetOutputSizePixel()); + Point aPos1(NAME_LINE_OFF_X, mnTextOffset + NAME_LINE_OFF_Y); + Point aPos2(aWinSize.Width() - (NAME_LINE_OFF_X * 2), mnTextOffset + NAME_LINE_OFF_Y); + if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono)) + { + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + rRenderContext.DrawLine(aPos1, aPos2); + aPos1.AdjustY( 1 ); + aPos2.AdjustY( 1 ); + rRenderContext.SetLineColor(rStyleSettings.GetLightColor()); + } + else + rRenderContext.SetLineColor(rStyleSettings.GetWindowTextColor()); + rRenderContext.DrawLine(aPos1, aPos2); + } + } + + ImplDrawSelect(rRenderContext); +} + +/** + * An inelegant method; sets the item width & height such that + * all of the included items and their labels fit; if we can + * calculate that. + */ +void ValueSet::RecalculateItemSizes() +{ + Size aLargestItem = GetLargestItemSize(); + + if ( mnUserItemWidth != aLargestItem.Width() || + mnUserItemHeight != aLargestItem.Height() ) + { + mnUserItemWidth = aLargestItem.Width(); + mnUserItemHeight = aLargestItem.Height(); + QueueReformat(); + } +} + +void ValueSet::SetFirstLine(sal_uInt16 nNewFirstLine) +{ + if (nNewFirstLine != mnFirstLine) + { + mnFirstLine = nNewFirstLine; + if (mxScrolledWindow) + mxScrolledWindow->vadjustment_set_value(mnFirstLine); + } +} + +void ValueSet::SelectItem( sal_uInt16 nItemId ) +{ + size_t nItemPos = 0; + + if ( nItemId ) + { + nItemPos = GetItemPos( nItemId ); + if ( nItemPos == VALUESET_ITEM_NOTFOUND ) + return; + } + + if ( !((mnSelItemId != nItemId) || mbNoSelection) ) + return; + + const sal_uInt16 nOldItem = mnSelItemId; + mnSelItemId = nItemId; + mbNoSelection = false; + + bool bNewOut = !mbFormat && IsReallyVisible() && IsUpdateMode(); + bool bNewLine = false; + + if (weld::DrawingArea* pNeedsFormatToScroll = !mnCols ? GetDrawingArea() : nullptr) + { + Format(pNeedsFormatToScroll->get_ref_device()); + // reset scrollbar so it's set to the later calculated mnFirstLine on + // the next Format + RecalcScrollBar(); + } + + // if necessary scroll to the visible area + if (mbScroll && nItemId && mnCols) + { + sal_uInt16 nNewLine = static_cast<sal_uInt16>(nItemPos / mnCols); + if ( nNewLine < mnFirstLine ) + { + SetFirstLine(nNewLine); + bNewLine = true; + } + else if ( nNewLine > o3tl::make_unsigned(mnFirstLine+mnVisLines-1) ) + { + SetFirstLine(static_cast<sal_uInt16>(nNewLine-mnVisLines+1)); + bNewLine = true; + } + } + + if ( bNewOut ) + { + if ( bNewLine ) + { + // redraw everything if the visible area has changed + mbFormat = true; + } + Invalidate(); + } + + if( !ImplHasAccessibleListeners() ) + return; + + // focus event (deselect) + if( nOldItem ) + { + const size_t nPos = GetItemPos( nItemId ); + + if( nPos != VALUESET_ITEM_NOTFOUND ) + { + ValueItemAcc* pItemAcc = ValueItemAcc::getImplementation( + mItemList[nPos]->GetAccessible( false/*bIsTransientChildrenDisabled*/ ) ); + + if( pItemAcc ) + { + Any aOldAny; + Any aNewAny; + aOldAny <<= Reference<XInterface>(static_cast<cppu::OWeakObject*>(pItemAcc)); + ImplFireAccessibleEvent(AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, aOldAny, aNewAny ); + } + } + } + + // focus event (select) + const size_t nPos = GetItemPos( mnSelItemId ); + + ValueSetItem* pItem; + if( nPos != VALUESET_ITEM_NOTFOUND ) + pItem = mItemList[nPos].get(); + else + pItem = mpNoneItem.get(); + + ValueItemAcc* pItemAcc = nullptr; + if (pItem != nullptr) + pItemAcc = ValueItemAcc::getImplementation( pItem->GetAccessible( false/*bIsTransientChildrenDisabled*/ ) ); + + if( pItemAcc ) + { + Any aOldAny; + Any aNewAny; + aNewAny <<= Reference<XInterface>(static_cast<cppu::OWeakObject*>(pItemAcc)); + ImplFireAccessibleEvent(AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, aOldAny, aNewAny); + } + + // selection event + Any aOldAny; + Any aNewAny; + ImplFireAccessibleEvent(AccessibleEventId::SELECTION_CHANGED, aOldAny, aNewAny); +} + +void ValueSet::SetNoSelection() +{ + mbNoSelection = true; + mbHighlight = false; + + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); +} + +void ValueSet::SetStyle(WinBits nStyle) +{ + if (nStyle != mnStyle) + { + mnStyle = nStyle; + mbFormat = true; + Invalidate(); + } +} + +void ValueSet::Format(vcl::RenderContext const & rRenderContext) +{ + Size aWinSize(GetOutputSizePixel()); + size_t nItemCount = mItemList.size(); + WinBits nStyle = GetStyle(); + tools::Long nTxtHeight = rRenderContext.GetTextHeight(); + tools::Long nOff; + tools::Long nNoneHeight; + tools::Long nNoneSpace; + + if (mxScrolledWindow && !(nStyle & WB_VSCROLL) && mxScrolledWindow->get_vpolicy() != VclPolicyType::NEVER) + TurnOffScrollBar(); + + // calculate item offset + if (nStyle & WB_ITEMBORDER) + { + if (nStyle & WB_DOUBLEBORDER) + nOff = ITEM_OFFSET_DOUBLE; + else + nOff = ITEM_OFFSET; + } + else + nOff = 0; + + // consider size, if NameField does exist + if (nStyle & WB_NAMEFIELD) + { + mnTextOffset = aWinSize.Height() - nTxtHeight - NAME_OFFSET; + aWinSize.AdjustHeight( -(nTxtHeight + NAME_OFFSET) ); + + if (!(nStyle & WB_FLATVALUESET)) + { + mnTextOffset -= NAME_LINE_HEIGHT + NAME_LINE_OFF_Y; + aWinSize.AdjustHeight( -(NAME_LINE_HEIGHT + NAME_LINE_OFF_Y) ); + } + } + else + mnTextOffset = 0; + + // consider offset and size, if NoneField does exist + if (nStyle & WB_NONEFIELD) + { + nNoneHeight = nTxtHeight + nOff; + nNoneSpace = mnSpacing; + } + else + { + nNoneHeight = 0; + nNoneSpace = 0; + mpNoneItem.reset(); + } + + // calculate number of columns + if (!mnUserCols) + { + if (mnUserItemWidth) + { + mnCols = static_cast<sal_uInt16>((aWinSize.Width() - mnSpacing) / (mnUserItemWidth + mnSpacing)); + if (mnCols <= 0) + mnCols = 1; + } + else + { + mnCols = 1; + } + } + else + { + mnCols = mnUserCols; + } + + // calculate number of rows + mbScroll = false; + + auto nOldLines = mnLines; + // Floor( (M+N-1)/N )==Ceiling( M/N ) + mnLines = (static_cast<tools::Long>(nItemCount) + mnCols - 1) / mnCols; + if (mnLines <= 0) + mnLines = 1; + + bool bAdjustmentOutOfDate = nOldLines != mnLines; + + auto nOldVisLines = mnVisLines; + + tools::Long nCalcHeight = aWinSize.Height() - nNoneHeight; + if (mnUserVisLines) + { + mnVisLines = mnUserVisLines; + } + else if (mnUserItemHeight) + { + mnVisLines = (nCalcHeight - nNoneSpace + mnSpacing) / (mnUserItemHeight + mnSpacing); + if (!mnVisLines) + mnVisLines = 1; + } + else + { + mnVisLines = mnLines; + } + + bAdjustmentOutOfDate |= nOldVisLines != mnVisLines; + + if (mnLines > mnVisLines) + mbScroll = true; + + if (mnLines <= mnVisLines) + { + SetFirstLine(0); + } + else + { + if (mnFirstLine > o3tl::make_unsigned(mnLines - mnVisLines)) + SetFirstLine(static_cast<sal_uInt16>(mnLines - mnVisLines)); + } + + // calculate item size + const tools::Long nColSpace = (mnCols - 1) * static_cast<tools::Long>(mnSpacing); + const tools::Long nLineSpace = ((mnVisLines - 1) * mnSpacing) + nNoneSpace; + if (mnUserItemWidth && !mnUserCols) + { + mnItemWidth = mnUserItemWidth; + if (mnItemWidth > aWinSize.Width() - nColSpace) + mnItemWidth = aWinSize.Width() - nColSpace; + } + else + mnItemWidth = (aWinSize.Width() - nColSpace) / mnCols; + if (mnUserItemHeight && !mnUserVisLines) + { + mnItemHeight = mnUserItemHeight; + if (mnItemHeight > nCalcHeight - nNoneSpace) + mnItemHeight = nCalcHeight - nNoneSpace; + } + else + { + nCalcHeight -= nLineSpace; + mnItemHeight = nCalcHeight / mnVisLines; + } + + // Init VirDev + maVirDev->SetSettings(rRenderContext.GetSettings()); + maVirDev->SetOutputSizePixel(aWinSize); + + // nothing is changed in case of too small items + if ((mnItemWidth <= 0) || + (mnItemHeight <= ((nStyle & WB_ITEMBORDER) ? 4 : 2)) || + !nItemCount) + { + mbHasVisibleItems = false; + + if ((nStyle & WB_NONEFIELD) && mpNoneItem) + { + mpNoneItem->mbVisible = false; + mpNoneItem->maText = GetText(); + } + + for (size_t i = 0; i < nItemCount; i++) + { + mItemList[i]->mbVisible = false; + } + + if (mxScrolledWindow && mxScrolledWindow->get_vpolicy() != VclPolicyType::NEVER) + TurnOffScrollBar(); + } + else + { + mbHasVisibleItems = true; + + // determine Frame-Style + if (nStyle & WB_DOUBLEBORDER) + mnFrameStyle = DrawFrameStyle::DoubleIn; + else + mnFrameStyle = DrawFrameStyle::In; + + // draw the selection with double width if the items are bigger + if ((nStyle & WB_DOUBLEBORDER) && + ((mnItemWidth >= 25) && (mnItemHeight >= 20))) + { + mbDoubleSel = true; + } + else + { + mbDoubleSel = false; + } + + // calculate offsets + tools::Long nStartX; + tools::Long nStartY; + if (mbFullMode) + { + tools::Long nAllItemWidth = (mnItemWidth * mnCols) + nColSpace; + tools::Long nAllItemHeight = (mnItemHeight * mnVisLines) + nNoneHeight + nLineSpace; + nStartX = (aWinSize.Width() - nAllItemWidth) / 2; + nStartY = (aWinSize.Height() - nAllItemHeight) / 2; + } + else + { + nStartX = 0; + nStartY = 0; + } + + // calculate and draw items + maVirDev->SetLineColor(); + tools::Long x = nStartX; + tools::Long y = nStartY; + + // create NoSelection field and show it + if (nStyle & WB_NONEFIELD) + { + if (!mpNoneItem) + mpNoneItem.reset(new ValueSetItem(*this)); + + mpNoneItem->mnId = 0; + mpNoneItem->meType = VALUESETITEM_NONE; + mpNoneItem->mbVisible = true; + maNoneItemRect.SetLeft( x ); + maNoneItemRect.SetTop( y ); + maNoneItemRect.SetRight( maNoneItemRect.Left() + aWinSize.Width() - x - 1 ); + maNoneItemRect.SetBottom( y + nNoneHeight - 1 ); + + ImplFormatItem(rRenderContext, mpNoneItem.get(), maNoneItemRect); + + y += nNoneHeight + nNoneSpace; + } + + // draw items + sal_uLong nFirstItem = static_cast<sal_uLong>(mnFirstLine) * mnCols; + sal_uLong nLastItem = nFirstItem + (mnVisLines * mnCols); + + maItemListRect.SetLeft( x ); + maItemListRect.SetTop( y ); + maItemListRect.SetRight( x + mnCols * (mnItemWidth + mnSpacing) - mnSpacing - 1 ); + maItemListRect.SetBottom( y + mnVisLines * (mnItemHeight + mnSpacing) - mnSpacing - 1 ); + + if (!mbFullMode) + { + // If want also draw parts of items in the last line, + // then we add one more line if parts of these line are + // visible + if (y + (mnVisLines * (mnItemHeight + mnSpacing)) < aWinSize.Height()) + nLastItem += mnCols; + maItemListRect.SetBottom( aWinSize.Height() - y ); + } + for (size_t i = 0; i < nItemCount; i++) + { + ValueSetItem* pItem = mItemList[i].get(); + + if (i >= nFirstItem && i < nLastItem) + { + if (!pItem->mbVisible && ImplHasAccessibleListeners()) + { + Any aOldAny; + Any aNewAny; + + aNewAny <<= pItem->GetAccessible(false/*bIsTransientChildrenDisabled*/); + ImplFireAccessibleEvent(AccessibleEventId::CHILD, aOldAny, aNewAny); + } + + pItem->mbVisible = true; + ImplFormatItem(rRenderContext, pItem, tools::Rectangle(Point(x, y), Size(mnItemWidth, mnItemHeight))); + + if (!((i + 1) % mnCols)) + { + x = nStartX; + y += mnItemHeight + mnSpacing; + } + else + x += mnItemWidth + mnSpacing; + } + else + { + if (pItem->mbVisible && ImplHasAccessibleListeners()) + { + Any aOldAny; + Any aNewAny; + + aOldAny <<= pItem->GetAccessible(false/*bIsTransientChildrenDisabled*/); + ImplFireAccessibleEvent(AccessibleEventId::CHILD, aOldAny, aNewAny); + } + + pItem->mbVisible = false; + } + } + + // arrange ScrollBar, set values and show it + if (mxScrolledWindow && (nStyle & WB_VSCROLL)) + { + bool bTurnScrollbarOn = mxScrolledWindow->get_vpolicy() != VclPolicyType::ALWAYS; + if (bAdjustmentOutOfDate || bTurnScrollbarOn) + { + tools::Long nPageSize = mnVisLines; + if (nPageSize < 1) + nPageSize = 1; + mxScrolledWindow->vadjustment_configure(mnFirstLine, 0, mnLines, 1, + mnVisLines, nPageSize); + } + + if (bTurnScrollbarOn) + TurnOnScrollBar(); + } + } + + // waiting for the next since the formatting is finished + mbFormat = false; +} + +void ValueSet::ImplDrawSelect(vcl::RenderContext& rRenderContext) +{ + if (!IsReallyVisible()) + return; + + const bool bFocus = HasFocus(); + + if (!bFocus && mbNoSelection && !mbHighlight) + return; + + tools::Rectangle aSelectedRect, aHoverRect; + ValueSetItem* pSelectedItem = ImplGetDrawSelectItem(mnSelItemId, bFocus, aSelectedRect); + ValueSetItem* pHighlightItem = mnHighItemId ? ImplGetDrawSelectItem(mnHighItemId, false, aHoverRect) : nullptr; + + if (pSelectedItem) + { + const bool bHover = pSelectedItem == pHighlightItem; + ImplDrawSelect(rRenderContext, aSelectedRect, pSelectedItem, bFocus, !mbNoSelection, true, bHover); + } + if (pHighlightItem && (pSelectedItem != pHighlightItem || mbNoSelection)) + { + // For the case that there isn't a selected item, but due to wanting to + // show focus is in the valueset, the above block will have drawn the + // first item with a focus rect. For that situation; if the valueset is + // the thin WB_MENUSTYLEVALUESET case then blend this highlight border + // on top of that focus rect and it will appear with a highlighted + // focus rect. If it's the other case of a thicker border then redraw + // the focus rect highlighted with the hover color. + bool bDrawFocus; + WinBits nStyle = GetStyle(); + if (nStyle & WB_MENUSTYLEVALUESET) + bDrawFocus = false; + else + bDrawFocus = pSelectedItem == pHighlightItem && mbNoSelection; + + ImplDrawSelect(rRenderContext, aHoverRect, pHighlightItem, bDrawFocus, mbHighlight, false, true); + } +} + +ValueSetItem* ValueSet::ImplGetDrawSelectItem(sal_uInt16 nItemId, const bool bFocus, tools::Rectangle& rRect) +{ + ValueSetItem* pItem = nullptr; + if (nItemId) + { + const size_t nPos = GetItemPos( nItemId ); + pItem = mItemList[ nPos ].get(); + rRect = ImplGetItemRect( nPos ); + } + else if (mpNoneItem) + { + pItem = mpNoneItem.get(); + rRect = maNoneItemRect; + } + else if (bFocus && (pItem = ImplGetFirstItem())) + { + rRect = ImplGetItemRect(0); + } + return pItem; +} + +void ValueSet::ImplDrawSelect(vcl::RenderContext& rRenderContext, + const tools::Rectangle& rRect, const ValueSetItem* pItem, + const bool bFocus, const bool bDrawSel, + const bool bSelected, const bool bHover) +{ + tools::Rectangle aRect(rRect); + + // draw selection + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + rRenderContext.SetFillColor(); + + Color aDoubleColor; + Color aSingleColor; + + sal_uInt16 nTransparencePercent = 0; + + if (bSelected && bHover) + { + aDoubleColor = rStyleSettings.GetActiveColor(); + aSingleColor = rStyleSettings.GetActiveTextColor(); + } + else if (bSelected || bHover) + { + aDoubleColor = rStyleSettings.GetHighlightColor(); + aSingleColor = rStyleSettings.GetHighlightTextColor(); + if (bHover) + { + nTransparencePercent = 55; + } + } + + // specify selection output + WinBits nStyle = GetStyle(); + if (nStyle & WB_MENUSTYLEVALUESET) + { + if (bFocus) + InvertFocusRect(rRenderContext, aRect); + if (bDrawSel) + { + rRenderContext.SetLineColor(aDoubleColor); + tools::PolyPolygon aPolyPoly(1); + aPolyPoly.Insert(aRect); + rRenderContext.DrawTransparent(aPolyPoly, nTransparencePercent); + } + } + else + { + rRenderContext.SetLineColor(aDoubleColor); + tools::Rectangle aFocusRect; + + if (!mbDoubleSel) + { + // an outer rectangle surrounding a "focus" rectangle, surrounding + // an inner rectangle. Focus rectangle is always drawn, but rendered + // empty when there is no focus. e.g. as seen in color valuesets + if (bDrawSel) + { + tools::PolyPolygon aPolyPoly(1); + aPolyPoly.Insert(aRect); + rRenderContext.DrawTransparent(aPolyPoly, nTransparencePercent); + } + + aRect.AdjustLeft( 1 ); + aRect.AdjustTop( 1 ); + aRect.AdjustRight( -1 ); + aRect.AdjustBottom( -1 ); + + aFocusRect = aRect; + + aRect.AdjustLeft( 1 ); + aRect.AdjustTop( 1 ); + aRect.AdjustRight( -1 ); + aRect.AdjustBottom( -1 ); + + if (bDrawSel) + { + tools::PolyPolygon aPolyPoly(1); + aPolyPoly.Insert(aRect); + rRenderContext.DrawTransparent(aPolyPoly, nTransparencePercent); + } + + if (bDrawSel) + rRenderContext.SetLineColor(aSingleColor); + else + rRenderContext.SetLineColor(COL_LIGHTGRAY); + + rRenderContext.DrawRect(aFocusRect); + } + else + { + // a thick bordered rectangle surrounding an optional "focus" + // rectangle which is only drawn when focused, as seen in format, + // bullets and numbering in writer + const int nAdjust = 2; + + aRect.AdjustLeft(nAdjust); + aRect.AdjustTop(nAdjust); + aRect.AdjustRight(-nAdjust); + aRect.AdjustBottom(-nAdjust); + + aFocusRect = aRect; + + if (bDrawSel) + { + const basegfx::B2DPolygon aRectPoly( + basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(aRect))); + + const int nThickness = nAdjust * 2; + + if (!rRenderContext.DrawPolyLineDirect(basegfx::B2DHomMatrix(), + aRectPoly, + nThickness, + nTransparencePercent / 100.0, + nullptr, + basegfx::B2DLineJoin::Miter)) + { + SAL_WARN("svtools", "presumably impossible in practice, but fallback to see something"); + rRenderContext.DrawPolyLine(aRectPoly, nThickness, basegfx::B2DLineJoin::Miter); + } + } + + if (bFocus) + { + if (bDrawSel) + rRenderContext.SetLineColor(aSingleColor); + else + rRenderContext.SetLineColor(COL_LIGHTGRAY); + rRenderContext.DrawRect(aFocusRect); + } + } + + if (bFocus) + InvertFocusRect(rRenderContext, aFocusRect); + } + + ImplDrawItemText(rRenderContext, pItem->maText); +} + +void ValueSet::ImplFormatItem(vcl::RenderContext const & rRenderContext, ValueSetItem* pItem, tools::Rectangle aRect) +{ + WinBits nStyle = GetStyle(); + if (nStyle & WB_ITEMBORDER) + { + aRect.AdjustLeft(1 ); + aRect.AdjustTop(1 ); + aRect.AdjustRight( -1 ); + aRect.AdjustBottom( -1 ); + + if (nStyle & WB_FLATVALUESET) + { + sal_Int32 nBorder = (nStyle & WB_DOUBLEBORDER) ? 2 : 1; + + aRect.AdjustLeft(nBorder ); + aRect.AdjustTop(nBorder ); + aRect.AdjustRight( -nBorder ); + aRect.AdjustBottom( -nBorder ); + } + else + { + DecorationView aView(maVirDev.get()); + aRect = aView.DrawFrame(aRect, mnFrameStyle); + } + } + + if (pItem == mpNoneItem.get()) + pItem->maText = GetText(); + + if ((aRect.GetHeight() <= 0) || (aRect.GetWidth() <= 0)) + return; + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + if (pItem == mpNoneItem.get()) + { + maVirDev->SetFont(rRenderContext.GetFont()); + maVirDev->SetTextColor((nStyle & WB_MENUSTYLEVALUESET) ? rStyleSettings.GetMenuTextColor() : rStyleSettings.GetWindowTextColor()); + maVirDev->SetTextFillColor(); + maVirDev->SetFillColor((nStyle & WB_MENUSTYLEVALUESET) ? rStyleSettings.GetMenuColor() : rStyleSettings.GetWindowColor()); + maVirDev->DrawRect(aRect); + Point aTxtPos(aRect.Left() + 2, aRect.Top()); + tools::Long nTxtWidth = rRenderContext.GetTextWidth(pItem->maText); + if ((aTxtPos.X() + nTxtWidth) > aRect.Right()) + { + maVirDev->SetClipRegion(vcl::Region(aRect)); + maVirDev->DrawText(aTxtPos, pItem->maText); + maVirDev->SetClipRegion(); + } + else + maVirDev->DrawText(aTxtPos, pItem->maText); + } + else if (pItem->meType == VALUESETITEM_COLOR) + { + maVirDev->SetFillColor(pItem->maColor); + maVirDev->DrawRect(aRect); + } + else + { + if (IsColor()) + maVirDev->SetFillColor(maColor); + else if (nStyle & WB_MENUSTYLEVALUESET) + maVirDev->SetFillColor(rStyleSettings.GetMenuColor()); + else if (IsEnabled()) + maVirDev->SetFillColor(rStyleSettings.GetWindowColor()); + else + maVirDev->SetFillColor(rStyleSettings.GetFaceColor()); + maVirDev->DrawRect(aRect); + + if (pItem->meType == VALUESETITEM_USERDRAW) + { + UserDrawEvent aUDEvt(maVirDev.get(), aRect, pItem->mnId); + UserDraw(aUDEvt); + } + else + { + Size aImageSize = pItem->maImage.GetSizePixel(); + Size aRectSize = aRect.GetSize(); + Point aPos(aRect.Left(), aRect.Top()); + aPos.AdjustX((aRectSize.Width() - aImageSize.Width()) / 2 ); + + if (pItem->meType != VALUESETITEM_IMAGE_AND_TEXT) + aPos.AdjustY((aRectSize.Height() - aImageSize.Height()) / 2 ); + + DrawImageFlags nImageStyle = DrawImageFlags::NONE; + if (!IsEnabled()) + nImageStyle |= DrawImageFlags::Disable; + + if (aImageSize.Width() > aRectSize.Width() || + aImageSize.Height() > aRectSize.Height()) + { + maVirDev->SetClipRegion(vcl::Region(aRect)); + maVirDev->DrawImage(aPos, pItem->maImage, nImageStyle); + maVirDev->SetClipRegion(); + } + else + maVirDev->DrawImage(aPos, pItem->maImage, nImageStyle); + + if (pItem->meType == VALUESETITEM_IMAGE_AND_TEXT) + { + maVirDev->SetFont(rRenderContext.GetFont()); + maVirDev->SetTextColor((nStyle & WB_MENUSTYLEVALUESET) ? rStyleSettings.GetMenuTextColor() : rStyleSettings.GetWindowTextColor()); + maVirDev->SetTextFillColor(); + + tools::Long nTxtWidth = maVirDev->GetTextWidth(pItem->maText); + + if (nTxtWidth > aRect.GetWidth()) + maVirDev->SetClipRegion(vcl::Region(aRect)); + + maVirDev->DrawText(Point(aRect.Left() + + (aRect.GetWidth() - nTxtWidth) / 2, + aRect.Bottom() - maVirDev->GetTextHeight()), + pItem->maText); + + if (nTxtWidth > aRect.GetWidth()) + maVirDev->SetClipRegion(); + } + } + } + + const sal_uInt16 nEdgeBlendingPercent(GetEdgeBlending() ? rStyleSettings.GetEdgeBlending() : 0); + + if (nEdgeBlendingPercent) + { + const Color& rTopLeft(rStyleSettings.GetEdgeBlendingTopLeftColor()); + const Color& rBottomRight(rStyleSettings.GetEdgeBlendingBottomRightColor()); + const sal_uInt8 nAlpha((nEdgeBlendingPercent * 255) / 100); + const BitmapEx aBlendFrame(createBlendFrame(aRect.GetSize(), nAlpha, rTopLeft, rBottomRight)); + + if (!aBlendFrame.IsEmpty()) + { + maVirDev->DrawBitmapEx(aRect.TopLeft(), aBlendFrame); + } + } +} + +void ValueSet::ImplDrawItemText(vcl::RenderContext& rRenderContext, const OUString& rText) +{ + if (!(GetStyle() & WB_NAMEFIELD)) + return; + + Size aWinSize(GetOutputSizePixel()); + tools::Long nTxtWidth = rRenderContext.GetTextWidth(rText); + tools::Long nTxtOffset = mnTextOffset; + + // delete rectangle and show text + if (GetStyle() & WB_FLATVALUESET) + { + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(rStyleSettings.GetFaceColor()); + rRenderContext.DrawRect(tools::Rectangle(Point(0, nTxtOffset), Point(aWinSize.Width(), aWinSize.Height()))); + rRenderContext.SetTextColor(rStyleSettings.GetButtonTextColor()); + } + else + { + nTxtOffset += NAME_LINE_HEIGHT+NAME_LINE_OFF_Y; + rRenderContext.SetBackground(Application::GetSettings().GetStyleSettings().GetFaceColor()); + rRenderContext.Erase(tools::Rectangle(Point(0, nTxtOffset), Point(aWinSize.Width(), aWinSize.Height()))); + } + rRenderContext.DrawText(Point((aWinSize.Width() - nTxtWidth) / 2, nTxtOffset + (NAME_OFFSET / 2)), rText); +} + +void ValueSet::StyleUpdated() +{ + mbFormat = true; + CustomWidgetController::StyleUpdated(); +} + +void ValueSet::EnableFullItemMode( bool bFullMode ) +{ + mbFullMode = bFullMode; +} + +void ValueSet::SetColCount( sal_uInt16 nNewCols ) +{ + if ( mnUserCols != nNewCols ) + { + mnUserCols = nNewCols; + QueueReformat(); + } +} + +void ValueSet::SetItemImage( sal_uInt16 nItemId, const Image& rImage ) +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos == VALUESET_ITEM_NOTFOUND ) + return; + + ValueSetItem* pItem = mItemList[nPos].get(); + pItem->meType = VALUESETITEM_IMAGE; + pItem->maImage = rImage; + + if ( !mbFormat && IsReallyVisible() && IsUpdateMode() ) + { + const tools::Rectangle aRect = ImplGetItemRect(nPos); + Invalidate(aRect); + } + else + mbFormat = true; +} + +void ValueSet::SetItemColor( sal_uInt16 nItemId, const Color& rColor ) +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos == VALUESET_ITEM_NOTFOUND ) + return; + + ValueSetItem* pItem = mItemList[nPos].get(); + pItem->meType = VALUESETITEM_COLOR; + pItem->maColor = rColor; + + if ( !mbFormat && IsReallyVisible() && IsUpdateMode() ) + { + const tools::Rectangle aRect = ImplGetItemRect(nPos); + Invalidate( aRect ); + } + else + mbFormat = true; +} + +Color ValueSet::GetItemColor( sal_uInt16 nItemId ) const +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos != VALUESET_ITEM_NOTFOUND ) + return mItemList[nPos]->maColor; + else + return Color(); +} + +Size ValueSet::CalcWindowSizePixel( const Size& rItemSize, sal_uInt16 nDesireCols, + sal_uInt16 nDesireLines ) const +{ + size_t nCalcCols = nDesireCols; + size_t nCalcLines = nDesireLines; + + if ( !nCalcCols ) + { + if ( mnUserCols ) + nCalcCols = mnUserCols; + else + nCalcCols = 1; + } + + if ( !nCalcLines ) + { + nCalcLines = mnVisLines; + + if ( mbFormat ) + { + if ( mnUserVisLines ) + nCalcLines = mnUserVisLines; + else + { + // Floor( (M+N-1)/N )==Ceiling( M/N ) + nCalcLines = (mItemList.size()+nCalcCols-1) / nCalcCols; + if ( !nCalcLines ) + nCalcLines = 1; + } + } + } + + Size aSize( rItemSize.Width() * nCalcCols, rItemSize.Height() * nCalcLines ); + WinBits nStyle = GetStyle(); + tools::Long nTxtHeight = GetTextHeight(); + tools::Long n; + + if ( nStyle & WB_ITEMBORDER ) + { + if ( nStyle & WB_DOUBLEBORDER ) + n = ITEM_OFFSET_DOUBLE; + else + n = ITEM_OFFSET; + + aSize.AdjustWidth(n * nCalcCols ); + aSize.AdjustHeight(n * nCalcLines ); + } + else + n = 0; + + if ( mnSpacing ) + { + aSize.AdjustWidth(mnSpacing * (nCalcCols - 1) ); + aSize.AdjustHeight(mnSpacing * (nCalcLines - 1) ); + } + + if ( nStyle & WB_NAMEFIELD ) + { + aSize.AdjustHeight(nTxtHeight + NAME_OFFSET ); + if ( !(nStyle & WB_FLATVALUESET) ) + aSize.AdjustHeight(NAME_LINE_HEIGHT + NAME_LINE_OFF_Y ); + } + + if ( nStyle & WB_NONEFIELD ) + { + aSize.AdjustHeight(nTxtHeight + n + mnSpacing ); + } + + return aSize; +} + +void ValueSet::InsertItem( sal_uInt16 nItemId, const Image& rImage ) +{ + std::unique_ptr<ValueSetItem> pItem(new ValueSetItem( *this )); + pItem->mnId = nItemId; + pItem->meType = VALUESETITEM_IMAGE; + pItem->maImage = rImage; + ImplInsertItem( std::move(pItem), VALUESET_APPEND ); +} + +void ValueSet::InsertItem( sal_uInt16 nItemId, const Image& rImage, + const OUString& rText, size_t nPos, + bool bShowLegend ) +{ + std::unique_ptr<ValueSetItem> pItem(new ValueSetItem( *this )); + pItem->mnId = nItemId; + pItem->meType = bShowLegend ? VALUESETITEM_IMAGE_AND_TEXT : VALUESETITEM_IMAGE; + pItem->maImage = rImage; + pItem->maText = rText; + ImplInsertItem( std::move(pItem), nPos ); +} + +void ValueSet::InsertItem( sal_uInt16 nItemId, size_t nPos ) +{ + std::unique_ptr<ValueSetItem> pItem(new ValueSetItem( *this )); + pItem->mnId = nItemId; + pItem->meType = VALUESETITEM_USERDRAW; + ImplInsertItem( std::move(pItem), nPos ); +} + +void ValueSet::InsertItem( sal_uInt16 nItemId, const Color& rColor, + const OUString& rText ) +{ + std::unique_ptr<ValueSetItem> pItem(new ValueSetItem( *this )); + pItem->mnId = nItemId; + pItem->meType = VALUESETITEM_COLOR; + pItem->maColor = rColor; + pItem->maText = rText; + ImplInsertItem( std::move(pItem), VALUESET_APPEND ); +} + +void ValueSet::ImplInsertItem( std::unique_ptr<ValueSetItem> pItem, const size_t nPos ) +{ + DBG_ASSERT( pItem->mnId, "ValueSet::InsertItem(): ItemId == 0" ); + DBG_ASSERT( GetItemPos( pItem->mnId ) == VALUESET_ITEM_NOTFOUND, + "ValueSet::InsertItem(): ItemId already exists" ); + + if ( nPos < mItemList.size() ) { + mItemList.insert( mItemList.begin() + nPos, std::move(pItem) ); + } else { + mItemList.push_back( std::move(pItem) ); + } + + QueueReformat(); +} + +int ValueSet::GetScrollWidth() const +{ + if (mxScrolledWindow) + return mxScrolledWindow->get_scroll_thickness(); + return 0; +} + +void ValueSet::SetEdgeBlending(bool bNew) +{ + if(mbEdgeBlending != bNew) + { + mbEdgeBlending = bNew; + mbFormat = true; + + if (GetDrawingArea() && IsReallyVisible() && IsUpdateMode()) + { + Invalidate(); + } + } +} + +Size ValueSet::CalcItemSizePixel( const Size& rItemSize) const +{ + Size aSize = rItemSize; + + WinBits nStyle = GetStyle(); + if ( nStyle & WB_ITEMBORDER ) + { + tools::Long n; + + if ( nStyle & WB_DOUBLEBORDER ) + n = ITEM_OFFSET_DOUBLE; + else + n = ITEM_OFFSET; + + aSize.AdjustWidth(n ); + aSize.AdjustHeight(n ); + } + + return aSize; +} + +void ValueSet::SetLineCount( sal_uInt16 nNewLines ) +{ + if ( mnUserVisLines != nNewLines ) + { + mnUserVisLines = nNewLines; + QueueReformat(); + } +} + +void ValueSet::SetItemWidth( tools::Long nNewItemWidth ) +{ + if ( mnUserItemWidth != nNewItemWidth ) + { + mnUserItemWidth = nNewItemWidth; + QueueReformat(); + } +} + +//method to set accessible when the style is user draw. +void ValueSet::InsertItem( sal_uInt16 nItemId, const OUString& rText, size_t nPos ) +{ + DBG_ASSERT( nItemId, "ValueSet::InsertItem(): ItemId == 0" ); + DBG_ASSERT( GetItemPos( nItemId ) == VALUESET_ITEM_NOTFOUND, + "ValueSet::InsertItem(): ItemId already exists" ); + std::unique_ptr<ValueSetItem> pItem(new ValueSetItem( *this )); + pItem->mnId = nItemId; + pItem->meType = VALUESETITEM_USERDRAW; + pItem->maText = rText; + ImplInsertItem( std::move(pItem), nPos ); +} + +void ValueSet::SetItemHeight( tools::Long nNewItemHeight ) +{ + if ( mnUserItemHeight != nNewItemHeight ) + { + mnUserItemHeight = nNewItemHeight; + QueueReformat(); + } +} + +OUString ValueSet::RequestHelp(tools::Rectangle& rHelpRect) +{ + Point aPos = rHelpRect.TopLeft(); + const size_t nItemPos = ImplGetItem( aPos ); + OUString sRet; + if (nItemPos != VALUESET_ITEM_NOTFOUND) + { + rHelpRect = ImplGetItemRect(nItemPos); + sRet = GetItemText(ImplGetItem(nItemPos)->mnId); + } + return sRet; +} + +OUString ValueSet::GetItemText(sal_uInt16 nItemId) const +{ + const size_t nPos = GetItemPos(nItemId); + + if ( nPos != VALUESET_ITEM_NOTFOUND ) + return mItemList[nPos]->maText; + + return OUString(); +} + +void ValueSet::SetExtraSpacing( sal_uInt16 nNewSpacing ) +{ + if ( GetStyle() & WB_ITEMBORDER ) + { + mnSpacing = nNewSpacing; + QueueReformat(); + } +} + +void ValueSet::SetFormat() +{ + mbFormat = true; +} + +void ValueSet::SetItemData( sal_uInt16 nItemId, void* pData ) +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos == VALUESET_ITEM_NOTFOUND ) + return; + + ValueSetItem* pItem = mItemList[nPos].get(); + pItem->mpData = pData; + + if ( pItem->meType == VALUESETITEM_USERDRAW ) + { + if ( !mbFormat && IsReallyVisible() && IsUpdateMode() ) + { + const tools::Rectangle aRect = ImplGetItemRect(nPos); + Invalidate(aRect); + } + else + mbFormat = true; + } +} + +void* ValueSet::GetItemData( sal_uInt16 nItemId ) const +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos != VALUESET_ITEM_NOTFOUND ) + return mItemList[nPos]->mpData; + else + return nullptr; +} + +void ValueSet::SetItemText(sal_uInt16 nItemId, const OUString& rText) +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos == VALUESET_ITEM_NOTFOUND ) + return; + + ValueSetItem* pItem = mItemList[nPos].get(); + + // Remember old and new name for accessibility event. + Any aOldName; + Any aNewName; + OUString sString (pItem->maText); + aOldName <<= sString; + sString = rText; + aNewName <<= sString; + + pItem->maText = rText; + + if (!mbFormat && IsReallyVisible() && IsUpdateMode()) + { + sal_uInt16 nTempId = mnSelItemId; + + if (mbHighlight) + nTempId = mnHighItemId; + + if (nTempId == nItemId) + Invalidate(); + } + + if (ImplHasAccessibleListeners()) + { + Reference<XAccessible> xAccessible(pItem->GetAccessible( false/*bIsTransientChildrenDisabled*/)); + ValueItemAcc* pValueItemAcc = static_cast<ValueItemAcc*>(xAccessible.get()); + pValueItemAcc->FireAccessibleEvent(AccessibleEventId::NAME_CHANGED, aOldName, aNewName); + } +} + +Size ValueSet::GetLargestItemSize() +{ + Size aLargestItem; + + for (const std::unique_ptr<ValueSetItem>& pItem : mItemList) + { + if (!pItem->mbVisible) + continue; + + if (pItem->meType != VALUESETITEM_IMAGE && + pItem->meType != VALUESETITEM_IMAGE_AND_TEXT) + { + // handle determining an optimal size for this case + continue; + } + + Size aSize = pItem->maImage.GetSizePixel(); + if (pItem->meType == VALUESETITEM_IMAGE_AND_TEXT) + { + aSize.AdjustHeight(3 * NAME_LINE_HEIGHT + + maVirDev->GetTextHeight() ); + aSize.setWidth( std::max(aSize.Width(), + maVirDev->GetTextWidth(pItem->maText) + NAME_OFFSET) ); + } + + aLargestItem.setWidth( std::max(aLargestItem.Width(), aSize.Width()) ); + aLargestItem.setHeight( std::max(aLargestItem.Height(), aSize.Height()) ); + } + + return aLargestItem; +} + +void ValueSet::SetOptimalSize() +{ + Size aLargestSize(GetLargestItemSize()); + aLargestSize.setWidth(std::max(aLargestSize.Width(), mnUserItemWidth)); + aLargestSize.setHeight(std::max(aLargestSize.Height(), mnUserItemHeight)); + Size aPrefSize(CalcWindowSizePixel(aLargestSize)); + GetDrawingArea()->set_size_request(aPrefSize.Width(), aPrefSize.Height()); +} + +Image ValueSet::GetItemImage(sal_uInt16 nItemId) const +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos != VALUESET_ITEM_NOTFOUND ) + return mItemList[nPos]->maImage; + else + return Image(); +} + +void ValueSet::SetColor(const Color& rColor) +{ + maColor = rColor; + mbFormat = true; + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); +} + +void ValueSet::Show() +{ + if (mxScrolledWindow) + mxScrolledWindow->show(); + CustomWidgetController::Show(); +} + +void ValueSet::Hide() +{ + CustomWidgetController::Hide(); + if (mxScrolledWindow) + mxScrolledWindow->hide(); +} + +FactoryFunction ValueSet::GetUITestFactory() const +{ + return ValueSetUIObject::create; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/dialogs/PlaceEditDialog.cxx b/svtools/source/dialogs/PlaceEditDialog.cxx new file mode 100644 index 000000000..b61abd58e --- /dev/null +++ b/svtools/source/dialogs/PlaceEditDialog.cxx @@ -0,0 +1,374 @@ +/* -*- 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 <config_oauth2.h> + +#include <svtools/PlaceEditDialog.hxx> + +#include <com/sun/star/uno/Sequence.hxx> +#include <officecfg/Office/Common.hxx> +#include <svtools/svtresid.hxx> +#include <svtools/strings.hrc> +#include <svtools/place.hxx> + +#include "ServerDetailsControls.hxx" + +using namespace com::sun::star::uno; + +PlaceEditDialog::PlaceEditDialog(weld::Window* pParent) + : GenericDialogController(pParent, "svt/ui/placeedit.ui", "PlaceEditDialog") + , m_xCurrentDetails() + , m_nCurrentType( 0 ) + , m_bLabelChanged( false ) + , m_bShowPassword( true ) + , m_xEDServerName(m_xBuilder->weld_entry("name")) + , m_xLBServerType(m_xBuilder->weld_combo_box("type")) + , m_xEDUsername(m_xBuilder->weld_entry("login")) + , m_xFTUsernameLabel(m_xBuilder->weld_label("loginLabel")) + , m_xBTOk(m_xBuilder->weld_button("ok")) + , m_xBTCancel(m_xBuilder->weld_button("cancel")) + , m_xBTDelete(m_xBuilder->weld_button("delete")) + , m_xBTRepoRefresh(m_xBuilder->weld_button("repositoriesRefresh")) + , m_xCBPassword(m_xBuilder->weld_check_button("rememberPassword")) + , m_xEDPassword(m_xBuilder->weld_entry("password")) + , m_xFTPasswordLabel(m_xBuilder->weld_label("passwordLabel")) + , m_xTypeGrid(m_xBuilder->weld_widget("TypeGrid")) + + , m_xRepositoryBox(m_xBuilder->weld_widget("RepositoryDetails")) + , m_xFTRepository(m_xBuilder->weld_label("repositoryLabel")) + , m_xLBRepository(m_xBuilder->weld_combo_box("repositories")) + + , m_xEDShare(m_xBuilder->weld_entry("share")) + , m_xFTShare(m_xBuilder->weld_label("shareLabel")) + + , m_xDetailsGrid(m_xBuilder->weld_widget("Details")) + , m_xHostBox(m_xBuilder->weld_widget("HostDetails")) + , m_xEDHost(m_xBuilder->weld_entry("host")) + , m_xFTHost(m_xBuilder->weld_label("hostLabel")) + , m_xEDPort(m_xBuilder->weld_spin_button("port")) + , m_xFTPort(m_xBuilder->weld_label("portLabel")) + , m_xEDRoot(m_xBuilder->weld_entry("path")) + , m_xFTRoot(m_xBuilder->weld_label("pathLabel")) + + , m_xCBDavs(m_xBuilder->weld_check_button("webdavs")) +{ + m_xBTOk->connect_clicked( LINK( this, PlaceEditDialog, OKHdl) ); + m_xBTOk->set_sensitive( false ); + + m_xEDServerName->connect_changed( LINK( this, PlaceEditDialog, EditLabelHdl) ); + + // This constructor is called when user request a place creation, so + // delete button is hidden. + m_xBTDelete->hide(); + + m_xLBServerType->connect_changed( LINK( this, PlaceEditDialog, SelectTypeHdl ) ); + m_xEDUsername->connect_changed( LINK( this, PlaceEditDialog, EditUsernameHdl ) ); + m_xEDPassword->connect_changed( LINK( this, PlaceEditDialog, EditUsernameHdl ) ); + + InitDetails( ); +} + +PlaceEditDialog::PlaceEditDialog(weld::Window* pParent, const std::shared_ptr<Place>& rPlace) + : GenericDialogController(pParent, "svt/ui/placeedit.ui", "PlaceEditDialog") + , m_xCurrentDetails( ) + , m_bLabelChanged( true ) + , m_bShowPassword( false ) + , m_xEDServerName(m_xBuilder->weld_entry("name")) + , m_xLBServerType(m_xBuilder->weld_combo_box("type")) + , m_xEDUsername(m_xBuilder->weld_entry("login")) + , m_xFTUsernameLabel(m_xBuilder->weld_label("loginLabel")) + , m_xBTOk(m_xBuilder->weld_button("ok")) + , m_xBTCancel(m_xBuilder->weld_button("cancel")) + , m_xBTDelete(m_xBuilder->weld_button("delete")) + , m_xBTRepoRefresh(m_xBuilder->weld_button("repositoriesRefresh")) + , m_xCBPassword(m_xBuilder->weld_check_button("rememberPassword")) + , m_xEDPassword(m_xBuilder->weld_entry("password")) + , m_xFTPasswordLabel(m_xBuilder->weld_label("passwordLabel")) + , m_xTypeGrid(m_xBuilder->weld_widget("TypeGrid")) + + , m_xRepositoryBox(m_xBuilder->weld_widget("RepositoryDetails")) + , m_xFTRepository(m_xBuilder->weld_label("repositoryLabel")) + , m_xLBRepository(m_xBuilder->weld_combo_box("repositories")) + + , m_xEDShare(m_xBuilder->weld_entry("share")) + , m_xFTShare(m_xBuilder->weld_label("shareLabel")) + + , m_xDetailsGrid(m_xBuilder->weld_widget("Details")) + , m_xHostBox(m_xBuilder->weld_widget("HostDetails")) + , m_xEDHost(m_xBuilder->weld_entry("host")) + , m_xFTHost(m_xBuilder->weld_label("hostLabel")) + , m_xEDPort(m_xBuilder->weld_spin_button("port")) + , m_xFTPort(m_xBuilder->weld_label("portLabel")) + , m_xEDRoot(m_xBuilder->weld_entry("path")) + , m_xFTRoot(m_xBuilder->weld_label("pathLabel")) + + , m_xCBDavs(m_xBuilder->weld_check_button("webdavs")) +{ + m_xEDPassword->hide(); + m_xFTPasswordLabel->hide(); + m_xCBPassword->hide(); + + m_xBTOk->connect_clicked( LINK( this, PlaceEditDialog, OKHdl) ); + m_xBTDelete->connect_clicked( LINK( this, PlaceEditDialog, DelHdl) ); + + m_xEDServerName->connect_changed( LINK( this, PlaceEditDialog, ModifyHdl) ); + m_xLBServerType->connect_changed( LINK( this, PlaceEditDialog, SelectTypeHdl ) ); + + InitDetails( ); + + m_xEDServerName->set_text(rPlace->GetName()); + + // Fill the boxes with the URL parts + bool bSuccess = false; + for (size_t i = 0 ; i < m_aDetailsContainers.size( ) && !bSuccess; ++i) + { + INetURLObject& rUrl = rPlace->GetUrlObject(); + bSuccess = m_aDetailsContainers[i]->setUrl( rUrl ); + if ( bSuccess ) + { + // Fill the Username field + if ( rUrl.HasUserData( ) ) + { + m_xEDUsername->set_text( rUrl.GetUser(INetURLObject::DecodeMechanism::WithCharset) ); + m_aDetailsContainers[i]->setUsername( rUrl.GetUser(INetURLObject::DecodeMechanism::WithCharset) ); + } + + m_xLBServerType->set_active(i); + SelectType(true); + } + } + + // In edit mode user can't change connection type + m_xTypeGrid->hide(); +} + +PlaceEditDialog::~PlaceEditDialog() +{ +} + +OUString PlaceEditDialog::GetServerUrl() +{ + OUString sUrl; + if (m_xCurrentDetails) + { + INetURLObject aUrl = m_xCurrentDetails->getUrl(); + OUString sUsername = m_xEDUsername->get_text().trim(); + if ( !sUsername.isEmpty( ) ) + aUrl.SetUser( sUsername ); + if ( !aUrl.HasError( ) ) + sUrl = aUrl.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + + return sUrl; +} + +std::shared_ptr<Place> PlaceEditDialog::GetPlace() +{ + return std::make_shared<Place>(m_xEDServerName->get_text(), GetServerUrl(), true); +} + +void PlaceEditDialog::InitDetails( ) +{ + // Create CMIS controls for each server type + + // Load the ServerType entries + bool bSkipGDrive = OUString( GDRIVE_CLIENT_ID ).isEmpty() || + OUString( GDRIVE_CLIENT_SECRET ).isEmpty(); + bool bSkipAlfresco = OUString( ALFRESCO_CLOUD_CLIENT_ID ).isEmpty() || + OUString( ALFRESCO_CLOUD_CLIENT_SECRET ).isEmpty(); + bool bSkipOneDrive= OUString( ONEDRIVE_CLIENT_ID ).isEmpty() || + OUString( ONEDRIVE_CLIENT_SECRET ).isEmpty(); + + Sequence< OUString > aTypesUrlsList( officecfg::Office::Common::Misc::CmisServersUrls::get() ); + Sequence< OUString > aTypesNamesList( officecfg::Office::Common::Misc::CmisServersNames::get() ); + + int nPos = 0; + auto nSize = std::min(aTypesUrlsList.getLength(), aTypesNamesList.getLength()); + for ( sal_Int32 i = 0; i < nSize; ++i ) + { + OUString sUrl = aTypesUrlsList[i].replaceFirst("<host", OUStringConcatenation("<" + SvtResId(STR_SVT_HOST))).replaceFirst("port>", OUStringConcatenation(SvtResId(STR_SVT_PORT) + ">")); + + if ((sUrl == GDRIVE_BASE_URL && bSkipGDrive) || + (sUrl.startsWith( ALFRESCO_CLOUD_BASE_URL) && bSkipAlfresco) || + (sUrl == ONEDRIVE_BASE_URL && bSkipOneDrive)) + { + // this service is not supported + continue; + } + + m_xLBServerType->insert_text(nPos, aTypesNamesList[i].replaceFirst("Other CMIS", SvtResId(STR_SVT_OTHER_CMIS))); + + std::shared_ptr<DetailsContainer> xCmisDetails(std::make_shared<CmisDetailsContainer>(this, sUrl)); + xCmisDetails->setChangeHdl( LINK( this, PlaceEditDialog, EditHdl ) ); + m_aDetailsContainers.push_back(xCmisDetails); + + ++nPos; + } + + // Create WebDAV / FTP / SSH details control + std::shared_ptr<DetailsContainer> xDavDetails(std::make_shared<DavDetailsContainer>(this)); + xDavDetails->setChangeHdl( LINK( this, PlaceEditDialog, EditHdl ) ); + m_aDetailsContainers.push_back(xDavDetails); + + std::shared_ptr<DetailsContainer> xFtpDetails(std::make_shared<HostDetailsContainer>(this, 21, "ftp")); + xFtpDetails->setChangeHdl( LINK( this, PlaceEditDialog, EditHdl ) ); + m_aDetailsContainers.push_back(xFtpDetails); + + std::shared_ptr<DetailsContainer> xSshDetails(std::make_shared<HostDetailsContainer>(this, 22, "sftp")); + xSshDetails->setChangeHdl( LINK( this, PlaceEditDialog, EditHdl ) ); + m_aDetailsContainers.push_back(xSshDetails); + + // Remove Windows Share entry from dialog on Windows OS, where it's non-functional +#if defined(_WIN32) + // nPos is the position of first item that is pre-defined in svtools/uiconfig/ui/placeedit.ui, + // after other CMIS types were inserted + m_xLBServerType->remove(nPos + 3); +#else + // Create Windows Share control + std::shared_ptr<DetailsContainer> xSmbDetails(std::make_shared<SmbDetailsContainer>(this)); + xSmbDetails->setChangeHdl( LINK( this, PlaceEditDialog, EditHdl ) ); + m_aDetailsContainers.push_back(xSmbDetails); +#endif + + // Set default to first value + m_xLBServerType->set_active(0); + + if (m_xLBServerType->get_active_text() == "--------------------" ) + m_xLBServerType->set_active(1); + + SelectType(true); +} + +IMPL_LINK( PlaceEditDialog, OKHdl, weld::Button&, /*rBtn*/, void) +{ + if ( !m_xCurrentDetails ) + return; + + OUString sUrl = m_xCurrentDetails->getUrl().GetHost( INetURLObject::DecodeMechanism::WithCharset ); + + if ( sUrl.startsWith( GDRIVE_BASE_URL ) + || sUrl.startsWith( ALFRESCO_CLOUD_BASE_URL ) + || sUrl.startsWith( ONEDRIVE_BASE_URL ) ) + { + m_xBTRepoRefresh->clicked(); + + sUrl = m_xCurrentDetails->getUrl().GetHost( INetURLObject::DecodeMechanism::WithCharset ); + INetURLObject aHostUrl( sUrl ); + OUString sRepoId = aHostUrl.GetMark(); + + if ( !sRepoId.isEmpty() ) + { + m_xDialog->response(RET_OK); + } + else + { + // TODO: repository id missing. Auth error? + } + } + else + { + m_xDialog->response(RET_OK); + } +} + +IMPL_LINK( PlaceEditDialog, DelHdl, weld::Button&, /*rButton*/, void) +{ + // ReUsing existing symbols... + m_xDialog->response(RET_NO); +} + +IMPL_LINK_NOARG( PlaceEditDialog, EditHdl, DetailsContainer*, void ) +{ + if( !m_bLabelChanged ) + { + if( !m_xEDUsername->get_text().isEmpty( ) ) + { + OUString sLabel = SvtResId( STR_SVT_DEFAULT_SERVICE_LABEL ); + OUString sUser = m_xEDUsername->get_text(); + + int nLength = sUser.indexOf( '@' ); + if( nLength < 0 ) + nLength = sUser.getLength(); + + sLabel = sLabel.replaceFirst( "$user$", sUser.subView( 0, nLength ) ); + sLabel = sLabel.replaceFirst( "$service$", m_xLBServerType->get_active_text() ); + + m_xEDServerName->set_text( sLabel ); + m_bLabelChanged = false; + } + else + { + m_xEDServerName->set_text( m_xLBServerType->get_active_text( ) ); + } + } + + OUString sUrl = GetServerUrl( ); + OUString sName = m_xEDServerName->get_text().trim(); + m_xBTOk->set_sensitive( !sName.isEmpty( ) && !sUrl.isEmpty( ) ); +} + +IMPL_LINK_NOARG( PlaceEditDialog, ModifyHdl, weld::Entry&, void ) +{ + EditHdl(nullptr); +} + +IMPL_LINK_NOARG( PlaceEditDialog, EditLabelHdl, weld::Entry&, void ) +{ + m_bLabelChanged = true; + EditHdl(nullptr); +} + +IMPL_LINK_NOARG( PlaceEditDialog, EditUsernameHdl, weld::Entry&, void ) +{ + for ( auto& rxDetailsContainer : m_aDetailsContainers ) + { + rxDetailsContainer->setUsername( m_xEDUsername->get_text() ); + rxDetailsContainer->setPassword( m_xEDPassword->get_text() ); + } + + EditHdl(nullptr); +} + +IMPL_LINK_NOARG( PlaceEditDialog, SelectTypeHdl, weld::ComboBox&, void ) +{ + SelectType(false); +} + +void PlaceEditDialog::SelectType(bool bSkipSeparator) +{ + if ( m_xLBServerType->get_active_text() == "--------------------" ) + { + if (bSkipSeparator) + m_xLBServerType->set_active(m_nCurrentType); + else + m_xLBServerType->set_active(-1); + return; + } + + if (m_xCurrentDetails) + m_xCurrentDetails->set_visible(false); + + const int nPos = m_xLBServerType->get_active( ); + m_xCurrentDetails = m_aDetailsContainers[nPos]; + m_nCurrentType = nPos; + + m_xCurrentDetails->set_visible(true); + + m_xCBPassword->set_visible( m_bShowPassword && m_xCurrentDetails->enableUserCredentials() ); + m_xEDPassword->set_visible( m_bShowPassword && m_xCurrentDetails->enableUserCredentials() ); + m_xFTPasswordLabel->set_visible( m_bShowPassword && m_xCurrentDetails->enableUserCredentials() ); + m_xEDUsername->set_visible( m_xCurrentDetails->enableUserCredentials() ); + m_xFTUsernameLabel->set_visible( m_xCurrentDetails->enableUserCredentials() ); + + m_xDialog->resize_to_request(); + + EditHdl(nullptr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/dialogs/ServerDetailsControls.cxx b/svtools/source/dialogs/ServerDetailsControls.cxx new file mode 100644 index 000000000..24a6de413 --- /dev/null +++ b/svtools/source/dialogs/ServerDetailsControls.cxx @@ -0,0 +1,492 @@ +/* -*- 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 <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/task/PasswordContainer.hpp> +#include <com/sun/star/task/XPasswordContainer2.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> + +#include <comphelper/processfactory.hxx> +#include <o3tl/safeint.hxx> +#include <rtl/uri.hxx> +#include <ucbhelper/content.hxx> +#include <ucbhelper/commandenvironment.hxx> +#include <tools/diagnose_ex.h> + +#include <svtools/PlaceEditDialog.hxx> +#include <config_oauth2.h> + +#include "ServerDetailsControls.hxx" + +using namespace com::sun::star::sdbc; +using namespace com::sun::star::task; +using namespace com::sun::star::ucb; +using namespace com::sun::star::uno; + +DetailsContainer::DetailsContainer(PlaceEditDialog* pDialog) + : m_pDialog(pDialog) +{ + m_pDialog->m_xEDPort->connect_output(LINK(this, DetailsContainer, FormatPortHdl)); +} + +//format without thousand separator +IMPL_STATIC_LINK(DetailsContainer, FormatPortHdl, weld::SpinButton&, rSpinButton, void) +{ + rSpinButton.set_text(OUString::number(rSpinButton.get_value())); +} + +DetailsContainer::~DetailsContainer( ) +{ +} + +void DetailsContainer::set_visible( bool ) +{ + m_pDialog->m_xDetailsGrid->set_sensitive(true); + + m_pDialog->m_xEDHost->connect_changed( LINK( this, DetailsContainer, ValueChangeHdl ) ); + m_pDialog->m_xEDPort->connect_changed( LINK( this, DetailsContainer, ValueChangeHdl ) ); + m_pDialog->m_xEDRoot->connect_changed( LINK( this, DetailsContainer, ValueChangeHdl ) ); +} + +INetURLObject DetailsContainer::getUrl( ) +{ + // Don't use that class directly: make it smarter by subclassing it. + return INetURLObject( ); +} + +bool DetailsContainer::setUrl( const INetURLObject& ) +{ + // That class doesn't contain any logic... it defers the dirty work + // to the sub classes. + return false; +} + +void DetailsContainer::notifyChange( ) +{ + m_aChangeHdl.Call( this ); +} + +IMPL_LINK_NOARG( DetailsContainer, ValueChangeHdl, weld::Entry&, void ) +{ + notifyChange( ); +} + +HostDetailsContainer::HostDetailsContainer(PlaceEditDialog* pDialog, sal_uInt16 nPort, const OUString& sScheme) : + DetailsContainer( pDialog ), + m_nDefaultPort( nPort ), + m_sScheme( sScheme ) +{ + set_visible( false ); +} + +void HostDetailsContainer::set_visible( bool bShow ) +{ + m_pDialog->m_xFTHost->set_visible( bShow ); + m_pDialog->m_xHostBox->set_visible( bShow ); + m_pDialog->m_xEDRoot->set_visible( bShow ); + m_pDialog->m_xFTRoot->set_visible( bShow ); + + DetailsContainer::set_visible( bShow ); + + if ( bShow ) + { + if (m_pDialog->m_xEDPort->get_value() == 0) + m_pDialog->m_xEDPort->set_value( m_nDefaultPort ); + m_pDialog->m_xEDHost->set_text( m_sHost ); + } + else + m_pDialog->m_xEDPort->set_value( 0 ); +} + +INetURLObject HostDetailsContainer::getUrl( ) +{ + OUString sHost = m_pDialog->m_xEDHost->get_text().trim(); + sal_Int64 nPort = m_pDialog->m_xEDPort->get_value(); + OUString sPath = m_pDialog->m_xEDRoot->get_text().trim(); + + OUString sUrl; + if ( !sHost.isEmpty( ) ) + { + sUrl = m_sScheme + "://" + sHost; + if ( nPort != m_nDefaultPort ) + sUrl += ":" + OUString::number( nPort ); + if ( !sPath.isEmpty( ) ) + if ( sPath.indexOf( '/' ) != 0 ) + sUrl += "/"; + sUrl += sPath; + } + + return INetURLObject( sUrl ); +} + +bool HostDetailsContainer::setUrl( const INetURLObject& rUrl ) +{ + bool bSuccess = verifyScheme( INetURLObject::GetScheme( rUrl.GetProtocol( ) ) ); + + if ( bSuccess ) + { + m_sHost = rUrl.GetHost( ); + m_pDialog->m_xEDHost->set_text( rUrl.GetHost( ) ); + m_pDialog->m_xEDPort->set_value( rUrl.GetPort( ) ); + m_pDialog->m_xEDRoot->set_text( rUrl.GetURLPath() ); + } + + return bSuccess; +} + +bool HostDetailsContainer::verifyScheme( const OUString& sScheme ) +{ + return sScheme == OUStringConcatenation( m_sScheme + "://" ); +} + +DavDetailsContainer::DavDetailsContainer(PlaceEditDialog* pBuilder) + : HostDetailsContainer(pBuilder, 80, "http") +{ + m_pDialog->m_xCBDavs->connect_toggled(LINK(this, DavDetailsContainer, ToggledDavsHdl)); + + set_visible( false ); +} + +void DavDetailsContainer::set_visible( bool bShow ) +{ + HostDetailsContainer::set_visible( bShow ); + + if ( !bShow ) + m_pDialog->m_xCBDavs->set_active(false); + + m_pDialog->m_xCBDavs->set_visible(bShow); +} + +bool DavDetailsContainer::verifyScheme( const OUString& rScheme ) +{ + bool bValid = false; + if ( rScheme == "http://" ) + { + bValid = true; + m_pDialog->m_xCBDavs->set_active(false); + ToggledDavsHdl(*m_pDialog->m_xCBDavs); + } + else if ( rScheme == "https://" ) + { + bValid = true; + m_pDialog->m_xCBDavs->set_active(true); + ToggledDavsHdl(*m_pDialog->m_xCBDavs); + } + return bValid; +} + +IMPL_LINK( DavDetailsContainer, ToggledDavsHdl, weld::Toggleable&, rCheckBox, void ) +{ + // Change default port if needed + bool bCheckedDavs = rCheckBox.get_active(); + if ( m_pDialog->m_xEDPort->get_value() == 80 && bCheckedDavs ) + m_pDialog->m_xEDPort->set_value( 443 ); + else if ( m_pDialog->m_xEDPort->get_value() == 443 && !bCheckedDavs ) + m_pDialog->m_xEDPort->set_value( 80 ); + + OUString sScheme( "http" ); + if ( bCheckedDavs ) + sScheme = "https"; + setScheme( sScheme ); + + notifyChange( ); +} + +SmbDetailsContainer::SmbDetailsContainer(PlaceEditDialog* pDialog) + : DetailsContainer(pDialog) +{ + m_pDialog->m_xEDShare->connect_changed( LINK( this, DetailsContainer, ValueChangeHdl ) ); + + set_visible( false ); +} + +INetURLObject SmbDetailsContainer::getUrl( ) +{ + OUString sHost = m_pDialog->m_xEDHost->get_text().trim( ); + OUString sShare = m_pDialog->m_xEDShare->get_text().trim( ); + OUString sPath = m_pDialog->m_xEDRoot->get_text().trim( ); + + OUString sUrl; + if ( !sHost.isEmpty( ) ) + { + sUrl = "smb://" + sHost + "/"; + if ( !sShare.isEmpty( ) ) + sUrl += sShare; + if ( !sPath.isEmpty( ) ) + if ( sPath.indexOf( '/' ) != 0 ) + sUrl += "/"; + sUrl += sPath; + } + + return INetURLObject( sUrl ); +} + +bool SmbDetailsContainer::setUrl( const INetURLObject& rUrl ) +{ + bool bSuccess = rUrl.GetProtocol() == INetProtocol::Smb; + + if ( bSuccess ) + { + OUString sShare = rUrl.getName( 0 ); + OUString sFullPath = rUrl.GetURLPath( ); + OUString sPath; + if ( sFullPath.getLength( ) > sShare.getLength( ) ) + { + sal_Int32 nPos = sShare.getLength( ); + if ( nPos != 0 ) + ++nPos; + sPath = sFullPath.copy( nPos ); + } + + m_sHost = rUrl.GetHost( ); + m_pDialog->m_xEDHost->set_text( m_sHost ); + m_pDialog->m_xEDShare->set_text( sShare ); + m_pDialog->m_xEDRoot->set_text( sPath ); + } + + return bSuccess; +} + +void SmbDetailsContainer::set_visible( bool bShow ) +{ + m_pDialog->m_xEDShare->set_visible( bShow ); + m_pDialog->m_xFTShare->set_visible( bShow ); + m_pDialog->m_xEDRoot->set_visible( bShow ); + m_pDialog->m_xFTRoot->set_visible( bShow ); + + m_pDialog->m_xFTHost->set_visible( bShow ); + m_pDialog->m_xHostBox->set_visible( bShow ); + m_pDialog->m_xEDPort->set_sensitive( !bShow ); + m_pDialog->m_xFTPort->set_sensitive( !bShow ); + + if ( bShow ) + m_pDialog->m_xEDHost->set_text( m_sHost ); +} + +CmisDetailsContainer::CmisDetailsContainer(PlaceEditDialog* pParentDialog, OUString const & sBinding) : + DetailsContainer( pParentDialog ), + m_sUsername( ), + m_xCmdEnv( ), + m_aRepoIds( ), + m_sRepoId( ), + m_sBinding( sBinding ), + m_xParentDialog(pParentDialog->getDialog()->GetXWindow()) +{ + Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + Reference< XInteractionHandler > xGlobalInteractionHandler = + InteractionHandler::createWithParent(xContext, m_xParentDialog); + m_xCmdEnv = new ucbhelper::CommandEnvironment( xGlobalInteractionHandler, Reference< XProgressHandler >() ); + + set_visible( false ); +} + +void CmisDetailsContainer::set_visible( bool bShow ) +{ + m_pDialog->m_xLBRepository->connect_changed( LINK( this, CmisDetailsContainer, SelectRepoHdl ) ); + m_pDialog->m_xBTRepoRefresh->connect_clicked( LINK( this, CmisDetailsContainer, RefreshReposHdl ) ); + + m_pDialog->m_xEDHost->set_text( m_sBinding ); + + if( ( m_sBinding == GDRIVE_BASE_URL ) + || m_sBinding.startsWith( ALFRESCO_CLOUD_BASE_URL ) + || ( m_sBinding == ONEDRIVE_BASE_URL ) ) + { + m_pDialog->m_xFTHost->hide(); + m_pDialog->m_xHostBox->hide(); + m_pDialog->m_xFTRepository->hide(); + m_pDialog->m_xRepositoryBox->hide(); + m_pDialog->m_xEDRoot->hide(); + m_pDialog->m_xFTRoot->hide(); + } + else + { + m_pDialog->m_xFTHost->set_visible( bShow ); + m_pDialog->m_xHostBox->set_visible( bShow ); + m_pDialog->m_xFTRepository->set_visible( bShow ); + m_pDialog->m_xRepositoryBox->set_visible( bShow ); + m_pDialog->m_xEDRoot->set_visible( bShow ); + m_pDialog->m_xFTRoot->set_visible( bShow ); + } + + DetailsContainer::set_visible( bShow ); + m_pDialog->m_xEDPort->set_sensitive( !bShow ); + m_pDialog->m_xFTPort->set_sensitive( !bShow ); +} + +INetURLObject CmisDetailsContainer::getUrl( ) +{ + OUString sBindingUrl = m_pDialog->m_xEDHost->get_text().trim(); + OUString sPath = m_pDialog->m_xEDRoot->get_text().trim(); + + bool bSkip = true; + if( ( m_sBinding == GDRIVE_BASE_URL ) + || m_sBinding.startsWith( ALFRESCO_CLOUD_BASE_URL ) + || ( m_sBinding == ONEDRIVE_BASE_URL ) ) + { + bSkip = m_sUsername.isEmpty(); + } + else + { + bSkip = m_sRepoId.isEmpty(); + } + + OUString sUrl; + if ( !sBindingUrl.isEmpty( ) && !bSkip ) + { + OUString sEncodedBinding = rtl::Uri::encode( + sBindingUrl + "#" + m_sRepoId, + rtl_UriCharClassRelSegment, + rtl_UriEncodeKeepEscapes, + RTL_TEXTENCODING_UTF8 ); + sUrl = "vnd.libreoffice.cmis://" + sEncodedBinding; + } + sUrl += sPath; + + return INetURLObject( sUrl ); +} + +bool CmisDetailsContainer::setUrl( const INetURLObject& rUrl ) +{ + bool bSuccess = rUrl.GetProtocol() == INetProtocol::Cmis; + + if ( bSuccess ) + { + OUString sDecodedHost = rUrl.GetHost( INetURLObject::DecodeMechanism::WithCharset ); + INetURLObject aHostUrl( sDecodedHost ); + m_sBinding = aHostUrl.GetURLNoMark( ); + m_sRepoId = aHostUrl.GetMark( ); + + m_pDialog->m_xEDHost->set_text( m_sBinding ); + m_pDialog->m_xEDRoot->set_text( rUrl.GetURLPath() ); + } + return bSuccess; +} + +void CmisDetailsContainer::setUsername( const OUString& rUsername ) +{ + m_sUsername = rUsername; +} + +void CmisDetailsContainer::setPassword( const OUString& rPass ) +{ + m_sPassword = rPass; +} + +void CmisDetailsContainer::selectRepository( ) +{ + // Get the repo ID and call the Change listener + const int nPos = m_pDialog->m_xLBRepository->get_active(); + if( o3tl::make_unsigned(nPos) < m_aRepoIds.size() ) + { + m_sRepoId = m_aRepoIds[nPos]; + notifyChange( ); + } +} + +IMPL_LINK_NOARG( CmisDetailsContainer, RefreshReposHdl, weld::Button&, void ) +{ + Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + Reference< XPasswordContainer2 > xMasterPasswd = PasswordContainer::create( xContext ); + + + OUString sBindingUrl = m_pDialog->m_xEDHost->get_text().trim( ); + + OUString sEncodedUsername = ""; + + if ( !m_sUsername.isEmpty( ) ) + { + sEncodedUsername = rtl::Uri::encode(m_sUsername, + rtl_UriCharClassUserinfo, + rtl_UriEncodeKeepEscapes, + RTL_TEXTENCODING_UTF8 ) + + "@"; + } + + // Clean the listbox + m_pDialog->m_xLBRepository->clear(); + m_aRepoIds.clear(); + + // Compute the URL + OUString sUrl; + if ( !sBindingUrl.isEmpty( ) ) + { + OUString sEncodedBinding = rtl::Uri::encode( + sBindingUrl, + rtl_UriCharClassRelSegment, + rtl_UriEncodeKeepEscapes, + RTL_TEXTENCODING_UTF8 ); + sUrl = "vnd.libreoffice.cmis://" + sEncodedUsername + sEncodedBinding; + } + + // temporary remember the password + try + { + if( !sUrl.isEmpty() && !m_sUsername.isEmpty() && !m_sPassword.isEmpty() ) + { + Reference< XInteractionHandler > xInteractionHandler = + InteractionHandler::createWithParent(xContext, m_xParentDialog); + + Sequence<OUString> aPasswd { m_sPassword }; + + xMasterPasswd->add( + sUrl, m_sUsername, aPasswd, xInteractionHandler ); + } + } + catch( const Exception& ) + {} + + try + { + // Get the Content + ::ucbhelper::Content aCnt( sUrl, m_xCmdEnv, comphelper::getProcessComponentContext() ); + Sequence<OUString> aProps { "Title" }; + Reference< XResultSet > xResultSet( aCnt.createCursor( aProps ), UNO_SET_THROW ); + Reference< XContentAccess > xAccess( xResultSet, UNO_QUERY_THROW ); + while ( xResultSet->next() ) + { + OUString sURL = xAccess->queryContentIdentifierString( ); + INetURLObject aURL( sURL ); + OUString sId = aURL.GetURLPath( INetURLObject::DecodeMechanism::WithCharset ); + sId = sId.copy( 1 ); + m_aRepoIds.push_back( sId ); + + Reference< XRow > xRow( xResultSet, UNO_QUERY ); + OUString sName = xRow->getString( 1 ); + m_pDialog->m_xLBRepository->append_text(sName); + } + } + catch ( const Exception&) + { + TOOLS_WARN_EXCEPTION( "svtools.dialogs", "RefreshReposHdl" ); + } + + // Auto-select the first one + if (m_pDialog->m_xLBRepository->get_count() > 0) + { + m_pDialog->m_xLBRepository->set_active(0); + selectRepository( ); + } + + // remove temporary password + try + { + xMasterPasswd->remove( sUrl, m_sUsername ); + } + catch( const Exception& ) + {} +} + +IMPL_LINK_NOARG( CmisDetailsContainer, SelectRepoHdl, weld::ComboBox&, void ) +{ + selectRepository( ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/dialogs/ServerDetailsControls.hxx b/svtools/source/dialogs/ServerDetailsControls.hxx new file mode 100644 index 000000000..b0f892fe2 --- /dev/null +++ b/svtools/source/dialogs/ServerDetailsControls.hxx @@ -0,0 +1,143 @@ +/* -*- 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/. + */ +#ifndef INCLUDED_SVTOOLS_SERVERDETAILSCONTROLS_HXX +#define INCLUDED_SVTOOLS_SERVERDETAILSCONTROLS_HXX + +#include <vector> + +#include <com/sun/star/uno/Reference.hxx> + +#include <tools/link.hxx> +#include <tools/urlobj.hxx> +#include <vcl/weld.hxx> + +namespace com :: sun :: star :: ucb { class XCommandEnvironment; } +namespace com :: sun :: star :: awt { class XWindow; } + +namespace weld { + class Button; + class ComboBox; + class Entry; + class SpinButton; + class ToggleButton; +} + +class PlaceEditDialog; + +class DetailsContainer +{ + protected: + PlaceEditDialog* m_pDialog; + Link<DetailsContainer*,void> m_aChangeHdl; + + public: + DetailsContainer(PlaceEditDialog* pDialog); + virtual ~DetailsContainer( ); + + void setChangeHdl( const Link<DetailsContainer*,void>& rLink ) { m_aChangeHdl = rLink; } + + virtual void set_visible( bool bShow ); + virtual INetURLObject getUrl( ); + + /** Try to split the URL in the controls of that container. + + \param sUrl the URL to split + \return true if the split worked, false otherwise. + */ + virtual bool setUrl( const INetURLObject& rUrl ); + + virtual void setUsername( const OUString& /*rUsername*/ ) { }; + virtual void setPassword( const OUString& ) { }; + + virtual bool enableUserCredentials( ) { return true; }; + + protected: + void notifyChange( ); + DECL_LINK(ValueChangeHdl, weld::Entry&, void); + DECL_STATIC_LINK(DetailsContainer, FormatPortHdl, weld::SpinButton&, void); +}; + +class HostDetailsContainer : public DetailsContainer +{ + private: + sal_uInt16 m_nDefaultPort; + OUString m_sScheme; + OUString m_sHost; + + public: + HostDetailsContainer(PlaceEditDialog* pDialog, sal_uInt16 nPort, const OUString& sScheme); + + virtual void set_visible( bool bShow ) override; + virtual INetURLObject getUrl( ) override; + virtual bool setUrl( const INetURLObject& rUrl ) override; + + protected: + void setScheme( const OUString& sScheme ) { m_sScheme = sScheme; } + + /** Verifies that the scheme split from the URL can be handled by + the container and set the proper controls accordingly if needed. + */ + virtual bool verifyScheme( const OUString& rScheme ); +}; + +class DavDetailsContainer final : public HostDetailsContainer +{ + public: + DavDetailsContainer(PlaceEditDialog* pDialog); + + virtual void set_visible( bool bShow ) override; + virtual bool enableUserCredentials( ) override { return false; }; + + private: + virtual bool verifyScheme( const OUString& rScheme ) override; + + DECL_LINK(ToggledDavsHdl, weld::Toggleable&, void); +}; + +class SmbDetailsContainer final : public DetailsContainer +{ + private: + OUString m_sHost; + + public: + SmbDetailsContainer(PlaceEditDialog* pDialog); + + virtual INetURLObject getUrl( ) override; + virtual bool setUrl( const INetURLObject& rUrl ) override; + virtual void set_visible( bool bShow ) override; +}; + +class CmisDetailsContainer final : public DetailsContainer +{ + private: + OUString m_sUsername; + OUString m_sPassword; + css::uno::Reference< css::ucb::XCommandEnvironment > m_xCmdEnv; + std::vector< OUString > m_aRepoIds; + OUString m_sRepoId; + OUString m_sBinding; + css::uno::Reference< css::awt::XWindow > m_xParentDialog; + + public: + CmisDetailsContainer(PlaceEditDialog* pDialog, OUString const & sBinding); + + virtual void set_visible( bool bShow ) override; + virtual INetURLObject getUrl( ) override; + virtual bool setUrl( const INetURLObject& rUrl ) override; + virtual void setUsername( const OUString& rUsername ) override; + virtual void setPassword( const OUString& rPass ) override; + + private: + void selectRepository( ); + DECL_LINK ( RefreshReposHdl, weld::Button&, void ); + DECL_LINK ( SelectRepoHdl, weld::ComboBox&, void ); +}; + +#endif +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/dialogs/addresstemplate.cxx b/svtools/source/dialogs/addresstemplate.cxx new file mode 100644 index 000000000..6f29151e3 --- /dev/null +++ b/svtools/source/dialogs/addresstemplate.cxx @@ -0,0 +1,1122 @@ +/* -*- 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 <svtools/addresstemplate.hxx> +#include <svtools/strings.hrc> +#include <svtools/svtresid.hxx> +#include <tools/debug.hxx> +#include <comphelper/interaction.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/string.hxx> +#include <unotools/configitem.hxx> +#include <vcl/stdtext.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <sal/log.hxx> +#include <tools/diagnose_ex.h> +#include <osl/diagnose.h> +#include <com/sun/star/util/AliasProgrammaticPair.hpp> +#include <com/sun/star/ui/dialogs/AddressBookSourcePilot.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/sdb/DatabaseContext.hpp> +#include <com/sun/star/sdb/XCompletedConnection.hpp> +#include <com/sun/star/sdb/SQLContext.hpp> +#include <com/sun/star/sdbc/SQLWarning.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdbc/XDataSource.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <svl/filenotation.hxx> +#include <tools/urlobj.hxx> +#include <algorithm> +#include <map> +#include <set> +#include <array> +#include <strings.hxx> + + +namespace svt +{ + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::ui::dialogs; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::sdb; + using namespace ::com::sun::star::sdbc; + using namespace ::com::sun::star::sdbcx; + using namespace ::com::sun::star::task; + using namespace ::comphelper; + using namespace ::utl; + + typedef std::set<OUString> StringBag; + typedef std::map<OUString, OUString> MapString2String; + + namespace + { + OUString lcl_getSelectedDataSource( const weld::ComboBox& dataSourceCombo ) + { + OUString selectedDataSource = dataSourceCombo.get_active_text(); + if (dataSourceCombo.find_text(selectedDataSource) == -1) + { + // none of the pre-selected entries -> assume a path to a database document + OFileNotation aFileNotation( selectedDataSource, OFileNotation::N_SYSTEM ); + selectedDataSource = aFileNotation.get( OFileNotation::N_URL ); + } + return selectedDataSource; + } + + // = IAssignmentData + + class IAssignmentData + { + public: + virtual ~IAssignmentData(); + + /// the data source to use for the address book + virtual OUString getDatasourceName() const = 0; + + /// the command to use for the address book + virtual OUString getCommand() const = 0; + + /// checks whether or not there is an assignment for a given logical field + virtual bool hasFieldAssignment(const OUString& _rLogicalName) = 0; + /// retrieves the assignment for a given logical field + virtual OUString getFieldAssignment(const OUString& _rLogicalName) = 0; + + /// set the assignment for a given logical field + virtual void setFieldAssignment(const OUString& _rLogicalName, const OUString& _rAssignment) = 0; + + virtual void setDatasourceName(const OUString& _rName) = 0; + virtual void setCommand(const OUString& _rCommand) = 0; + }; + + } + + IAssignmentData::~IAssignmentData() + { + } + + + // = AssignmentTransientData + + namespace { + + class AssignmentTransientData : public IAssignmentData + { + protected: + OUString m_sDSName; + OUString m_sTableName; + MapString2String m_aAliases; + + public: + AssignmentTransientData( + const OUString& _rDataSourceName, + const OUString& _rTableName, + const Sequence< AliasProgrammaticPair >& _rFields + ); + + // IAssignmentData overridables + virtual OUString getDatasourceName() const override; + virtual OUString getCommand() const override; + + virtual bool hasFieldAssignment(const OUString& _rLogicalName) override; + virtual OUString getFieldAssignment(const OUString& _rLogicalName) override; + virtual void setFieldAssignment(const OUString& _rLogicalName, const OUString& _rAssignment) override; + + virtual void setDatasourceName(const OUString& _rName) override; + virtual void setCommand(const OUString& _rCommand) override; + }; + + } + + AssignmentTransientData::AssignmentTransientData( + const OUString& _rDataSourceName, const OUString& _rTableName, + const Sequence< AliasProgrammaticPair >& _rFields ) + :m_sDSName( _rDataSourceName ) + ,m_sTableName( _rTableName ) + { + // fill our aliases structure + // first collect all known programmatic names + StringBag aKnownNames; + + OUString const sLogicalFieldNames(STR_LOGICAL_FIELD_NAMES); + sal_Int32 nIndex = 0; + do + { + OUString aToken = sLogicalFieldNames.getToken(0, ';', nIndex); + aKnownNames.insert(aToken); + } + while ( nIndex >= 0); + + // loop through the given names + for (const AliasProgrammaticPair& rField : _rFields) + { + if ( aKnownNames.end() != aKnownNames.find( rField.ProgrammaticName ) ) + { + m_aAliases[ rField.ProgrammaticName ] = rField.Alias; + } + else + { + SAL_WARN( "svtools", "AssignmentTransientData::AssignmentTransientData: unknown programmatic name: " + << rField.ProgrammaticName ); + } + } + } + + + OUString AssignmentTransientData::getDatasourceName() const + { + return m_sDSName; + } + + + OUString AssignmentTransientData::getCommand() const + { + return m_sTableName; + } + + + bool AssignmentTransientData::hasFieldAssignment(const OUString& _rLogicalName) + { + MapString2String::const_iterator aPos = m_aAliases.find( _rLogicalName ); + return ( m_aAliases.end() != aPos ) + && ( !aPos->second.isEmpty() ); + } + + + OUString AssignmentTransientData::getFieldAssignment(const OUString& _rLogicalName) + { + OUString sReturn; + MapString2String::const_iterator aPos = m_aAliases.find( _rLogicalName ); + if ( m_aAliases.end() != aPos ) + sReturn = aPos->second; + + return sReturn; + } + + + void AssignmentTransientData::setFieldAssignment(const OUString& _rLogicalName, const OUString& _rAssignment) + { + m_aAliases[ _rLogicalName ] = _rAssignment; + } + + + void AssignmentTransientData::setDatasourceName(const OUString&) + { + OSL_FAIL( "AssignmentTransientData::setDatasourceName: cannot be implemented for transient data!" ); + } + + + void AssignmentTransientData::setCommand(const OUString&) + { + OSL_FAIL( "AssignmentTransientData::setCommand: cannot be implemented for transient data!" ); + } + + + // = AssignmentPersistentData + + namespace { + + class AssignmentPersistentData + :public ::utl::ConfigItem + ,public IAssignmentData + { + protected: + StringBag m_aStoredFields; + + protected: + css::uno::Any getProperty(const OUString& _rLocalName) const; + + OUString getStringProperty(const char* _pLocalName) const; + OUString getStringProperty(const OUString& _rLocalName) const; + + void setStringProperty(const char* _pLocalName, const OUString& _rValue); + + public: + AssignmentPersistentData(); + + // IAssignmentData overridables + virtual OUString getDatasourceName() const override; + virtual OUString getCommand() const override; + + virtual bool hasFieldAssignment(const OUString& _rLogicalName) override; + virtual OUString getFieldAssignment(const OUString& _rLogicalName) override; + virtual void setFieldAssignment(const OUString& _rLogicalName, const OUString& _rAssignment) override; + + virtual void setDatasourceName(const OUString& _rName) override; + virtual void setCommand(const OUString& _rCommand) override; + + virtual void Notify( const css::uno::Sequence<OUString>& aPropertyNames) override; + + private: + virtual void ImplCommit() override; + void clearFieldAssignment(const OUString& _rLogicalName); + }; + + } + +void AssignmentPersistentData::Notify( const css::uno::Sequence<OUString>& ) +{ +} + +void AssignmentPersistentData::ImplCommit() +{ +} + + + AssignmentPersistentData::AssignmentPersistentData() + :ConfigItem("Office.DataAccess/AddressBook") + { + const Sequence< OUString > aStoredNames = GetNodeNames("Fields"); + m_aStoredFields.insert(aStoredNames.begin(), aStoredNames.end()); + } + + bool AssignmentPersistentData::hasFieldAssignment(const OUString& _rLogicalName) + { + return (m_aStoredFields.end() != m_aStoredFields.find(_rLogicalName)); + } + + + OUString AssignmentPersistentData::getFieldAssignment(const OUString& _rLogicalName) + { + OUString sAssignment; + if (hasFieldAssignment(_rLogicalName)) + { + OUString sFieldPath = "Fields/" + _rLogicalName + "/AssignedFieldName"; + sAssignment = getStringProperty(sFieldPath); + } + return sAssignment; + } + + + Any AssignmentPersistentData::getProperty(const OUString& _rLocalName) const + { + Sequence< OUString > aProperties(&_rLocalName, 1); + Sequence< Any > aValues = const_cast<AssignmentPersistentData*>(this)->GetProperties(aProperties); + DBG_ASSERT(aValues.getLength() == 1, "AssignmentPersistentData::getProperty: invalid sequence length!"); + return aValues[0]; + } + + + OUString AssignmentPersistentData::getStringProperty(const OUString& _rLocalName) const + { + OUString sReturn; + getProperty( _rLocalName ) >>= sReturn; + return sReturn; + } + + + OUString AssignmentPersistentData::getStringProperty(const char* _pLocalName) const + { + OUString sReturn; + getProperty(OUString::createFromAscii(_pLocalName)) >>= sReturn; + return sReturn; + } + + + void AssignmentPersistentData::setStringProperty(const char* _pLocalName, const OUString& _rValue) + { + Sequence< OUString > aNames { OUString::createFromAscii(_pLocalName) }; + Sequence< Any > aValues{ Any(_rValue) }; + PutProperties(aNames, aValues); + } + + + void AssignmentPersistentData::setFieldAssignment(const OUString& _rLogicalName, const OUString& _rAssignment) + { + if (_rAssignment.isEmpty()) + { + if (hasFieldAssignment(_rLogicalName)) + { + // the assignment exists but it should be reset + clearFieldAssignment(_rLogicalName); + } + return; + } + + // Fields + OUString sDescriptionNodePath("Fields"); + + // Fields/<field> + OUString sFieldElementNodePath = sDescriptionNodePath + "/" + _rLogicalName; + + Sequence< PropertyValue > aNewFieldDescription{ + // Fields/<field>/ProgrammaticFieldName + comphelper::makePropertyValue(sFieldElementNodePath + "/ProgrammaticFieldName", + _rLogicalName), + // Fields/<field>/AssignedFieldName + comphelper::makePropertyValue(sFieldElementNodePath + "/AssignedFieldName", + _rAssignment) + }; + + // just set the new value + bool bSuccess = + SetSetProperties(sDescriptionNodePath, aNewFieldDescription); + DBG_ASSERT(bSuccess, "AssignmentPersistentData::setFieldAssignment: could not commit the changes a field!"); + } + + + void AssignmentPersistentData::clearFieldAssignment(const OUString& _rLogicalName) + { + if (!hasFieldAssignment(_rLogicalName)) + // nothing to do + return; + + Sequence< OUString > aNames(&_rLogicalName, 1); + ClearNodeElements("Fields", aNames); + } + + + OUString AssignmentPersistentData::getDatasourceName() const + { + return getStringProperty( "DataSourceName" ); + } + + + OUString AssignmentPersistentData::getCommand() const + { + return getStringProperty( "Command" ); + } + + + void AssignmentPersistentData::setDatasourceName(const OUString& _rName) + { + setStringProperty( "DataSourceName", _rName ); + } + + + void AssignmentPersistentData::setCommand(const OUString& _rCommand) + { + setStringProperty( "Command", _rCommand ); + } + + + // = AddressBookSourceDialogData + + struct AddressBookSourceDialogData + { + std::array<std::unique_ptr<weld::Label>, FIELD_PAIRS_VISIBLE*2> pFieldLabels; + std::array<std::unique_ptr<weld::ComboBox>, FIELD_PAIRS_VISIBLE*2> pFields; + + /// when working transient, we need the data source + Reference< XDataSource > + m_xTransientDataSource; + /// current scroll pos in the field list + sal_Int32 nFieldScrollPos; + /// indicates that we've an odd field number. This member is for efficiency only, it's redundant. + bool bOddFieldNumber : 1; + /// indicates that we're working with the real persistent configuration + bool bWorkingPersistent : 1; + + /// the strings to use as labels for the field selection listboxes + std::vector<OUString> aFieldLabels; + // the current field assignment + std::vector<OUString> aFieldAssignments; + /// the logical field names + std::vector<OUString> aLogicalFieldNames; + + std::unique_ptr<IAssignmentData> pConfigData; + + + AddressBookSourceDialogData( ) + :pFieldLabels{{nullptr}} + ,pFields{{nullptr}} + ,nFieldScrollPos(0) + ,bOddFieldNumber(false) + ,bWorkingPersistent( true ) + ,pConfigData( new AssignmentPersistentData ) + { + } + + AddressBookSourceDialogData( const Reference< XDataSource >& _rxTransientDS, const OUString& _rDataSourceName, + const OUString& _rTableName, const Sequence< AliasProgrammaticPair >& _rFields ) + :pFieldLabels{{nullptr}} + ,pFields{{nullptr}} + ,m_xTransientDataSource( _rxTransientDS ) + ,nFieldScrollPos(0) + ,bOddFieldNumber(false) + ,bWorkingPersistent( false ) + ,pConfigData( new AssignmentTransientData( _rDataSourceName, _rTableName, _rFields ) ) + { + } + + // Copy assignment is forbidden and not implemented. + AddressBookSourceDialogData (const AddressBookSourceDialogData &) = delete; + AddressBookSourceDialogData & operator= (const AddressBookSourceDialogData &) = delete; + }; + + // = AddressBookSourceDialog + AddressBookSourceDialog::AddressBookSourceDialog(weld::Window* pParent, + const Reference< XComponentContext >& _rxORB ) + : GenericDialogController(pParent, "svt/ui/addresstemplatedialog.ui", "AddressTemplateDialog") + , m_sNoFieldSelection(SvtResId(STR_NO_FIELD_SELECTION)) + , m_xORB(_rxORB) + , m_pImpl( new AddressBookSourceDialogData ) + { + implConstruct(); + } + + AddressBookSourceDialog::AddressBookSourceDialog(weld::Window* pParent, const Reference< XComponentContext >& _rxORB, + const Reference< XDataSource >& _rxTransientDS, const OUString& _rDataSourceName, + const OUString& _rTable, const Sequence< AliasProgrammaticPair >& _rMapping ) + : GenericDialogController(pParent, "svt/ui/addresstemplatedialog.ui", "AddressTemplateDialog") + , m_sNoFieldSelection(SvtResId(STR_NO_FIELD_SELECTION)) + , m_xORB(_rxORB) + , m_pImpl( new AddressBookSourceDialogData( _rxTransientDS, _rDataSourceName, _rTable, _rMapping ) ) + { + implConstruct(); + } + + void AddressBookSourceDialog::implConstruct() + { + m_xOKButton = m_xBuilder->weld_button("ok"); + m_xDatasource = m_xBuilder->weld_combo_box("datasource"); + m_xAdministrateDatasources = m_xBuilder->weld_button("admin"); + m_xTable = m_xBuilder->weld_combo_box("datatable"); + m_xFieldScroller = m_xBuilder->weld_scrolled_window("scrollwindow", true); + m_xGrid = m_xBuilder->weld_widget("grid"); + + for (sal_Int32 row=0; row<FIELD_PAIRS_VISIBLE; ++row) + { + for (sal_Int32 column=0; column<2; ++column) + { + // the label + m_pImpl->pFieldLabels[row * 2 + column] = m_xBuilder->weld_label("label" + OString::number(row * 2 + column)); + // the listbox + m_pImpl->pFields[row * 2 + column] = m_xBuilder->weld_combo_box("box" + OString::number(row * 2 + column)); + m_pImpl->pFields[row * 2 + column]->connect_changed(LINK(this, AddressBookSourceDialog, OnFieldSelect)); + } + } + + initializeDatasources(); + + // for the moment, we have a hard coded list of all known fields. + // A better solution would be to store all known field translations in the configuration, which could be + // extensible by the user in an arbitrary way. + // But for the moment we need a quick solution ... + // (the main thing would be to store the translations to use here in the user interface, besides that, the code + // should be adjustable with a rather small effort.) + + // initialize the strings for the field labels + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_FIRSTNAME )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_LASTNAME )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_COMPANY)); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_DEPARTMENT )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_STREET )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_ZIPCODE )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_CITY )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_STATE)); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_COUNTRY )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_HOMETEL )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_WORKTEL )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_OFFICETEL)); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_MOBILE)); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_TELOTHER)); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_PAGER)); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_FAX )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_EMAIL )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_URL )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_TITLE )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_POSITION )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_INITIALS )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_ADDRFORM )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_SALUTATION )); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_ID)); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_CALENDAR)); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_INVITE)); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_NOTE)); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_USER1)); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_USER2)); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_USER3)); + m_pImpl->aFieldLabels.push_back( SvtResId( STR_FIELD_USER4)); + + tools::Long nLabelWidth = 0; + tools::Long nListBoxWidth = m_pImpl->pFields[0]->get_approximate_digit_width() * 18; + for (auto const& fieldLabel : m_pImpl->aFieldLabels) + { + m_pImpl->pFieldLabels[0]->set_label(fieldLabel); + nLabelWidth = std::max(nLabelWidth, m_pImpl->pFieldLabels[0]->get_preferred_size().Width()); + } + for (sal_Int32 row=0; row<FIELD_PAIRS_VISIBLE; ++row) + { + for (sal_Int32 column=0; column<2; ++column) + { + m_pImpl->pFieldLabels[row * 2 + column]->set_size_request(nLabelWidth, -1); + m_pImpl->pFields[row * 2 + column]->set_size_request(nListBoxWidth, -1); + } + } + + // force an even number of known fields + m_pImpl->bOddFieldNumber = (m_pImpl->aFieldLabels.size() % 2) != 0; + if (m_pImpl->bOddFieldNumber) + m_pImpl->aFieldLabels.emplace_back(); + + // limit the scrollbar range accordingly + sal_Int32 nOverallFieldPairs = m_pImpl->aFieldLabels.size() / 2; + m_xFieldScroller->vadjustment_configure(0, 0, nOverallFieldPairs, + 1, FIELD_PAIRS_VISIBLE - 1, FIELD_PAIRS_VISIBLE); + + // reset the current field assignments + m_pImpl->aFieldAssignments.resize(m_pImpl->aFieldLabels.size()); + // (empty strings mean "no assignment") + + // some knittings + m_xFieldScroller->connect_vadjustment_changed(LINK(this, AddressBookSourceDialog, OnFieldScroll)); + m_xAdministrateDatasources->connect_clicked(LINK(this, AddressBookSourceDialog, OnAdministrateDatasources)); + m_xDatasource->set_entry_completion(true); + m_xTable->set_entry_completion(true); + m_xTable->connect_focus_in(LINK(this, AddressBookSourceDialog, OnComboGetFocus)); + m_xDatasource->connect_focus_in(LINK(this, AddressBookSourceDialog, OnComboGetFocus)); + m_xTable->connect_focus_out(LINK(this, AddressBookSourceDialog, OnComboLoseFocus)); + m_xDatasource->connect_focus_out(LINK(this, AddressBookSourceDialog, OnComboLoseFocus)); + m_xTable->connect_changed(LINK(this, AddressBookSourceDialog, OnComboSelect)); + m_xDatasource->connect_changed(LINK(this, AddressBookSourceDialog, OnComboSelect)); + m_xOKButton->connect_clicked(LINK(this, AddressBookSourceDialog, OnOkClicked)); + + // initialize the field controls + resetFields(); + // tdf#136494 wait until contents are filled before getting preferred height + m_xFieldScroller->set_size_request(-1, m_xGrid->get_preferred_size().Height()); + m_xFieldScroller->vadjustment_set_value(0); + m_pImpl->nFieldScrollPos = -1; + implScrollFields(0, false, false); + + // the logical names + OUString sLogicalFieldNames(STR_LOGICAL_FIELD_NAMES); + sal_Int32 nAdjustedTokenCount = comphelper::string::getTokenCount(sLogicalFieldNames, ';') + (m_pImpl->bOddFieldNumber ? 1 : 0); + DBG_ASSERT(nAdjustedTokenCount == static_cast<sal_Int32>(m_pImpl->aFieldLabels.size()), + "AddressBookSourceDialog::AddressBookSourceDialog: inconsistence between logical and UI field names!"); + m_pImpl->aLogicalFieldNames.reserve(nAdjustedTokenCount); + sal_Int32 nIdx{ 0 }; + for (sal_Int32 i = 0; i<nAdjustedTokenCount; ++i) + m_pImpl->aLogicalFieldNames.push_back(sLogicalFieldNames.getToken(0, ';', nIdx)); + + Application::PostUserEvent(LINK(this, AddressBookSourceDialog, OnDelayedInitialize), nullptr, false); + + // so the dialog will at least show up before we do the loading of the + // configuration data and the (maybe time consuming) analysis of the data source/table to select + + if (m_pImpl->bWorkingPersistent) + return; + + m_xAdministrateDatasources->hide(); + } + + void AddressBookSourceDialog::getFieldMapping(Sequence< AliasProgrammaticPair >& _rMapping) const + { + _rMapping.realloc( m_pImpl->aLogicalFieldNames.size() ); + AliasProgrammaticPair* pPair = _rMapping.getArray(); + + for (auto const& logicalFieldName : m_pImpl->aLogicalFieldNames) + { + if ( m_pImpl->pConfigData->hasFieldAssignment(logicalFieldName) ) + { + // the user gave us an assignment for this field + pPair->ProgrammaticName = logicalFieldName; + pPair->Alias = m_pImpl->pConfigData->getFieldAssignment(logicalFieldName); + ++pPair; + } + } + + _rMapping.realloc( pPair - _rMapping.getArray() ); + } + + void AddressBookSourceDialog::loadConfiguration() + { + OUString sName = m_pImpl->pConfigData->getDatasourceName(); + INetURLObject aURL( sName ); + if( aURL.GetProtocol() != INetProtocol::NotValid ) + { + OFileNotation aFileNotation( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + sName = aFileNotation.get(OFileNotation::N_SYSTEM); + } + + m_xDatasource->set_entry_text(sName); + m_xTable->set_entry_text(m_pImpl->pConfigData->getCommand()); + // we ignore the CommandType: only tables are supported + + // the logical names for the fields + // AddressBookSourceDialog::loadConfiguration: inconsistence between field names and field assignments! + assert(m_pImpl->aLogicalFieldNames.size() == m_pImpl->aFieldAssignments.size()); + + auto aAssignment = m_pImpl->aFieldAssignments.begin(); + for (auto const& logicalFieldName : m_pImpl->aLogicalFieldNames) + { + *aAssignment = m_pImpl->pConfigData->getFieldAssignment(logicalFieldName); + ++aAssignment; + } + } + + AddressBookSourceDialog::~AddressBookSourceDialog() + { + } + + void AddressBookSourceDialog::initializeDatasources() + { + if (!m_xDatabaseContext.is()) + { + DBG_ASSERT(m_xORB.is(), "AddressBookSourceDialog::initializeDatasources: no service factory!"); + if (!m_xORB.is()) + return; + + try + { + m_xDatabaseContext = DatabaseContext::create(m_xORB); + } + catch(const Exception&) { } + if (!m_xDatabaseContext.is()) + { + ShowServiceNotAvailableError(m_xDialog.get(), u"com.sun.star.sdb.DatabaseContext", false); + return; + } + } + m_xDatasource->clear(); + + // fill the datasources listbox + try + { + const css::uno::Sequence<OUString> aElementNames = m_xDatabaseContext->getElementNames(); + for (const OUString& rDatasourceName : aElementNames) + m_xDatasource->append_text(rDatasourceName); + } + catch(Exception&) + { + TOOLS_WARN_EXCEPTION( "svtools", "AddressBookSourceDialog::initializeDatasources: caught an exception while asking for the data source names!"); + } + } + + IMPL_LINK(AddressBookSourceDialog, OnFieldScroll, weld::ScrolledWindow&, rScrollBar, void) + { + implScrollFields(rScrollBar.vadjustment_get_value(), true, true); + } + + void AddressBookSourceDialog::resetTables() + { + if (!m_xDatabaseContext.is()) + return; + + weld::WaitObject aWaitCursor(m_xDialog.get()); + + // no matter what we do here, we handled the currently selected data source (no matter if successful or not) + m_xDatasource->save_value(); + + // create an interaction handler (may be needed for connecting) + Reference< XInteractionHandler > xHandler; + try + { + xHandler.set( + InteractionHandler::createWithParent(m_xORB, m_xDialog->GetXWindow()), + UNO_QUERY_THROW ); + } + catch(const Exception&) { } + if (!xHandler.is()) + { + ShowServiceNotAvailableError(m_xDialog.get(), u"com.sun.star.task.InteractionHandler", true); + return; + } + + // the currently selected table + OUString sOldTable = m_xTable->get_active_text(); + + m_xTable->clear(); + + m_xCurrentDatasourceTables= nullptr; + + // get the tables of the connection + Sequence< OUString > aTableNames; + Any aException; + try + { + Reference< XCompletedConnection > xDS; + if ( m_pImpl->bWorkingPersistent ) + { + OUString sSelectedDS = lcl_getSelectedDataSource(*m_xDatasource); + + // get the data source the user has chosen and let it build a connection + INetURLObject aURL( sSelectedDS ); + if ( aURL.GetProtocol() != INetProtocol::NotValid || m_xDatabaseContext->hasByName(sSelectedDS) ) + m_xDatabaseContext->getByName( sSelectedDS ) >>= xDS; + } + else + { + xDS.set(m_pImpl->m_xTransientDataSource, css::uno::UNO_QUERY); + } + + // build the connection + Reference< XConnection > xConn; + if (xDS.is()) + xConn = xDS->connectWithCompletion(xHandler); + + // get the table names + Reference< XTablesSupplier > xSupplTables(xConn, UNO_QUERY); + if (xSupplTables.is()) + { + m_xCurrentDatasourceTables = xSupplTables->getTables(); + if (m_xCurrentDatasourceTables.is()) + aTableNames = m_xCurrentDatasourceTables->getElementNames(); + } + } + catch(const SQLContext& e) { aException <<= e; } + catch(const SQLWarning& e) { aException <<= e; } + catch(const SQLException& e) { aException <<= e; } + catch(Exception&) + { + OSL_FAIL("AddressBookSourceDialog::resetTables: could not retrieve the table!"); + } + + if (aException.hasValue()) + { + Reference< XInteractionRequest > xRequest = new OInteractionRequest(aException); + try + { + xHandler->handle(xRequest); + } + catch(Exception&) { } + return; + } + + bool bKnowOldTable = false; + // fill the table list + for (const OUString& rTableName : std::as_const(aTableNames)) + { + m_xTable->append_text(rTableName); + if (rTableName == sOldTable) + bKnowOldTable = true; + } + + // set the old table, if the new data source knows a table with this name, too. Else reset the tables edit field. + if (!bKnowOldTable) + sOldTable.clear(); + m_xTable->set_entry_text(sOldTable); + + resetFields(); + } + + void AddressBookSourceDialog::resetFields() + { + weld::WaitObject aWaitCursor(m_xDialog.get()); + + // no matter what we do here, we handled the currently selected table (no matter if successful or not) + m_xDatasource->save_value(); + + OUString sSelectedTable = m_xTable->get_active_text(); + Sequence< OUString > aColumnNames; + try + { + if (m_xCurrentDatasourceTables.is()) + { + // get the table and the columns + Reference< XColumnsSupplier > xSuppTableCols; + if (m_xCurrentDatasourceTables->hasByName(sSelectedTable)) + xSuppTableCols.set( + m_xCurrentDatasourceTables->getByName(sSelectedTable), + css::uno::UNO_QUERY); + Reference< XNameAccess > xColumns; + if (xSuppTableCols.is()) + xColumns = xSuppTableCols->getColumns(); + if (xColumns.is()) + aColumnNames = xColumns->getElementNames(); + } + } + catch (const Exception&) + { + OSL_FAIL("AddressBookSourceDialog::resetFields: could not retrieve the table columns!"); + } + + + // for quicker access + ::std::set< OUString > aColumnNameSet(std::cbegin(aColumnNames), std::cend(aColumnNames)); + + std::vector<OUString>::iterator aInitialSelection = m_pImpl->aFieldAssignments.begin() + m_pImpl->nFieldScrollPos; + + OUString sSaveSelection; + for (sal_Int32 i=0; i<FIELD_CONTROLS_VISIBLE; ++i, ++aInitialSelection) + { + weld::ComboBox* pListbox = m_pImpl->pFields[i].get(); + sSaveSelection = pListbox->get_active_text(); + + pListbox->clear(); + + // the one entry for "no selection" + pListbox->append_text(m_sNoFieldSelection); + // as it's entry data, set the index of the list box in our array + pListbox->set_id(0, OUString::number(i)); + + // the field names + for (const OUString& rColumnName : std::as_const(aColumnNames)) + pListbox->append_text(rColumnName); + + if (!aInitialSelection->isEmpty() && (aColumnNameSet.end() != aColumnNameSet.find(*aInitialSelection))) + // we can select the entry as specified in our field assignment array + pListbox->set_active_text(*aInitialSelection); + else + // try to restore the selection + if (aColumnNameSet.end() != aColumnNameSet.find(sSaveSelection)) + // the old selection is a valid column name + pListbox->set_active_text(sSaveSelection); + else + // select the <none> entry + pListbox->set_active(0); + } + + // adjust m_pImpl->aFieldAssignments + for (auto & fieldAssignment : m_pImpl->aFieldAssignments) + if (!fieldAssignment.isEmpty()) + if (aColumnNameSet.end() == aColumnNameSet.find(fieldAssignment)) + fieldAssignment.clear(); + } + + IMPL_LINK(AddressBookSourceDialog, OnFieldSelect, weld::ComboBox&, rListbox, void) + { + // the index of the affected list box in our array + sal_Int32 nListBoxIndex = rListbox.get_id(0).toInt32(); + DBG_ASSERT(nListBoxIndex >= 0 && nListBoxIndex < FIELD_CONTROLS_VISIBLE, + "AddressBookSourceDialog::OnFieldScroll: invalid list box entry!"); + + // update the array where we remember the field selections + if (0 == rListbox.get_active()) + // it's the "no field selection" entry + m_pImpl->aFieldAssignments[m_pImpl->nFieldScrollPos * 2 + nListBoxIndex].clear(); + else + // it's a regular field entry + m_pImpl->aFieldAssignments[m_pImpl->nFieldScrollPos * 2 + nListBoxIndex] = rListbox.get_active_text(); + } + + + void AddressBookSourceDialog::implScrollFields(sal_Int32 _nPos, bool _bAdjustFocus, bool _bAdjustScrollbar) + { + if (_nPos == m_pImpl->nFieldScrollPos) + // nothing to do + return; + + // loop through our field control rows and do some adjustments + // for the new texts + auto pLeftLabelControl = m_pImpl->pFieldLabels.begin(); + auto pRightLabelControl = pLeftLabelControl+1; + auto pLeftColumnLabel = m_pImpl->aFieldLabels.cbegin() + 2 * _nPos; + auto pRightColumnLabel = pLeftColumnLabel + 1; + + // for the focus movement and the selection scroll + auto pLeftListControl = m_pImpl->pFields.begin(); + auto pRightListControl = pLeftListControl + 1; + + // for the focus movement + sal_Int32 nOldFocusRow = -1; + sal_Int32 nOldFocusColumn = 0; + + // for the selection scroll + auto pLeftAssignment = m_pImpl->aFieldAssignments.cbegin() + 2 * _nPos; + auto pRightAssignment = pLeftAssignment + 1; + + // loop + for (sal_Int32 i=0; i<FIELD_PAIRS_VISIBLE; ++i) + { + if ((*pLeftListControl)->has_focus()) + { + nOldFocusRow = i; + nOldFocusColumn = 0; + } + else if ((*pRightListControl)->has_focus()) + { + nOldFocusRow = i; + nOldFocusColumn = 1; + } + + // the new texts of the label controls + (*pLeftLabelControl)->set_label(*pLeftColumnLabel); + (*pRightLabelControl)->set_label(*pRightColumnLabel); + + // we may have to hide the controls in the right column, if we have no label text for it + // (which means we have an odd number of fields, though we forced our internal arrays to + // be even-sized for easier handling) + // (If sometimes we support an arbitrary number of field assignments, we would have to care for + // an invisible left hand side column, too. But right now, the left hand side controls are always + // visible) + bool bHideRightColumn = pRightColumnLabel->isEmpty(); + (*pRightLabelControl)->set_visible(!bHideRightColumn); + (*pRightListControl)->set_visible(!bHideRightColumn); + // the new selections of the listboxes + implSelectField(pLeftListControl->get(), *pLeftAssignment); + implSelectField(pRightListControl->get(), *pRightAssignment); + + // increment ... + if ( i < FIELD_PAIRS_VISIBLE - 1 ) + { // (not in the very last round, here the +=2 could result in an invalid + // iterator position, which causes an abort in a non-product version + pLeftLabelControl += 2; + pRightLabelControl += 2; + pLeftColumnLabel += 2; + pRightColumnLabel += 2; + + pLeftListControl += 2; + pRightListControl += 2; + pLeftAssignment += 2; + pRightAssignment += 2; + } + } + + if (_bAdjustFocus && (nOldFocusRow >= 0)) + { // we have to adjust the focus and one of the list boxes has the focus + sal_Int32 nDelta = m_pImpl->nFieldScrollPos - _nPos; + // the new row for the focus + sal_Int32 nNewFocusRow = nOldFocusRow + nDelta; + // normalize + nNewFocusRow = std::min(nNewFocusRow, sal_Int32(FIELD_PAIRS_VISIBLE - 1), ::std::less< sal_Int32 >()); + nNewFocusRow = std::max(nNewFocusRow, sal_Int32(0), ::std::less< sal_Int32 >()); + // set the new focus (in the same column) + m_pImpl->pFields[nNewFocusRow * 2 + nOldFocusColumn]->grab_focus(); + } + + m_pImpl->nFieldScrollPos = _nPos; + + if (_bAdjustScrollbar) + m_xFieldScroller->vadjustment_set_value(m_pImpl->nFieldScrollPos); + } + + void AddressBookSourceDialog::implSelectField(weld::ComboBox* pBox, const OUString& rText) + { + if (!rText.isEmpty()) + // a valid field name + pBox->set_active_text(rText); + else + // no selection for this item + pBox->set_active(0); + } + + IMPL_LINK_NOARG(AddressBookSourceDialog, OnDelayedInitialize, void*, void) + { + // load the initial data from the configuration + loadConfiguration(); + resetTables(); + // will reset the tables/fields implicitly + + if ( !m_pImpl->bWorkingPersistent ) + if ( m_pImpl->pFields[0] ) + m_pImpl->pFields[0]->grab_focus(); + } + + IMPL_LINK(AddressBookSourceDialog, OnComboSelect, weld::ComboBox&, rBox, void) + { + if (&rBox == m_xDatasource.get()) + resetTables(); + else + resetFields(); + } + + IMPL_STATIC_LINK(AddressBookSourceDialog, OnComboGetFocus, weld::Widget&, rBox, void) + { + dynamic_cast<weld::ComboBox&>(rBox).save_value(); + } + + IMPL_LINK(AddressBookSourceDialog, OnComboLoseFocus, weld::Widget&, rControl, void) + { + weld::ComboBox& rBox = dynamic_cast<weld::ComboBox&>(rControl); + if (rBox.get_value_changed_from_saved()) + { + if (&rBox == m_xDatasource.get()) + resetTables(); + else + resetFields(); + } + } + + IMPL_LINK_NOARG(AddressBookSourceDialog, OnOkClicked, weld::Button&, void) + { + OUString sSelectedDS = lcl_getSelectedDataSource(*m_xDatasource); + if ( m_pImpl->bWorkingPersistent ) + { + m_pImpl->pConfigData->setDatasourceName(sSelectedDS); + m_pImpl->pConfigData->setCommand(m_xTable->get_active_text()); + } + + // AddressBookSourceDialog::loadConfiguration: inconsistence between field names and field assignments! + assert(m_pImpl->aLogicalFieldNames.size() == m_pImpl->aFieldAssignments.size()); + + // set the field assignments + auto aAssignment = m_pImpl->aFieldAssignments.cbegin(); + for (auto const& logicalFieldName : m_pImpl->aLogicalFieldNames) + { + m_pImpl->pConfigData->setFieldAssignment(logicalFieldName, *aAssignment); + ++aAssignment; + } + + m_xDialog->response(RET_OK); + } + + IMPL_LINK_NOARG(AddressBookSourceDialog, OnAdministrateDatasources, weld::Button&, void) + { + // create the dialog object + Reference< XExecutableDialog > xAdminDialog; + try + { + xAdminDialog = AddressBookSourcePilot::createWithParent(m_xORB, m_xDialog->GetXWindow()); + } + catch(const Exception&) { } + if (!xAdminDialog.is()) + { + ShowServiceNotAvailableError(m_xDialog.get(), u"com.sun.star.ui.dialogs.AddressBookSourcePilot", true); + return; + } + + // execute the dialog + try + { + if ( xAdminDialog->execute() == RET_OK ) + { + Reference<XPropertySet> xProp(xAdminDialog,UNO_QUERY); + if ( xProp.is() ) + { + OUString sName; + xProp->getPropertyValue("DataSourceName") >>= sName; + + INetURLObject aURL( sName ); + if( aURL.GetProtocol() != INetProtocol::NotValid ) + { + OFileNotation aFileNotation( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + sName = aFileNotation.get(OFileNotation::N_SYSTEM); + } + m_xDatasource->append_text(sName); + m_pImpl->pConfigData.reset( new AssignmentPersistentData ); + loadConfiguration(); + resetTables(); + // will reset the fields implicitly + } + } + } + catch(const Exception&) + { + OSL_FAIL("AddressBookSourceDialog::OnAdministrateDatasources: an error occurred while executing the administration dialog!"); + } + + // re-fill the data source list + // try to preserve the current selection + +// initializeDatasources(); + } + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/dialogs/colrdlg.cxx b/svtools/source/dialogs/colrdlg.cxx new file mode 100644 index 000000000..27008ccfc --- /dev/null +++ b/svtools/source/dialogs/colrdlg.cxx @@ -0,0 +1,161 @@ +/* -*- 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 <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/beans/XPropertyAccess.hpp> +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/cui/AsynchronousColorPicker.hpp> +#include <com/sun/star/cui/ColorPicker.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> + +#include <svtools/colrdlg.hxx> +#include <svtools/dialogclosedlistener.hxx> +#include <vcl/weld.hxx> +#include <osl/diagnose.h> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::ui::dialogs; + +const OUStringLiteral sColor = u"Color"; + +SvColorDialog::SvColorDialog() + : meMode(svtools::ColorPickerMode::Select) +{ +} + +SvColorDialog::~SvColorDialog() +{ +} +void SvColorDialog::SetColor( const Color& rColor ) +{ + maColor = rColor; +} + +void SvColorDialog::SetMode( svtools::ColorPickerMode eMode ) +{ + meMode = eMode; +} + +short SvColorDialog::Execute(weld::Window* pParent) +{ + short ret = 0; + try + { + Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + + Reference<css::awt::XWindow> xParent; + if (pParent) + xParent = pParent->GetXWindow(); + + Reference< XExecutableDialog > xDialog = css::cui::ColorPicker::createWithParent(xContext, xParent); + Reference< XPropertyAccess > xPropertyAccess( xDialog, UNO_QUERY_THROW ); + + Sequence< PropertyValue > props{ + comphelper::makePropertyValue(OUString( sColor ), maColor), + comphelper::makePropertyValue("Mode", static_cast<sal_Int16>(meMode)) + }; + + xPropertyAccess->setPropertyValues( props ); + + ret = xDialog->execute(); + + if( ret ) + { + props = xPropertyAccess->getPropertyValues(); + for( const auto& rProp : std::as_const(props) ) + { + if( rProp.Name == sColor ) + { + rProp.Value >>= maColor; + } + } + } + } + catch(Exception&) + { + OSL_ASSERT(false); + } + + return ret; +} + +void SvColorDialog::ExecuteAsync(weld::Window* pParent, const std::function<void(sal_Int32)>& func) +{ + m_aResultFunc = func; + + try + { + Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + + Reference<css::awt::XWindow> xParent; + if (pParent) + xParent = pParent->GetXWindow(); + + mxDialog = css::cui::AsynchronousColorPicker::createWithParent(xContext, xParent); + Reference< XPropertyAccess > xPropertyAccess( mxDialog, UNO_QUERY_THROW ); + + Sequence< PropertyValue > props{ + comphelper::makePropertyValue(OUString( sColor ), maColor), + comphelper::makePropertyValue("Mode", static_cast<sal_Int16>(meMode)) + }; + + xPropertyAccess->setPropertyValues( props ); + + rtl::Reference< ::svt::DialogClosedListener > pListener = new ::svt::DialogClosedListener(); + pListener->SetDialogClosedLink( LINK( this, SvColorDialog, DialogClosedHdl ) ); + + mxDialog->startExecuteModal( pListener ); + } + catch(Exception&) + { + OSL_ASSERT(false); + } +} + +IMPL_LINK( SvColorDialog, DialogClosedHdl, css::ui::dialogs::DialogClosedEvent*, pEvent, void ) +{ + sal_Int32 nResult = 0; + sal_Int16 nDialogRet = pEvent->DialogResult; + if( nDialogRet == ExecutableDialogResults::OK ) + { + nResult = RET_OK; + + Reference< XPropertyAccess > xPropertyAccess( mxDialog, UNO_QUERY_THROW ); + Sequence< PropertyValue > props = xPropertyAccess->getPropertyValues(); + + for( const auto& rProp : std::as_const(props) ) + { + if( rProp.Name == sColor ) + { + rProp.Value >>= maColor; + } + } + } + + m_aResultFunc(nResult); + mxDialog.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/dialogs/insdlg.cxx b/svtools/source/dialogs/insdlg.cxx new file mode 100644 index 000000000..adaa1b905 --- /dev/null +++ b/svtools/source/dialogs/insdlg.cxx @@ -0,0 +1,356 @@ +/* -*- 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 <svtools/insdlg.hxx> +#include <svtools/strings.hrc> +#include <svtools/svtresid.hxx> + +#include <unotools/configmgr.hxx> +#include <comphelper/classids.hxx> +#include <sot/stg.hxx> +#include <sal/macros.h> + +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertysequence.hxx> +#include <com/sun/star/container/XNameAccess.hpp> + +using namespace ::com::sun::star; + +#if defined _WIN32 +#include <prewin.h> +#include <oleidl.h> +#include <postwin.h> +#else +typedef Size SIZEL; +typedef Point POINTL; +#endif + +// this struct conforms to the Microsoft +// OBJECTDESCRIPTOR -> see oleidl.h +// (MS platform sdk) + +namespace { + +struct OleObjectDescriptor +{ + sal_uInt32 cbSize; + ClsId clsid; + sal_uInt32 dwDrawAspect; + SIZEL sizel; + POINTL pointl; + sal_uInt32 dwStatus; + sal_uInt32 dwFullUserTypeName; + sal_uInt32 dwSrcOfCopy; +}; + +#if defined _WIN32 +static_assert(sizeof(OleObjectDescriptor) == sizeof(OBJECTDESCRIPTOR)); +// check the two fields that we use here +static_assert(offsetof(OleObjectDescriptor, dwFullUserTypeName) + == offsetof(OBJECTDESCRIPTOR, dwFullUserTypeName)); +static_assert(offsetof(OleObjectDescriptor, dwSrcOfCopy) + == offsetof(OBJECTDESCRIPTOR, dwSrcOfCopy)); +#endif + +} + +/********************** SvObjectServerList ******************************** +**************************************************************************/ + +const SvObjectServer * SvObjectServerList::Get( std::u16string_view rHumanName ) const +{ + for(const auto & i : aObjectServerList) + { + if( rHumanName == i.GetHumanName() ) + return &i; + } + return nullptr; +} + +const SvObjectServer * SvObjectServerList::Get( const SvGlobalName & rName ) const +{ + for(const auto & i : aObjectServerList) + { + if( rName == i.GetClassName() ) + return &i; + } + return nullptr; +} + +void SvObjectServerList::Remove( const SvGlobalName & rName ) +{ + aObjectServerList.erase(std::remove_if(aObjectServerList.begin(), aObjectServerList.end(), + [rName](const SvObjectServer& rServer) { return rServer.GetClassName() == rName; }), + aObjectServerList.end()); +} + + +void SvObjectServerList::FillInsertObjects() +/* [Description] + + The list is filled with all types which can be selected in the insert-dialog. +*/ +{ + try{ + uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); + + uno::Reference< lang::XMultiServiceFactory > sProviderMSFactory = + configuration::theDefaultProvider::get(xContext); + + uno::Sequence<uno::Any> aArguments(comphelper::InitAnyPropertySequence( + { + {"nodepath", uno::Any(OUString( "/org.openoffice.Office.Embedding/ObjectNames" ))} + })); + uno::Reference< container::XNameAccess > xNameAccess( + sProviderMSFactory->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationAccess", aArguments ), + uno::UNO_QUERY ); + + if( xNameAccess.is()) + { + const uno::Sequence< OUString > seqNames= xNameAccess->getElementNames(); + + OUString aStringProductName( "%PRODUCTNAME" ); + sal_Int32 nStringProductNameLength = aStringProductName.getLength(); + + OUString aStringProductVersion( "%PRODUCTVERSION" ); + sal_Int32 nStringProductVersionLength = aStringProductVersion.getLength(); + + for( const auto& rName : seqNames ) + { + uno::Reference< container::XNameAccess > xEntry ; + xNameAccess->getByName( rName ) >>= xEntry; + if ( xEntry.is() ) + { + OUString aUIName; + OUString aClassID; + xEntry->getByName("ObjectUIName") >>= aUIName; + xEntry->getByName("ClassID") >>= aClassID; + + if ( !aUIName.isEmpty() ) + { + // replace %PRODUCTNAME + sal_Int32 nIndex = aUIName.indexOf( aStringProductName ); + while( nIndex != -1 ) + { + aUIName = aUIName.replaceAt( + nIndex, nStringProductNameLength, + utl::ConfigManager::getProductName() ); + nIndex = aUIName.indexOf( aStringProductName ); + } + + // replace %PRODUCTVERSION + nIndex = aUIName.indexOf( aStringProductVersion ); + while( nIndex != -1 ) + { + aUIName = aUIName.replaceAt( + nIndex, nStringProductVersionLength, + utl::ConfigManager::getProductVersion() ); + nIndex = aUIName.indexOf( aStringProductVersion ); + } + } + + SvGlobalName aClassName; + if( aClassName.MakeId( aClassID) ) + { + if( !Get( aClassName ) ) + // not entered yet + aObjectServerList.emplace_back( aClassName, aUIName ); + } + } + } + } + + +#ifdef _WIN32 + SvGlobalName aOleFact( SO3_OUT_CLASSID ); + OUString aOleObj( SvtResId( STR_FURTHER_OBJECT ) ); + aObjectServerList.push_back( SvObjectServer( aOleFact, aOleObj ) ); +#endif + + }catch(const container::NoSuchElementException&) + { + }catch(const uno::Exception&) + { + } + catch(...) + { + } +} + +OUString SvPasteObjectHelper::GetSotFormatUIName( SotClipboardFormatId nId ) +{ + struct SotResourcePair + { + SotClipboardFormatId mnSotId; + TranslateId mpResId; + }; + + static const SotResourcePair aSotResourcePairs[] = + { + { SotClipboardFormatId::STRING, STR_FORMAT_STRING }, + { SotClipboardFormatId::BITMAP, STR_FORMAT_BITMAP }, + { SotClipboardFormatId::GDIMETAFILE, STR_FORMAT_GDIMETAFILE }, + { SotClipboardFormatId::RTF, STR_FORMAT_RTF }, + { SotClipboardFormatId::DRAWING, STR_FORMAT_ID_DRAWING }, + { SotClipboardFormatId::SVXB, STR_FORMAT_ID_SVXB }, + { SotClipboardFormatId::INTERNALLINK_STATE, STR_FORMAT_ID_INTERNALLINK_STATE }, + { SotClipboardFormatId::SOLK, STR_FORMAT_ID_SOLK }, + { SotClipboardFormatId::NETSCAPE_BOOKMARK, STR_FORMAT_ID_NETSCAPE_BOOKMARK }, + { SotClipboardFormatId::STARSERVER, STR_FORMAT_ID_STARSERVER }, + { SotClipboardFormatId::STAROBJECT, STR_FORMAT_ID_STAROBJECT }, + { SotClipboardFormatId::APPLETOBJECT, STR_FORMAT_ID_APPLETOBJECT }, + { SotClipboardFormatId::PLUGIN_OBJECT, STR_FORMAT_ID_PLUGIN_OBJECT }, + { SotClipboardFormatId::STARWRITER_30, STR_FORMAT_ID_STARWRITER_30 }, + { SotClipboardFormatId::STARWRITER_40, STR_FORMAT_ID_STARWRITER_40 }, + { SotClipboardFormatId::STARWRITER_50, STR_FORMAT_ID_STARWRITER_50 }, + { SotClipboardFormatId::STARWRITERWEB_40, STR_FORMAT_ID_STARWRITERWEB_40 }, + { SotClipboardFormatId::STARWRITERWEB_50, STR_FORMAT_ID_STARWRITERWEB_50 }, + { SotClipboardFormatId::STARWRITERGLOB_40, STR_FORMAT_ID_STARWRITERGLOB_40 }, + { SotClipboardFormatId::STARWRITERGLOB_50, STR_FORMAT_ID_STARWRITERGLOB_50 }, + { SotClipboardFormatId::STARDRAW, STR_FORMAT_ID_STARDRAW }, + { SotClipboardFormatId::STARDRAW_40, STR_FORMAT_ID_STARDRAW_40 }, + { SotClipboardFormatId::STARIMPRESS_50, STR_FORMAT_ID_STARIMPRESS_50 }, + { SotClipboardFormatId::STARDRAW_50, STR_FORMAT_ID_STARDRAW_50 }, + { SotClipboardFormatId::STARCALC, STR_FORMAT_ID_STARCALC }, + { SotClipboardFormatId::STARCALC_40, STR_FORMAT_ID_STARCALC_40 }, + { SotClipboardFormatId::STARCALC_50, STR_FORMAT_ID_STARCALC_50 }, + { SotClipboardFormatId::STARCHART, STR_FORMAT_ID_STARCHART }, + { SotClipboardFormatId::STARCHART_40, STR_FORMAT_ID_STARCHART_40 }, + { SotClipboardFormatId::STARCHART_50, STR_FORMAT_ID_STARCHART_50 }, + { SotClipboardFormatId::STARIMAGE, STR_FORMAT_ID_STARIMAGE }, + { SotClipboardFormatId::STARIMAGE_40, STR_FORMAT_ID_STARIMAGE_40 }, + { SotClipboardFormatId::STARIMAGE_50, STR_FORMAT_ID_STARIMAGE_50 }, + { SotClipboardFormatId::STARMATH, STR_FORMAT_ID_STARMATH }, + { SotClipboardFormatId::STARMATH_40, STR_FORMAT_ID_STARMATH_40 }, + { SotClipboardFormatId::STARMATH_50, STR_FORMAT_ID_STARMATH_50 }, + { SotClipboardFormatId::STAROBJECT_PAINTDOC, STR_FORMAT_ID_STAROBJECT_PAINTDOC }, + { SotClipboardFormatId::HTML, STR_FORMAT_ID_HTML }, + { SotClipboardFormatId::HTML_SIMPLE, STR_FORMAT_ID_HTML_SIMPLE }, + { SotClipboardFormatId::BIFF_5, STR_FORMAT_ID_BIFF_5 }, + { SotClipboardFormatId::BIFF_8, STR_FORMAT_ID_BIFF_8 }, + { SotClipboardFormatId::SYLK, STR_FORMAT_ID_SYLK }, + { SotClipboardFormatId::LINK, STR_FORMAT_ID_LINK }, + { SotClipboardFormatId::DIF, STR_FORMAT_ID_DIF }, + { SotClipboardFormatId::MSWORD_DOC, STR_FORMAT_ID_MSWORD_DOC }, + { SotClipboardFormatId::STAR_FRAMESET_DOC, STR_FORMAT_ID_STAR_FRAMESET_DOC }, + { SotClipboardFormatId::OFFICE_DOC, STR_FORMAT_ID_OFFICE_DOC }, + { SotClipboardFormatId::NOTES_DOCINFO, STR_FORMAT_ID_NOTES_DOCINFO }, + { SotClipboardFormatId::SFX_DOC, STR_FORMAT_ID_SFX_DOC }, + { SotClipboardFormatId::STARCHARTDOCUMENT_50,STR_FORMAT_ID_STARCHARTDOCUMENT_50 }, + { SotClipboardFormatId::GRAPHOBJ, STR_FORMAT_ID_GRAPHOBJ }, + { SotClipboardFormatId::STARWRITER_60, STR_FORMAT_ID_STARWRITER_60 }, + { SotClipboardFormatId::STARWRITERWEB_60, STR_FORMAT_ID_STARWRITERWEB_60 }, + { SotClipboardFormatId::STARWRITERGLOB_60, STR_FORMAT_ID_STARWRITERGLOB_60 }, + { SotClipboardFormatId::STARDRAW_60, STR_FORMAT_ID_STARDRAW_60 }, + { SotClipboardFormatId::STARIMPRESS_60, STR_FORMAT_ID_STARIMPRESS_60 }, + { SotClipboardFormatId::STARCALC_60, STR_FORMAT_ID_STARCALC_60 }, + { SotClipboardFormatId::STARCHART_60, STR_FORMAT_ID_STARCHART_60 }, + { SotClipboardFormatId::STARMATH_60, STR_FORMAT_ID_STARMATH_60 }, + { SotClipboardFormatId::WMF, STR_FORMAT_ID_WMF }, + { SotClipboardFormatId::DBACCESS_QUERY, STR_FORMAT_ID_DBACCESS_QUERY }, + { SotClipboardFormatId::DBACCESS_TABLE, STR_FORMAT_ID_DBACCESS_TABLE }, + { SotClipboardFormatId::DBACCESS_COMMAND, STR_FORMAT_ID_DBACCESS_COMMAND }, + { SotClipboardFormatId::DIALOG_60, STR_FORMAT_ID_DIALOG_60 }, + { SotClipboardFormatId::FILEGRPDESCRIPTOR, STR_FORMAT_ID_FILEGRPDESCRIPTOR }, + { SotClipboardFormatId::HTML_NO_COMMENT, STR_FORMAT_ID_HTML_NO_COMMENT }, + { SotClipboardFormatId::RICHTEXT, STR_FORMAT_ID_RICHTEXT }, + { SotClipboardFormatId::STRING_TSVC, STR_FORMAT_ID_STRING_TSVC }, + { SotClipboardFormatId::PNG, STR_FORMAT_ID_PNG_BITMAP }, + }; + + TranslateId pResId; + + sal_uInt32 const nCount = SAL_N_ELEMENTS( aSotResourcePairs ); + for (sal_uInt32 i = 0; ( i < nCount ) && !pResId; ++i) + { + if( aSotResourcePairs[ i ].mnSotId == nId ) + pResId = aSotResourcePairs[ i ].mpResId; + } + + OUString aUIName; + if (pResId) + aUIName = SvtResId(pResId); + else + aUIName = SotExchange::GetFormatName( nId ); + + return aUIName; +} + +bool SvPasteObjectHelper::GetEmbeddedName(const TransferableDataHelper& rData, OUString& _rName, OUString& _rSource, SotClipboardFormatId const & _nFormat) +{ + if( _nFormat != SotClipboardFormatId::EMBED_SOURCE_OLE && _nFormat != SotClipboardFormatId::EMBEDDED_OBJ_OLE ) + return false; + + datatransfer::DataFlavor aFlavor; + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::OBJECTDESCRIPTOR_OLE, aFlavor ); + + if( !rData.HasFormat( aFlavor ) ) + return false; + + uno::Any aAny = rData.GetAny(aFlavor, OUString()); + if (!aAny.hasValue()) + return false; + + uno::Sequence< sal_Int8 > anySequence; + aAny >>= anySequence; + + OleObjectDescriptor* pOleObjDescr = + reinterpret_cast< OleObjectDescriptor* >( anySequence.getArray( ) ); + + // determine the user friendly description of the embedded object + if ( pOleObjDescr->dwFullUserTypeName ) + { + // we set the pointer to the start of user friendly description + // string. it starts at &OleObjectDescriptor + dwFullUserTypeName. + // dwFullUserTypeName is the offset in bytes. + // the user friendly description string is '\0' terminated. + const sal_Unicode* pUserTypeName = + reinterpret_cast< sal_Unicode* >( + reinterpret_cast< char* >( pOleObjDescr ) + + pOleObjDescr->dwFullUserTypeName ); + + _rName += pUserTypeName; + // the following statement was here for historical reasons, it is commented out since it causes bug i49460 + // _nFormat = SotClipboardFormatId::EMBED_SOURCE_OLE; + } + + // determine the source of the embedded object + if ( pOleObjDescr->dwSrcOfCopy ) + { + // we set the pointer to the start of source string + // it starts at &OleObjectDescriptor + dwSrcOfCopy. + // dwSrcOfCopy is the offset in bytes. + // the source string is '\0' terminated. + const sal_Unicode* pSrcOfCopy = + reinterpret_cast< sal_Unicode* >( + reinterpret_cast< char* >( pOleObjDescr ) + + pOleObjDescr->dwSrcOfCopy ); + + _rSource += pSrcOfCopy; + } + else + _rSource = SvtResId(STR_UNKNOWN_SOURCE); + + return true; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/dialogs/prnsetup.cxx b/svtools/source/dialogs/prnsetup.cxx new file mode 100644 index 000000000..60dba4348 --- /dev/null +++ b/svtools/source/dialogs/prnsetup.cxx @@ -0,0 +1,351 @@ +/* -*- 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 <string_view> + +#include <svtools/prnsetup.hxx> +#include <svtools/strings.hrc> +#include <svtools/svtresid.hxx> +#include <vcl/QueueInfo.hxx> +#include <vcl/svapp.hxx> +#include <vcl/print.hxx> +#include <vcl/event.hxx> +#include <sal/log.hxx> + +void ImplFillPrnDlgListBox( const Printer* pPrinter, + weld::ComboBox* pBox, weld::Button* pPropBtn ) +{ + ImplFreePrnDlgListBox( pBox ); + + const std::vector<OUString>& rPrinters = Printer::GetPrinterQueues(); + unsigned int nCount = rPrinters.size(); + if ( nCount ) + { + for( unsigned int i = 0; i < nCount; i++ ) + pBox->append_text( rPrinters[i] ); + pBox->set_active_text(pPrinter->GetName()); + } + + pBox->set_sensitive(nCount != 0); + pPropBtn->set_visible( pPrinter->HasSupport( PrinterSupport::SetupDialog ) ); +} + + +void ImplFreePrnDlgListBox( weld::ComboBox* pBox, bool bClear ) +{ + if ( bClear ) + pBox->clear(); +} + + +Printer* ImplPrnDlgListBoxSelect( const weld::ComboBox* pBox, weld::Button* pPropBtn, + Printer const * pPrinter, Printer* pTempPrinterIn ) +{ + VclPtr<Printer> pTempPrinter( pTempPrinterIn ); + if ( pBox->get_active() != -1 ) + { + const QueueInfo* pInfo = Printer::GetQueueInfo( pBox->get_active_text(), true ); + if( pInfo) + { + if ( !pTempPrinter ) + { + if ( (pPrinter->GetName() == pInfo->GetPrinterName()) && + (pPrinter->GetDriverName() == pInfo->GetDriver()) ) + pTempPrinter = VclPtr<Printer>::Create( pPrinter->GetJobSetup() ); + else + pTempPrinter = VclPtr<Printer>::Create( *pInfo ); + } + else + { + if ( (pTempPrinter->GetName() != pInfo->GetPrinterName()) || + (pTempPrinter->GetDriverName() != pInfo->GetDriver()) ) + { + pTempPrinter.disposeAndClear(); + pTempPrinter = VclPtr<Printer>::Create( *pInfo ); + } + } + + pPropBtn->set_sensitive(pTempPrinter->HasSupport(PrinterSupport::SetupDialog)); + } + else + pPropBtn->set_sensitive(false); + } + else + pPropBtn->set_sensitive(false); + + return pTempPrinter; +} + + +Printer* ImplPrnDlgUpdatePrinter( Printer const * pPrinter, Printer* pTempPrinterIn ) +{ + VclPtr<Printer> pTempPrinter( pTempPrinterIn ); + OUString aPrnName; + if ( pTempPrinter ) + aPrnName = pTempPrinter->GetName(); + else + aPrnName = pPrinter->GetName(); + + if ( ! Printer::GetQueueInfo( aPrnName, false ) ) + { + pTempPrinter.disposeAndClear(); + pTempPrinter = VclPtr<Printer>::Create(); + } + + return pTempPrinter; +} + + +void ImplPrnDlgUpdateQueueInfo( const weld::ComboBox* pBox, QueueInfo& rInfo ) +{ + if ( pBox->get_active() != -1 ) + { + const QueueInfo* pInfo = Printer::GetQueueInfo( pBox->get_active_text(), true ); + if( pInfo ) + rInfo = *pInfo; + } +} + + +static OUString ImplPrnDlgAddString(const OUString& rStr, std::u16string_view rAddStr) +{ + OUString aStr(rStr); + if (!aStr.isEmpty()) + aStr += "; " ; + return aStr + rAddStr; +} + + +static OUString ImplPrnDlgAddResString(const OUString& rStr, TranslateId pResId) +{ + return ImplPrnDlgAddString(rStr, SvtResId(pResId)); +} + + +OUString ImplPrnDlgGetStatusText( const QueueInfo& rInfo ) +{ + OUString aStr; + PrintQueueFlags nStatus = rInfo.GetStatus(); + + // Default-Printer + if ( !rInfo.GetPrinterName().isEmpty() && + (rInfo.GetPrinterName() == Printer::GetDefaultPrinterName()) ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_DEFPRINTER ); + + // Status + if ( nStatus & PrintQueueFlags::Ready ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_READY ); + if ( nStatus & PrintQueueFlags::Paused ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_PAUSED ); + if ( nStatus & PrintQueueFlags::PendingDeletion ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_PENDING ); + if ( nStatus & PrintQueueFlags::Busy ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_BUSY ); + if ( nStatus & PrintQueueFlags::Initializing ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_INITIALIZING ); + if ( nStatus & PrintQueueFlags::Waiting ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_WAITING ); + if ( nStatus & PrintQueueFlags::WarmingUp ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_WARMING_UP ); + if ( nStatus & PrintQueueFlags::Processing ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_PROCESSING ); + if ( nStatus & PrintQueueFlags::Printing ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_PRINTING ); + if ( nStatus & PrintQueueFlags::Offline ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_OFFLINE ); + if ( nStatus & PrintQueueFlags::Error ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_ERROR ); + if ( nStatus & PrintQueueFlags::StatusUnknown ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_SERVER_UNKNOWN ); + if ( nStatus & PrintQueueFlags::PaperJam ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_PAPER_JAM ); + if ( nStatus & PrintQueueFlags::PaperOut ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_PAPER_OUT ); + if ( nStatus & PrintQueueFlags::ManualFeed ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_MANUAL_FEED ); + if ( nStatus & PrintQueueFlags::PaperProblem ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_PAPER_PROBLEM ); + if ( nStatus & PrintQueueFlags::IOActive ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_IO_ACTIVE ); + if ( nStatus & PrintQueueFlags::OutputBinFull ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_OUTPUT_BIN_FULL ); + if ( nStatus & PrintQueueFlags::TonerLow ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_TONER_LOW ); + if ( nStatus & PrintQueueFlags::NoToner ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_NO_TONER ); + if ( nStatus & PrintQueueFlags::PagePunt ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_PAGE_PUNT ); + if ( nStatus & PrintQueueFlags::UserIntervention ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_USER_INTERVENTION ); + if ( nStatus & PrintQueueFlags::OutOfMemory ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_OUT_OF_MEMORY ); + if ( nStatus & PrintQueueFlags::DoorOpen ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_DOOR_OPEN ); + if ( nStatus & PrintQueueFlags::PowerSave ) + aStr = ImplPrnDlgAddResString( aStr, STR_SVT_PRNDLG_POWER_SAVE ); + + // Number of jobs + sal_uInt32 nJobs = rInfo.GetJobs(); + if ( nJobs && (nJobs != QUEUE_JOBS_DONTKNOW) ) + { + OUString aJobStr( SvtResId( STR_SVT_PRNDLG_JOBCOUNT ) ); + OUString aJobs( OUString::number( nJobs ) ); + aStr = ImplPrnDlgAddString(aStr, aJobStr.replaceAll("%d", aJobs)); + } + + return aStr; +} + +PrinterSetupDialog::PrinterSetupDialog(weld::Window* pParent) + : GenericDialogController(pParent, "svt/ui/printersetupdialog.ui", "PrinterSetupDialog") + , m_xLbName(m_xBuilder->weld_combo_box("name")) + , m_xBtnProperties(m_xBuilder->weld_button("properties")) + , m_xBtnOptions(m_xBuilder->weld_button("options")) + , m_xFiStatus(m_xBuilder->weld_label("status")) + , m_xFiType(m_xBuilder->weld_label("type")) + , m_xFiLocation(m_xBuilder->weld_label("location")) + , m_xFiComment(m_xBuilder->weld_label("comment")) + , maStatusTimer("PrinterSetupDialog maStatusTimer") +{ + m_xLbName->make_sorted(); + + // show options button only if link is set + m_xBtnOptions->hide(); + + mpPrinter = nullptr; + mpTempPrinter = nullptr; + + maStatusTimer.SetTimeout( IMPL_PRINTDLG_STATUS_UPDATE ); + maStatusTimer.SetInvokeHandler( LINK( this, PrinterSetupDialog, ImplStatusHdl ) ); + m_xBtnProperties->connect_clicked( LINK( this, PrinterSetupDialog, ImplPropertiesHdl ) ); + m_xLbName->connect_changed( LINK( this, PrinterSetupDialog, ImplChangePrinterHdl ) ); + m_xDialog->connect_focus_in( LINK( this, PrinterSetupDialog, ImplGetFocusHdl ) ); + Application::AddEventListener(LINK( this, PrinterSetupDialog, ImplDataChangedHdl ) ); +} + +PrinterSetupDialog::~PrinterSetupDialog() +{ + Application::RemoveEventListener(LINK( this, PrinterSetupDialog, ImplDataChangedHdl ) ); + ImplFreePrnDlgListBox(m_xLbName.get(), false); +} + +void PrinterSetupDialog::SetOptionsHdl(const Link<weld::Button&, void>& rLink) +{ + m_xBtnOptions->connect_clicked(rLink); + m_xBtnOptions->set_accessible_description(SvtResId(STR_A11Y_DESC_OPTIONS)); + m_xBtnOptions->set_visible(rLink.IsSet()); +} + +void PrinterSetupDialog::ImplSetInfo() +{ + const QueueInfo* pInfo = Printer::GetQueueInfo(m_xLbName->get_active_text(), true); + if ( pInfo ) + { + m_xFiType->set_label( pInfo->GetDriver() ); + m_xFiLocation->set_label( pInfo->GetLocation() ); + m_xFiComment->set_label( pInfo->GetComment() ); + m_xFiStatus->set_label( ImplPrnDlgGetStatusText( *pInfo ) ); + } + else + { + const OUString aTempStr; + m_xFiType->set_label( aTempStr ); + m_xFiLocation->set_label( aTempStr ); + m_xFiComment->set_label( aTempStr ); + m_xFiStatus->set_label( aTempStr ); + } +} + +IMPL_LINK_NOARG(PrinterSetupDialog, ImplStatusHdl, Timer *, void) +{ + QueueInfo aInfo; + ImplPrnDlgUpdateQueueInfo(m_xLbName.get(), aInfo); + m_xFiStatus->set_label( ImplPrnDlgGetStatusText( aInfo ) ); +} + + +IMPL_LINK_NOARG(PrinterSetupDialog, ImplPropertiesHdl, weld::Button&, void) +{ + if ( !mpTempPrinter ) + mpTempPrinter = VclPtr<Printer>::Create( mpPrinter->GetJobSetup() ); + mpTempPrinter->Setup(m_xDialog.get()); +} + +IMPL_LINK_NOARG(PrinterSetupDialog, ImplChangePrinterHdl, weld::ComboBox&, void) +{ + mpTempPrinter = ImplPrnDlgListBoxSelect(m_xLbName.get(), m_xBtnProperties.get(), + mpPrinter, mpTempPrinter); + ImplSetInfo(); +} + +IMPL_LINK(PrinterSetupDialog, ImplGetFocusHdl, weld::Widget&, rWidget, void) +{ + if (rWidget.is_visible()) + ImplStatusHdl(&maStatusTimer); +} + +IMPL_LINK(PrinterSetupDialog, ImplDataChangedHdl, VclSimpleEvent&, rEvt, void) +{ + VclEventId nEvent = rEvt.GetId(); + if (nEvent != VclEventId::ApplicationDataChanged) + return; + + DataChangedEvent* pData = static_cast<DataChangedEvent*>(static_cast<VclWindowEvent&>(rEvt).GetData()); + if (!pData || pData->GetType() != DataChangedEventType::PRINTER) + return; + + mpTempPrinter = ImplPrnDlgUpdatePrinter(mpPrinter, mpTempPrinter); + Printer* pPrn; + if (mpTempPrinter) + pPrn = mpTempPrinter; + else + pPrn = mpPrinter; + ImplFillPrnDlgListBox(pPrn, m_xLbName.get(), m_xBtnProperties.get()); + ImplSetInfo(); +} + +short PrinterSetupDialog::run() +{ + if ( !mpPrinter || mpPrinter->IsPrinting() || mpPrinter->IsJobActive() ) + { + SAL_WARN( "svtools.dialogs", "PrinterSetupDialog::execute() - No Printer or printer is printing" ); + return RET_CANCEL; + } + + Printer::updatePrinters(); + + ImplFillPrnDlgListBox(mpPrinter, m_xLbName.get(), m_xBtnProperties.get()); + ImplSetInfo(); + maStatusTimer.Start(); + + // start dialog + short nRet = GenericDialogController::run(); + + // update data if the dialog was terminated with OK + if ( nRet == RET_OK && mpTempPrinter ) + mpPrinter->SetPrinterProps( mpTempPrinter ); + + maStatusTimer.Stop(); + + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/dialogs/restartdialog.cxx b/svtools/source/dialogs/restartdialog.cxx new file mode 100644 index 000000000..30954f176 --- /dev/null +++ b/svtools/source/dialogs/restartdialog.cxx @@ -0,0 +1,124 @@ +/* -*- 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 <sal/config.h> + +#include <cassert> + +#include <com/sun/star/task/OfficeRestartManager.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <svtools/restartdialog.hxx> +#include <tools/link.hxx> +#include <vcl/weld.hxx> + +namespace { + +class RestartDialog : public weld::GenericDialogController{ +public: + RestartDialog(weld::Window* parent, svtools::RestartReason reason) + : GenericDialogController(parent, "svt/ui/restartdialog.ui", "RestartDialog") + , btnYes_(m_xBuilder->weld_button("yes")) + , btnNo_(m_xBuilder->weld_button("no")) + { + switch (reason) { + case svtools::RESTART_REASON_JAVA: + reason_ = m_xBuilder->weld_widget("reason_java"); + break; + case svtools::RESTART_REASON_PDF_AS_STANDARD_JOB_FORMAT: + reason_ = m_xBuilder->weld_widget("reason_pdf"); + break; + case svtools::RESTART_REASON_BIBLIOGRAPHY_INSTALL: + reason_ = m_xBuilder->weld_widget("reason_bibliography_install"); + break; + case svtools::RESTART_REASON_MAILMERGE_INSTALL: + reason_ = m_xBuilder->weld_widget("reason_mailmerge_install"); + break; + case svtools::RESTART_REASON_LANGUAGE_CHANGE: + reason_ = m_xBuilder->weld_widget("reason_language_change"); + break; + case svtools::RESTART_REASON_ADDING_PATH: + reason_ = m_xBuilder->weld_widget("reason_adding_path"); + break; + case svtools::RESTART_REASON_ASSIGNING_JAVAPARAMETERS: + reason_ = m_xBuilder->weld_widget("reason_assigning_javaparameters"); + break; + case svtools::RESTART_REASON_ASSIGNING_FOLDERS: + reason_ = m_xBuilder->weld_widget("reason_assigning_folders"); + break; + case svtools::RESTART_REASON_EXP_FEATURES: + reason_ = m_xBuilder->weld_widget("reason_exp_features"); + break; + case svtools::RESTART_REASON_EXTENSION_INSTALL: + reason_ = m_xBuilder->weld_widget("reason_extension_install"); + break; + case svtools::RESTART_REASON_SKIA: + reason_ = m_xBuilder->weld_widget("reason_skia"); + break; + case svtools::RESTART_REASON_OPENCL: + reason_ = m_xBuilder->weld_widget("reason_opencl"); + break; + case svtools::RESTART_REASON_THREADING: + reason_ = m_xBuilder->weld_widget("reason_threading"); + break; + case svtools::RESTART_REASON_MSCOMPATIBLE_FORMS_MENU: + reason_ = m_xBuilder->weld_widget("reason_mscompatible_formsmenu"); + break; + case svtools::RESTART_REASON_SAVE: + reason_ = m_xBuilder->weld_widget("reason_save"); + break; + case svtools::RESTART_REASON_UI_CHANGE: + reason_ = m_xBuilder->weld_widget("reason_uichange"); + break; + default: + assert(false); // this cannot happen + } + reason_->show(); + btnYes_->connect_clicked(LINK(this, RestartDialog, hdlYes)); + btnNo_->connect_clicked(LINK(this, RestartDialog, hdlNo)); + } +private: + DECL_LINK(hdlYes, weld::Button&, void); + DECL_LINK(hdlNo, weld::Button&, void); + + std::unique_ptr<weld::Widget> reason_; + std::unique_ptr<weld::Button> btnYes_; + std::unique_ptr<weld::Button> btnNo_; +}; + +IMPL_LINK_NOARG(RestartDialog, hdlYes, weld::Button&, void) +{ + m_xDialog->response(RET_OK); +} + +IMPL_LINK_NOARG(RestartDialog, hdlNo, weld::Button&, void) +{ + m_xDialog->response(RET_CANCEL); +} + +} + +bool svtools::executeRestartDialog( + css::uno::Reference< css::uno::XComponentContext > const & context, + weld::Window* parent, RestartReason reason) +{ + auto xRestartManager = css::task::OfficeRestartManager::get(context); + if (xRestartManager->isRestartRequested(false)) + return true; // don't try to show another dialog when restart is already in progress + RestartDialog aDlg(parent, reason); + if (aDlg.run()) { + xRestartManager->requestRestart( + css::uno::Reference< css::task::XInteractionHandler >()); + return true; + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/filter/DocumentToGraphicRenderer.cxx b/svtools/source/filter/DocumentToGraphicRenderer.cxx new file mode 100644 index 000000000..c2858a1da --- /dev/null +++ b/svtools/source/filter/DocumentToGraphicRenderer.cxx @@ -0,0 +1,340 @@ +/* -*- 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 <svtools/DocumentToGraphicRenderer.hxx> + +#include <comphelper/propertyvalue.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/svapp.hxx> +#include <vcl/outdev.hxx> +#include <vcl/pdfextoutdevdata.hxx> + +#include <tools/fract.hxx> + +#include <com/sun/star/awt/XDevice.hpp> +#include <com/sun/star/awt/XToolkit.hpp> +#include <com/sun/star/text/XPageCursor.hpp> +#include <com/sun/star/text/XTextViewCursorSupplier.hpp> +#include <com/sun/star/view/XRenderable.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> +#include <com/sun/star/beans/PropertyValues.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/frame/XModel.hpp> + +#include <toolkit/helper/vclunohelper.hxx> + +using namespace css; +using namespace css::uno; +using namespace css::lang; +using namespace css::beans; + +DocumentToGraphicRenderer::DocumentToGraphicRenderer( const Reference<XComponent>& rxDocument, bool bSelectionOnly ) : + mxDocument(rxDocument), + mxModel( mxDocument, uno::UNO_QUERY ), + mxController( mxModel->getCurrentController() ), + mxRenderable (mxDocument, uno::UNO_QUERY ), + mxToolkit( VCLUnoHelper::CreateToolkit() ), + meDocType( UNKNOWN ) +{ + try + { + uno::Reference< lang::XServiceInfo > xServiceInfo( mxDocument, uno::UNO_QUERY); + if (xServiceInfo.is()) + { + if (xServiceInfo->supportsService("com.sun.star.text.TextDocument")) + meDocType = WRITER; + else if (xServiceInfo->supportsService("com.sun.star.sheet.SpreadsheetDocument")) + meDocType = CALC; + else if (xServiceInfo->supportsService("com.sun.star.presentation.PresentationDocument")) + meDocType = IMPRESS; + else + meDocType = UNKNOWN; + } + } + catch (const uno::Exception&) + { + } + + if (!(bSelectionOnly && mxController.is())) + return; + + try + { + uno::Reference< view::XSelectionSupplier > xSelSup( mxController, uno::UNO_QUERY); + if (xSelSup.is()) + { + uno::Any aViewSelection( xSelSup->getSelection()); + if (aViewSelection.hasValue()) + { + /* FIXME: Writer always has a selection even if nothing is + * selected, but passing a selection to + * XRenderable::render() it always renders an empty page. + * So disable the selection already here. The current page + * the cursor is on is rendered. */ + if (!isWriter()) + maSelection = aViewSelection; + } + } + } + catch (const uno::Exception&) + { + } +} + +DocumentToGraphicRenderer::~DocumentToGraphicRenderer() +{ +} + +Size DocumentToGraphicRenderer::getDocumentSizeInPixels(sal_Int32 nCurrentPage) +{ + Size aSize100mm = getDocumentSizeIn100mm(nCurrentPage); + return Application::GetDefaultDevice()->LogicToPixel(aSize100mm, MapMode(MapUnit::Map100thMM)); +} + +bool DocumentToGraphicRenderer::hasSelection() const +{ + return maSelection.hasValue(); +} + +uno::Any DocumentToGraphicRenderer::getSelection() const +{ + uno::Any aSelection; + if (hasSelection()) + aSelection = maSelection; + else + aSelection <<= mxDocument; // default: render whole document + return aSelection; +} + +Size DocumentToGraphicRenderer::getDocumentSizeIn100mm(sal_Int32 nCurrentPage, + Point* pDocumentPosition, Point* pCalcPagePosition, Size* pCalcPageSize) +{ + Reference< awt::XDevice > xDevice(mxToolkit->createScreenCompatibleDevice( 32, 32 ) ); + + uno::Any selection( getSelection()); + + PropertyValues renderProperties{ comphelper::makePropertyValue("IsPrinter", true), + comphelper::makePropertyValue("RenderDevice", xDevice), + comphelper::makePropertyValue("View", mxController), + comphelper::makePropertyValue("RenderToGraphic", true) }; + + awt::Size aSize; + awt::Size aCalcPageSize; + awt::Point aPos; + awt::Point aCalcPos; + + sal_Int32 nPages = mxRenderable->getRendererCount( selection, renderProperties ); + if (nPages >= nCurrentPage) + { + const Sequence< beans::PropertyValue > aResult = mxRenderable->getRenderer(nCurrentPage - 1, selection, renderProperties ); + for( const auto& rProperty : aResult ) + { + if ( rProperty.Name == "PageSize" ) + { + rProperty.Value >>= aSize; + } + else if (rProperty.Name == "PagePos") + { + rProperty.Value >>= aPos; + } + else if (rProperty.Name == "CalcPagePos") + { + rProperty.Value >>= aCalcPos; + } + else if (rProperty.Name == "CalcPageContentSize") + { + rProperty.Value >>= aCalcPageSize; + } + } + } + + if (pDocumentPosition) + { + *pDocumentPosition = Point(aPos.X, aPos.Y); + } + if (pCalcPagePosition) + { + *pCalcPagePosition = Point(aCalcPos.X, aCalcPos.Y); + } + if (pCalcPageSize) + { + *pCalcPageSize = Size(aCalcPageSize.Width, aCalcPageSize.Height); + } + + return Size( aSize.Width, aSize.Height ); +} + +Graphic DocumentToGraphicRenderer::renderToGraphic( + sal_Int32 nCurrentPage, + Size aDocumentSizePixel, + Size aTargetSizePixel, + Color aPageColor, + bool bExtOutDevData) + +{ + if (!mxModel.is() || !mxController.is() || !mxRenderable.is()) + return Graphic(); + + Reference< awt::XDevice > xDevice(mxToolkit->createScreenCompatibleDevice( aTargetSizePixel.Width(), aTargetSizePixel.Height() ) ); + if (!xDevice.is()) + return Graphic(); + + assert( !aDocumentSizePixel.IsEmpty() && !aTargetSizePixel.IsEmpty()); + + double fScaleX = aTargetSizePixel.Width() / static_cast<double>(aDocumentSizePixel.Width()); + double fScaleY = aTargetSizePixel.Height() / static_cast<double>(aDocumentSizePixel.Height()); + + PropertyValues renderProps{ + comphelper::makePropertyValue("IsPrinter", true), + comphelper::makePropertyValue("RenderDevice", xDevice), + comphelper::makePropertyValue("View", mxController), + comphelper::makePropertyValue("RenderToGraphic", true), + comphelper::makePropertyValue("HasPDFExtOutDevData", bExtOutDevData), + comphelper::makePropertyValue("PageRange", OUString::number(nCurrentPage)) + }; + + GDIMetaFile aMtf; + + OutputDevice* pOutputDev = VCLUnoHelper::GetOutputDevice( xDevice ); + + vcl::PDFExtOutDevData aPDFExtOutDevData(*pOutputDev); + if (bExtOutDevData) + { + aPDFExtOutDevData.SetIsExportBookmarks(true); + pOutputDev->SetExtOutDevData(&aPDFExtOutDevData); + } + + pOutputDev->SetAntialiasing(pOutputDev->GetAntialiasing() | AntialiasingFlags::Enable); + MapMode mm = pOutputDev->GetMapMode(); + mm.SetScaleX( Fraction(fScaleX) ); + mm.SetScaleY( Fraction(fScaleY) ); + pOutputDev->SetMapMode( mm ); + + aMtf.Record( pOutputDev ); + + if (aPageColor != COL_TRANSPARENT) + { + pOutputDev->SetBackground(Wallpaper(aPageColor)); + pOutputDev->Erase(); + } + + uno::Any aSelection( getSelection()); + mxRenderable->render(nCurrentPage - 1, aSelection, renderProps ); + + aMtf.Stop(); + aMtf.WindStart(); + aMtf.SetPrefSize( aTargetSizePixel ); + + if (bExtOutDevData) + maChapterNames = aPDFExtOutDevData.GetChapterNames(); + + return Graphic(aMtf); +} + +const std::vector<OUString>& DocumentToGraphicRenderer::getChapterNames() const +{ + return maChapterNames; +} + +sal_Int32 DocumentToGraphicRenderer::getCurrentPage() +{ + if (hasSelection()) + return 1; + + if (isWriter()) + return getCurrentPageWriter(); + + /* TODO: other application specific page detection? */ + return 1; +} + +sal_Int32 DocumentToGraphicRenderer::getPageCount() +{ + Reference< awt::XDevice > xDevice(mxToolkit->createScreenCompatibleDevice( 32, 32 ) ); + + uno::Any selection( getSelection() ); + + PropertyValues renderProperties{ comphelper::makePropertyValue("IsPrinter", true), + comphelper::makePropertyValue("RenderDevice", xDevice), + comphelper::makePropertyValue("View", mxController), + comphelper::makePropertyValue("RenderToGraphic", true) }; + + sal_Int32 nPages = mxRenderable->getRendererCount( selection, renderProperties ); + + return nPages; +} + +sal_Int32 DocumentToGraphicRenderer::getCurrentPageWriter() +{ + Reference<text::XTextViewCursorSupplier> xTextViewCursorSupplier(mxModel->getCurrentController(), UNO_QUERY); + if (!xTextViewCursorSupplier.is()) + return 1; + Reference<text::XPageCursor> xCursor(xTextViewCursorSupplier->getViewCursor(), UNO_QUERY); + return xCursor.is() ? xCursor->getPage() : 1; +} + +// static +bool DocumentToGraphicRenderer::isShapeSelected( + css::uno::Reference< css::drawing::XShapes > & rxShapes, + css::uno::Reference< css::drawing::XShape > & rxShape, + const css::uno::Reference< css::frame::XController > & rxController ) +{ + bool bShape = false; + if (rxController.is()) + { + uno::Reference< view::XSelectionSupplier > xSelectionSupplier( rxController, uno::UNO_QUERY); + if (xSelectionSupplier.is()) + { + uno::Any aAny( xSelectionSupplier->getSelection()); + if (aAny >>= rxShapes) + bShape = true; + else if (aAny >>= rxShape) + bShape = true; + } + } + return bShape; +} + +bool DocumentToGraphicRenderer::isWriter() const +{ + if (meDocType == WRITER) + return true; + else + return false; +} + +bool DocumentToGraphicRenderer::isCalc() const +{ + if (meDocType == CALC) + return true; + else + return false; +} + +bool DocumentToGraphicRenderer::isImpress() const +{ + if (meDocType == IMPRESS) + return true; + else + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/filter/SvFilterOptionsDialog.cxx b/svtools/source/filter/SvFilterOptionsDialog.cxx new file mode 100644 index 000000000..5d0926ace --- /dev/null +++ b/svtools/source/filter/SvFilterOptionsDialog.cxx @@ -0,0 +1,290 @@ +/* -*- 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/FilterConfigItem.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/svapp.hxx> +#include <FltCallDialogParameter.hxx> +#include "exportdialog.hxx" +#include <tools/fldunit.hxx> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/beans/XPropertyAccess.hpp> +#include <com/sun/star/document/XExporter.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/uno/Any.h> +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <unotools/localedatawrapper.hxx> +#include <unotools/syslocale.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> + +using namespace ::com::sun::star; + +namespace { + +class SvFilterOptionsDialog : public cppu::WeakImplHelper +< + document::XExporter, + ui::dialogs::XExecutableDialog, + beans::XPropertyAccess, + lang::XInitialization, + lang::XServiceInfo +> +{ + const uno::Reference< uno::XComponentContext > + mxContext; + uno::Sequence< beans::PropertyValue > + maMediaDescriptor; + uno::Sequence< beans::PropertyValue > + maFilterDataSequence; + uno::Reference< lang::XComponent > + mxSourceDocument; + + css::uno::Reference<css::awt::XWindow> mxParent; + FieldUnit meFieldUnit; + bool mbExportSelection; + bool mbGraphicsSource; + +public: + + explicit SvFilterOptionsDialog( const uno::Reference< uno::XComponentContext >& _rxORB ); + + // XInterface + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + + // XInitialization + virtual void SAL_CALL initialize( const uno::Sequence< uno::Any > & aArguments ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XPropertyAccess + virtual uno::Sequence< beans::PropertyValue > SAL_CALL getPropertyValues() override; + virtual void SAL_CALL setPropertyValues( const uno::Sequence< beans::PropertyValue > & aProps ) override; + + // XExecuteDialog + virtual sal_Int16 SAL_CALL execute() override; + virtual void SAL_CALL setTitle( const OUString& aTitle ) override; + + // XExporter + virtual void SAL_CALL setSourceDocument( const uno::Reference< lang::XComponent >& xDoc ) override; + +}; + +SvFilterOptionsDialog::SvFilterOptionsDialog( const uno::Reference< uno::XComponentContext >& rxContext ) : + mxContext ( rxContext ), + meFieldUnit ( FieldUnit::CM ), + mbExportSelection ( false ), + mbGraphicsSource ( true ) +{ +} + +void SAL_CALL SvFilterOptionsDialog::acquire() noexcept +{ + OWeakObject::acquire(); +} + + +void SAL_CALL SvFilterOptionsDialog::release() noexcept +{ + OWeakObject::release(); +} + +// XInitialization +void SAL_CALL SvFilterOptionsDialog::initialize(const uno::Sequence<uno::Any>& rArguments) +{ + for(const uno::Any& rArgument : rArguments) + { + beans::PropertyValue aProperty; + if (rArgument >>= aProperty) + { + if( aProperty.Name == "ParentWindow" ) + { + aProperty.Value >>= mxParent; + } + } + } +} + +// XServiceInfo +OUString SAL_CALL SvFilterOptionsDialog::getImplementationName() +{ + return "com.sun.star.svtools.SvFilterOptionsDialog"; +} +sal_Bool SAL_CALL SvFilterOptionsDialog::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} +uno::Sequence< OUString > SAL_CALL SvFilterOptionsDialog::getSupportedServiceNames() +{ + return { "com.sun.star.ui.dialogs.FilterOptionsDialog" }; +} + +// XPropertyAccess +uno::Sequence< beans::PropertyValue > SvFilterOptionsDialog::getPropertyValues() +{ + auto pProp = std::find_if(std::cbegin(maMediaDescriptor), std::cend(maMediaDescriptor), + [](const beans::PropertyValue& rProp) { return rProp.Name == "FilterData"; }); + auto i = static_cast<sal_Int32>(std::distance(std::cbegin(maMediaDescriptor), pProp)); + sal_Int32 nCount = maMediaDescriptor.getLength(); + if ( i == nCount ) + maMediaDescriptor.realloc( ++nCount ); + + // the "FilterData" Property is an Any that will contain our PropertySequence of Values + auto& item = maMediaDescriptor.getArray()[ i ]; + item.Name = "FilterData"; + item.Value <<= maFilterDataSequence; + return maMediaDescriptor; +} + +void SvFilterOptionsDialog::setPropertyValues( const uno::Sequence< beans::PropertyValue > & aProps ) +{ + maMediaDescriptor = aProps; + + for ( const auto& rProp : std::as_const(maMediaDescriptor) ) + { + if ( rProp.Name == "FilterData" ) + { + rProp.Value >>= maFilterDataSequence; + } + else if ( rProp.Name == "SelectionOnly" ) + { + rProp.Value >>= mbExportSelection; + } + } +} + +// XExecutableDialog +void SvFilterOptionsDialog::setTitle( const OUString& ) +{ +} + +sal_Int16 SvFilterOptionsDialog::execute() +{ + sal_Int16 nRet = ui::dialogs::ExecutableDialogResults::CANCEL; + + OUString aInternalFilterName; + uno::Reference<graphic::XGraphic> xGraphic; + for ( const auto& rProp : std::as_const(maMediaDescriptor) ) + { + const OUString& rName = rProp.Name; + if ( rName == "FilterName" ) + { + OUString aStr; + rProp.Value >>= aStr; + aInternalFilterName = aStr.replaceFirst( "draw_", "" ); + aInternalFilterName = aInternalFilterName.replaceFirst( "impress_", "" ); + aInternalFilterName = aInternalFilterName.replaceFirst( "calc_", "" ); + aInternalFilterName = aInternalFilterName.replaceFirst( "writer_", "" ); + break; + } + else if ( rName == "Graphic" ) + { + rProp.Value >>= xGraphic; + } + } + if ( !aInternalFilterName.isEmpty() ) + { + GraphicFilter aGraphicFilter( true ); + + sal_uInt16 nFormat, nFilterCount = aGraphicFilter.GetExportFormatCount(); + for ( nFormat = 0; nFormat < nFilterCount; nFormat++ ) + { + if ( aGraphicFilter.GetExportInternalFilterName( nFormat ) == aInternalFilterName ) + break; + } + if ( nFormat < nFilterCount ) + { + FltCallDialogParameter aFltCallDlgPara(Application::GetFrameWeld(mxParent), meFieldUnit); + aFltCallDlgPara.aFilterData = maFilterDataSequence; + aFltCallDlgPara.aFilterExt = aGraphicFilter.GetExportFormatShortName( nFormat ); + bool bIsPixelFormat( aGraphicFilter.IsExportPixelFormat( nFormat ) ); + + ExportDialog aDialog(aFltCallDlgPara, mxContext, mxSourceDocument, mbExportSelection, + bIsPixelFormat, mbGraphicsSource, xGraphic); + if (aDialog.run() == RET_OK) + nRet = ui::dialogs::ExecutableDialogResults::OK; + + // taking the out parameter from the dialog + maFilterDataSequence = aFltCallDlgPara.aFilterData; + } + } + return nRet; +} + +// XEmporter +void SvFilterOptionsDialog::setSourceDocument( const uno::Reference< lang::XComponent >& xDoc ) +{ + mxSourceDocument = xDoc; + + mbGraphicsSource = true; // default Draw and Impress like it was before + + // try to set the corresponding metric unit + OUString aConfigPath; + uno::Reference< lang::XServiceInfo > xServiceInfo + ( xDoc, uno::UNO_QUERY ); + if ( !xServiceInfo.is() ) + return; + + if ( xServiceInfo->supportsService("com.sun.star.presentation.PresentationDocument") ) + aConfigPath = "Office.Impress/Layout/Other/MeasureUnit"; + else if ( xServiceInfo->supportsService("com.sun.star.drawing.DrawingDocument") ) + aConfigPath = "Office.Draw/Layout/Other/MeasureUnit"; + else + { + mbGraphicsSource = false; + if ( xServiceInfo->supportsService("com.sun.star.sheet.SpreadsheetDocument") ) + aConfigPath = "Office.Calc/Layout/Other/MeasureUnit"; + else if ( xServiceInfo->supportsService("com.sun.star.text.TextDocument") ) + aConfigPath = "Office.Writer/Layout/Other/MeasureUnit"; + } + if ( !aConfigPath.isEmpty() ) + { + FilterConfigItem aConfigItem( aConfigPath ); + OUString aPropertyName; + SvtSysLocale aSysLocale; + if ( aSysLocale.GetLocaleData().getMeasurementSystemEnum() == MeasurementSystem::Metric ) + aPropertyName = "Metric"; + else + aPropertyName = "NonMetric"; + meFieldUnit = static_cast<FieldUnit>( + aConfigItem.ReadInt32(aPropertyName, sal_Int32(FieldUnit::CM))); + } +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_svtools_SvFilterOptionsDialog_get_implementation( + css::uno::XComponentContext * context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new SvFilterOptionsDialog(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/filter/exportdialog.cxx b/svtools/source/filter/exportdialog.cxx new file mode 100644 index 000000000..879c8ec01 --- /dev/null +++ b/svtools/source/filter/exportdialog.cxx @@ -0,0 +1,1136 @@ +/* -*- 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 <algorithm> + +#include <comphelper/propertyvalue.hxx> +#include <o3tl/safeint.hxx> +#include <tools/stream.hxx> +#include <tools/fract.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/FilterConfigItem.hxx> +#include <svtools/strings.hrc> +#include <svtools/svtresid.hxx> +#include <svtools/DocumentToGraphicRenderer.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/drawing/GraphicExportFilter.hpp> +#include <com/sun/star/drawing/XDrawView.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/graphic/PrimitiveFactory2D.hpp> +#include <com/sun/star/geometry/AffineMatrix2D.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <unotools/streamwrap.hxx> +#include <vcl/svapp.hxx> +#include <vcl/outdev.hxx> +#include <vcl/graph.hxx> +#include <rtl/ustrbuf.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include "exportdialog.hxx" + +#define FORMAT_UNKNOWN 0 +#define FORMAT_JPG 1 +#define FORMAT_PNG 2 +#define FORMAT_BMP 3 +#define FORMAT_GIF 4 +#define FORMAT_TIF 10 +#define FORMAT_WMF 12 +#define FORMAT_EMF 13 +#define FORMAT_EPS 14 +#define FORMAT_SVG 16 +#define FORMAT_WEBP 17 + +#define UNIT_DEFAULT -1 +#define UNIT_INCH 0 +#define UNIT_CM 1 +#define UNIT_MM 2 +#define UNIT_POINT 3 +#define UNIT_PIXEL 4 +#define UNIT_MAX_ID UNIT_PIXEL + +using namespace ::com::sun::star; + +static sal_Int16 GetFilterFormat(std::u16string_view rExt) +{ + sal_Int16 nFormat = FORMAT_UNKNOWN; + if ( rExt == u"JPG" ) + nFormat = FORMAT_JPG; + else if ( rExt == u"PNG" ) + nFormat = FORMAT_PNG; + else if ( rExt == u"BMP" ) + nFormat = FORMAT_BMP; + else if ( rExt == u"GIF" ) + nFormat = FORMAT_GIF; + else if ( rExt == u"TIF" ) + nFormat = FORMAT_TIF; + else if ( rExt == u"WMF" ) + nFormat = FORMAT_WMF; + else if ( rExt == u"EMF" ) + nFormat = FORMAT_EMF; + else if ( rExt == u"EPS" ) + nFormat = FORMAT_EPS; + else if ( rExt == u"SVG" ) + nFormat = FORMAT_SVG; + else if ( rExt == u"WEBP" ) + nFormat = FORMAT_WEBP; + return nFormat; +} + +static MapUnit GetMapUnit( sal_Int32 nUnit ) +{ + MapUnit aMapUnit( MapUnit::MapPixel ); + switch( nUnit ) + { + case UNIT_INCH : aMapUnit = MapUnit::MapInch; break; + case UNIT_CM : aMapUnit = MapUnit::MapCM; break; + case UNIT_MM : aMapUnit = MapUnit::MapMM; break; + case UNIT_POINT : aMapUnit = MapUnit::MapPoint; break; + case UNIT_PIXEL : aMapUnit = MapUnit::MapPixel; break; + } + return aMapUnit; +} + +sal_Int32 ExportDialog::GetDefaultUnit() const +{ + sal_Int32 nDefaultUnit = UNIT_CM; + switch( mrFltCallPara.eFieldUnit ) + { +// case FieldUnit::NONE : +// case FieldUnit::PERCENT : +// case FieldUnit::CUSTOM : + default: nDefaultUnit = UNIT_CM; break; + + case FieldUnit::MILE : // PASSTHROUGH INTENDED + case FieldUnit::FOOT : + case FieldUnit::TWIP : + case FieldUnit::PICA : nDefaultUnit = UNIT_INCH; break; + + case FieldUnit::KM : // PASSTHROUGH INTENDED + case FieldUnit::M : + case FieldUnit::MM_100TH : nDefaultUnit = UNIT_CM; break; + + case FieldUnit::INCH : nDefaultUnit = UNIT_INCH; break; + case FieldUnit::CM : nDefaultUnit = UNIT_CM; break; + case FieldUnit::MM : nDefaultUnit = UNIT_MM; break; + case FieldUnit::POINT : nDefaultUnit = UNIT_POINT; break; + } + return nDefaultUnit; +} + +static basegfx::B2DRange GetShapeRangeForXShape( const uno::Reference< drawing::XShape >& rxShape, + const uno::Reference< graphic::XPrimitiveFactory2D >& rxPrimitiveFactory2D, const uno::Sequence< beans::PropertyValue >& rViewInformation ) +{ + basegfx::B2DRange aShapeRange; + + const uno::Sequence< beans::PropertyValue > aParams; + const uno::Sequence< uno::Reference< graphic::XPrimitive2D > > aPrimitiveSequence( rxPrimitiveFactory2D->createPrimitivesFromXShape( rxShape, aParams ) ); + + for( const auto& rPrimitive : aPrimitiveSequence ) + { + const geometry::RealRectangle2D aRect( rPrimitive->getRange( rViewInformation ) ); + aShapeRange.expand( basegfx::B2DTuple( aRect.X1, aRect.Y1 ) ); + aShapeRange.expand( basegfx::B2DTuple( aRect.X2, aRect.Y2 ) ); + } + return aShapeRange; +} + +uno::Sequence< beans::PropertyValue > ExportDialog::GetFilterData( bool bUpdateConfig ) +{ + if ( bUpdateConfig ) + { + sal_Int32 nUnit = mxLbSizeX->get_active(); + if ( nUnit < 0 ) + nUnit = UNIT_CM; + + if ( ( mnInitialResolutionUnit == UNIT_DEFAULT ) && ( nUnit == GetDefaultUnit() ) ) + nUnit = UNIT_DEFAULT; + + // updating ui configuration + if ( mbIsPixelFormat ) + { + if ( nUnit > UNIT_MAX_ID ) + nUnit = UNIT_PIXEL; + + sal_Int32 nResolution = mxNfResolution->get_value(); + if ( nResolution < 1 ) + nResolution = 96; + + mpOptionsItem->WriteInt32("PixelExportUnit", nUnit); + mpOptionsItem->WriteInt32("PixelExportResolution", nResolution); + mpOptionsItem->WriteInt32("PixelExportResolutionUnit", mxLbResolution->get_active()); + } + else + { + if ( nUnit >= UNIT_PIXEL ) + nUnit = UNIT_CM; + + mpOptionsItem->WriteInt32("VectorExportUnit", nUnit); + } + } + + FilterConfigItem* pFilterOptions; + if ( bUpdateConfig ) + pFilterOptions = mpFilterOptionsItem.get(); + else + { + uno::Sequence< beans::PropertyValue > aFilterData( mpFilterOptionsItem->GetFilterData() ); + pFilterOptions = new FilterConfigItem( &aFilterData ); + } + + static const OUStringLiteral sLogicalWidth(u"LogicalWidth"); + static const OUStringLiteral sLogicalHeight(u"LogicalHeight"); + if ( mbIsPixelFormat ) + { + pFilterOptions->WriteInt32("PixelWidth", maSize.Width ); + pFilterOptions->WriteInt32("PixelHeight", maSize.Height ); + if ( maResolution.Width && maResolution.Height ) + { + const double f100thmmPerPixelX = 100000.0 / maResolution.Width; + const double f100thmmPerPixelY = 100000.0 / maResolution.Height; + sal_Int32 nLogicalWidth = static_cast< sal_Int32 >( f100thmmPerPixelX * maSize.Width ); + sal_Int32 nLogicalHeight= static_cast< sal_Int32 >( f100thmmPerPixelY * maSize.Height ); + if ( nLogicalWidth && nLogicalHeight ) + { + pFilterOptions->WriteInt32( sLogicalWidth, nLogicalWidth ); + pFilterOptions->WriteInt32( sLogicalHeight, nLogicalHeight ); + } + } + } + else + { + pFilterOptions->WriteInt32( sLogicalWidth, maSize.Width ); + pFilterOptions->WriteInt32( sLogicalHeight, maSize.Height ); + } + switch ( mnFormat ) + { + case FORMAT_JPG : + { + sal_Int32 nColor = mxLbColorDepth->get_active(); + if ( nColor == 1 ) + nColor = 0; + else + nColor = 1; + pFilterOptions->WriteInt32("ColorMode", nColor); + assert(mpSbCompression); + pFilterOptions->WriteInt32("Quality", static_cast<sal_Int32>(mpSbCompression->get_value())); + } + break; + + case FORMAT_PNG : + { + assert(mpSbCompression); + pFilterOptions->WriteInt32("Compression", static_cast<sal_Int32>(mpSbCompression->get_value())); + sal_Int32 nInterlace = 0; + if ( mxCbInterlaced->get_active() ) + nInterlace++; + pFilterOptions->WriteInt32("Interlaced", nInterlace); + sal_Int32 nValue = 0; + if ( mxCbSaveTransparency->get_active() ) + nValue++; + pFilterOptions->WriteInt32("Translucent", nValue); + } + break; + + case FORMAT_BMP : + { + pFilterOptions->WriteInt32("Color", mxLbColorDepth->get_active() + 1); + pFilterOptions->WriteBool("RLE_Coding", mxCbRLEEncoding->get_active()); + } + break; + + case FORMAT_GIF : + { + sal_Int32 nValue = 0; + if ( mxCbInterlaced->get_active() ) + nValue++; + pFilterOptions->WriteInt32("Interlaced", nValue); + + nValue = 0; + if (mxCbSaveTransparency->get_active()) + nValue++; + pFilterOptions->WriteInt32("Translucent", nValue); + } + break; + + case FORMAT_EPS : + { + sal_Int32 nCheck = 0; + if ( mxCbEPSPreviewTIFF->get_active() ) + nCheck++; + if ( mxCbEPSPreviewEPSI->get_active() ) + nCheck += 2; + pFilterOptions->WriteInt32("Preview", nCheck); + + nCheck = 1; + if ( mxRbEPSLevel2->get_active() ) + nCheck++; + pFilterOptions->WriteInt32("Version", nCheck); + + nCheck = 1; + if ( mxRbEPSColorFormat2->get_active() ) + nCheck++; + pFilterOptions->WriteInt32("ColorFormat", nCheck); + + nCheck = 1; + if ( mxRbEPSCompressionNone->get_active() ) + nCheck++; + pFilterOptions->WriteInt32("CompressionMode", nCheck); + } + break; + + case FORMAT_WEBP : + { + assert(mpSbCompression); + pFilterOptions->WriteInt32("Quality", static_cast<sal_Int32>(mpSbCompression->get_value())); + pFilterOptions->WriteBool("Lossless", mxCbLossless->get_active()); + } + break; + + } + + uno::Sequence< beans::PropertyValue > aRet( pFilterOptions->GetFilterData() ); + if ( !bUpdateConfig ) + delete pFilterOptions; + return aRet; +} + + +awt::Size ExportDialog::GetOriginalSize() +{ + basegfx::B2DRange aShapesRange; + + if ( mxPage.is () ) + { + uno::Reference< beans::XPropertySet > xPagePropSet( mxPage, uno::UNO_QUERY ); + if ( xPagePropSet.is() ) + { + sal_Int32 nWidth = 0; + sal_Int32 nHeight= 0; + css::uno::Any aAny; + aAny = xPagePropSet->getPropertyValue("Width"); + aAny >>= nWidth; + aAny = xPagePropSet->getPropertyValue("Height"); + aAny >>= nHeight; + aShapesRange = basegfx::B2DRange( 0, 0, nWidth, nHeight ); + } + } + else if (mxShapes.is() || mxShape.is()) + { + uno::Reference< graphic::XPrimitiveFactory2D > xPrimitiveFactory = graphic::PrimitiveFactory2D::create( mxContext ); + + basegfx::B2DHomMatrix aViewTransformation( Application::GetDefaultDevice()->GetViewTransformation() ); + css::geometry::AffineMatrix2D aTransformation; + aTransformation.m00 = aViewTransformation.get(0,0); + aTransformation.m01 = aViewTransformation.get(0,1); + aTransformation.m02 = aViewTransformation.get(0,2); + aTransformation.m10 = aViewTransformation.get(1,0); + aTransformation.m11 = aViewTransformation.get(1,1); + aTransformation.m12 = aViewTransformation.get(1,2); + + uno::Sequence< beans::PropertyValue > aViewInformation{ comphelper::makePropertyValue( + "ViewTransformation", aTransformation) }; + + if ( mxShape.is() ) + aShapesRange = GetShapeRangeForXShape( mxShape, xPrimitiveFactory, aViewInformation ); + else if ( mxShapes.is() ) + { + const sal_Int32 nCount = mxShapes->getCount(); + for( sal_Int32 nIndex = 0; nIndex < nCount; nIndex++ ) + { + uno::Reference< drawing::XShape > xShape; + mxShapes->getByIndex( nIndex ) >>= xShape; + aShapesRange.expand( GetShapeRangeForXShape( xShape, xPrimitiveFactory, aViewInformation ) ); + } + } + } + else if (!mbGraphicsSource) + { + DocumentToGraphicRenderer aRenderer( mxSourceDocument, mbExportSelection); + const sal_Int32 nCurrentPage = aRenderer.getCurrentPage(); + const Size aSize = aRenderer.getDocumentSizeIn100mm( nCurrentPage); + return awt::Size( aSize.Width(), aSize.Height()); + } + return awt::Size( static_cast<sal_Int32>(aShapesRange.getWidth()), static_cast<sal_Int32>(aShapesRange.getHeight()) ); +} + +void ExportDialog::GetGraphicSource() +{ + if (mxGraphic.is()) + return; + + if ( !mxSourceDocument.is() ) + return; + + uno::Reference< frame::XModel > xModel( mxSourceDocument, uno::UNO_QUERY ); + if ( !xModel.is() ) + return; + + uno::Reference< frame::XController > xController( xModel->getCurrentController() ); + if ( !xController.is() ) + return; + + if ( mbExportSelection ) // check if there is a selection + { + if (DocumentToGraphicRenderer::isShapeSelected( mxShapes, mxShape, xController)) + mbGraphicsSource = true; + } + if ( !mxShape.is() && !mxShapes.is() && mbGraphicsSource ) + { + uno::Reference< drawing::XDrawView > xDrawView( xController, uno::UNO_QUERY ); + if ( xDrawView.is() ) + { + uno::Reference< drawing::XDrawPage > xCurrentPage( xDrawView->getCurrentPage() ); + if ( xCurrentPage.is() ) + { + mxPage = xCurrentPage; // exporting whole page + } + } + } + // For !mbGraphicsSource the mxSourceDocument is used, from + // which XRenderable can query XController and + // XSelectionSupplier the same. +} + +void ExportDialog::GetGraphicStream() +{ + if ( !IsTempExportAvailable() ) + { + mpTempStream.reset(new SvMemoryStream()); + return; + } + + bool bRecreateOutputStream = mpTempStream->Tell() == 0; + + static uno::Sequence< beans::PropertyValue > aOldFilterData; + uno::Sequence< beans::PropertyValue > aNewFilterData( GetFilterData( false ) ); + if ( aOldFilterData != aNewFilterData ) + { + aOldFilterData = aNewFilterData; + bRecreateOutputStream = true; + } + try + { + if ( bRecreateOutputStream ) + { + mpTempStream.reset(new SvMemoryStream()); + + uno::Reference< graphic::XGraphic > xGraphic; + if (!mbGraphicsSource && !mxGraphic.is()) + { + // Create a Graphic to be used below. + DocumentToGraphicRenderer aRenderer( mxSourceDocument, mbExportSelection); + const sal_Int32 nCurrentPage = aRenderer.getCurrentPage(); + const Size aDocumentSizePixel = aRenderer.getDocumentSizeInPixels( nCurrentPage); + + const Size aTargetSizePixel( mbIsPixelFormat ? + Size( maSize.Width, maSize.Height) : + aDocumentSizePixel ); + + Graphic aGraphic( aRenderer.renderToGraphic( nCurrentPage, + aDocumentSizePixel, aTargetSizePixel, COL_WHITE, /*bExtOutDevData=*/false)); + xGraphic = aGraphic.GetXGraphic(); + } + + if ( mxGraphic.is() || xGraphic.is() ) + { + Graphic aGraphic( mxGraphic.is() ? mxGraphic : xGraphic ); + + if ( aGraphic.GetType() == GraphicType::Bitmap ) + { + Size aSizePixel( aGraphic.GetSizePixel() ); + if( maSize.Width && maSize.Height && + ( ( maSize.Width != aSizePixel.Width() ) || + ( maSize.Height != aSizePixel.Height() ) ) ) + { + BitmapEx aBmpEx( aGraphic.GetBitmapEx() ); + // export: use highest quality + aBmpEx.Scale( Size( maSize.Width, maSize.Height ), BmpScaleFlag::Lanczos ); + aGraphic = aBmpEx; + } + } + + GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); + const sal_uInt16 nFilter = rFilter.GetExportFormatNumberForShortName( maExt ); + if ( rFilter.IsExportPixelFormat( nFilter ) ) + { + mpTempStream->SetResizeOffset(1024); + mpTempStream->SetStreamSize(1024); + rFilter.ExportGraphic( aGraphic, u"", *mpTempStream, nFilter, &aNewFilterData ); + } + } + else + { + uno::Reference< lang::XComponent > xSourceDoc; + if ( mxPage.is() ) + xSourceDoc.set( mxPage, uno::UNO_QUERY_THROW ); + else if ( mxShapes.is() ) + xSourceDoc.set( mxShapes, uno::UNO_QUERY_THROW ); + else if ( mxShape.is() ) + xSourceDoc.set( mxShape, uno::UNO_QUERY_THROW ); + if ( xSourceDoc.is() ) + { + uno::Reference < io::XStream > xStream( new utl::OStreamWrapper( *mpTempStream ) ); + uno::Reference < io::XOutputStream > xOutputStream( xStream->getOutputStream() ); + + OUString sFormat( maExt ); + uno::Sequence< beans::PropertyValue > aDescriptor{ + comphelper::makePropertyValue("OutputStream", xOutputStream), + comphelper::makePropertyValue("FilterName", sFormat), + comphelper::makePropertyValue("FilterData", aNewFilterData) + }; + + uno::Reference< drawing::XGraphicExportFilter > xGraphicExporter = + drawing::GraphicExportFilter::create( mxContext ); + + xGraphicExporter->setSourceDocument( xSourceDoc ); + xGraphicExporter->filter( aDescriptor ); + } + } + } + } + catch( uno::Exception& ) + { + + // ups + + } +} + +sal_uInt32 ExportDialog::GetRawFileSize() const +{ + sal_uInt64 nRawFileSize = 0; + if ( mbIsPixelFormat ) + { + sal_Int32 nBitsPerPixel = 24; + OUString aEntry(mxLbColorDepth->get_active_text()); + if ( ms1BitThreshold == aEntry ) + nBitsPerPixel = 1; + else if ( ms8BitGrayscale == aEntry ) + nBitsPerPixel = 8; + else if ( ms8BitColorPalette == aEntry ) + nBitsPerPixel = 8; + else if ( ms24BitColor == aEntry ) + nBitsPerPixel = 24; + + if ( mbIsPixelFormat ) + { + nRawFileSize = ( maSize.Width * nBitsPerPixel + 7 ) &~ 7; // rounding up to 8 bits + nRawFileSize /= 8; // in bytes + nRawFileSize *= maSize.Height; + } + if ( nRawFileSize > SAL_MAX_UINT32 ) + nRawFileSize = 0; + } + return static_cast< sal_uInt32 >( nRawFileSize ); +} + +// checks if the source dimension/resolution is not too big +// to determine the exact graphic output size and preview for jpg +bool ExportDialog::IsTempExportAvailable() const +{ + return GetRawFileSize() < o3tl::make_unsigned( mnMaxFilesizeForRealtimePreview ); +} + +ExportDialog::ExportDialog(FltCallDialogParameter& rPara, + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Reference< css::lang::XComponent >& rxSourceDocument, + bool bExportSelection, bool bIsPixelFormat, bool bGraphicsSource, + const css::uno::Reference< css::graphic::XGraphic >& rxGraphic) + : GenericDialogController(rPara.pWindow, "svt/ui/graphicexport.ui", "GraphicExportDialog") + , mrFltCallPara(rPara) + , mxContext(rxContext) + , mxSourceDocument(rxSourceDocument) + , mxGraphic(rxGraphic) + , msEstimatedSizePix1(SvtResId(STR_SVT_ESTIMATED_SIZE_PIX_1)) + , msEstimatedSizePix2(SvtResId(STR_SVT_ESTIMATED_SIZE_PIX_2)) + , msEstimatedSizeVec(SvtResId(STR_SVT_ESTIMATED_SIZE_VEC)) + , ms1BitThreshold(SvtResId(STR_SVT_1BIT_THRESHOLD)) + , ms8BitGrayscale(SvtResId(STR_SVT_8BIT_GRAYSCALE)) + , ms8BitColorPalette(SvtResId(STR_SVT_8BIT_COLOR_PALETTE)) + , ms24BitColor(SvtResId(STR_SVT_24BIT_TRUE_COLOR)) + , maExt(rPara.aFilterExt) + , mnFormat(FORMAT_UNKNOWN) + , mnMaxFilesizeForRealtimePreview(0) + , mpTempStream(new SvMemoryStream()) + , maOriginalSize(awt::Size(0, 0)) + , mbIsPixelFormat(bIsPixelFormat) + , mbExportSelection(bExportSelection) + , mbGraphicsSource(bGraphicsSource) + , mpSbCompression(nullptr) + , mpNfCompression(nullptr) + , mxMfSizeX(m_xBuilder->weld_spin_button("widthmf")) + , mxLbSizeX(m_xBuilder->weld_combo_box("widthlb")) + , mxMfSizeY(m_xBuilder->weld_spin_button( "heightmf")) + , mxFtResolution(m_xBuilder->weld_label("resolutionft")) + , mxNfResolution(m_xBuilder->weld_spin_button("resolutionmf")) + , mxLbResolution(m_xBuilder->weld_combo_box("resolutionlb")) + , mxColorDepth(m_xBuilder->weld_widget("colordepth")) + , mxLbColorDepth(m_xBuilder->weld_combo_box("colordepthlb")) + , mxJPGWEBPQuality(m_xBuilder->weld_widget("jpgwebpquality")) + , mxPNGCompression(m_xBuilder->weld_widget("pngcompression")) + , mxSbPngCompression(m_xBuilder->weld_scale("compressionpngsb")) + , mxNfPngCompression(m_xBuilder->weld_spin_button("compressionpngnf")) + , mxSbJpgWebpCompression(m_xBuilder->weld_scale("compressionjpgwebpsb")) + , mxNfJpgWebpCompression(m_xBuilder->weld_spin_button("compressionjpgwebpnf")) + , mxCbLossless(m_xBuilder->weld_check_button("losslesscb")) + , mxMode(m_xBuilder->weld_widget("mode")) + , mxCbInterlaced(m_xBuilder->weld_check_button("interlacedcb")) + , mxBMPCompression(m_xBuilder->weld_widget("bmpcompression")) + , mxCbRLEEncoding(m_xBuilder->weld_check_button("rlecb")) + , mxDrawingObjects(m_xBuilder->weld_widget("drawingobjects")) + , mxCbSaveTransparency(m_xBuilder->weld_check_button("savetransparencycb")) + , mxEncoding(m_xBuilder->weld_widget("encoding")) + , mxRbBinary(m_xBuilder->weld_radio_button("binarycb")) + , mxRbText(m_xBuilder->weld_radio_button("textcb")) + , mxEPSGrid(m_xBuilder->weld_widget("epsgrid")) + , mxModifyDimension(m_xBuilder->weld_radio_button("modifydimensionscb")) + , mxModifyResolution(m_xBuilder->weld_radio_button("modifyresolutioncb")) + , mxCbEPSPreviewTIFF(m_xBuilder->weld_check_button("tiffpreviewcb")) + , mxCbEPSPreviewEPSI(m_xBuilder->weld_check_button("epsipreviewcb")) + , mxRbEPSLevel1(m_xBuilder->weld_radio_button("level1rb")) + , mxRbEPSLevel2(m_xBuilder->weld_radio_button("level2rb")) + , mxRbEPSColorFormat1(m_xBuilder->weld_radio_button("color1rb")) + , mxRbEPSColorFormat2(m_xBuilder->weld_radio_button("color2rb")) + , mxRbEPSCompressionLZW(m_xBuilder->weld_radio_button("compresslzw")) + , mxRbEPSCompressionNone(m_xBuilder->weld_radio_button("compressnone")) + , mxInfo(m_xBuilder->weld_widget("information")) + , mxFtEstimatedSize(m_xBuilder->weld_label("estsizeft")) + , mxBtnOK(m_xBuilder->weld_button("ok")) +{ + GetGraphicSource(); + + maExt = maExt.toAsciiUpperCase(); + + OUString aFilterConfigPath( "Office.Common/Filter/Graphic/Export/" ); + mpOptionsItem.reset(new FilterConfigItem( aFilterConfigPath, &rPara.aFilterData )); + aFilterConfigPath += maExt; + mpFilterOptionsItem.reset(new FilterConfigItem( aFilterConfigPath, &rPara.aFilterData )); + + mnInitialResolutionUnit = mbIsPixelFormat + ? mpOptionsItem->ReadInt32("PixelExportUnit", UNIT_DEFAULT) + : mpOptionsItem->ReadInt32("VectorExportUnit", UNIT_DEFAULT); + + mnMaxFilesizeForRealtimePreview = std::max( + mpOptionsItem->ReadInt32("MaxFilesizeForRealtimePreview", 0), sal_Int32(0)); + mxFtEstimatedSize->set_label(" \n "); + + m_xDialog->set_title(m_xDialog->get_title().replaceFirst("%1", maExt)); //Set dialog title + + mnFormat = GetFilterFormat( maExt ); + + Size aResolution( Application::GetDefaultDevice()->LogicToPixel(Size(100, 100), MapMode(MapUnit::MapCM)) ); + maResolution.Width = aResolution.Width(); + maResolution.Height= aResolution.Height(); + + if ( mxGraphic.is() ) + { + Graphic aGraphic(mxGraphic); + Size aSize = aGraphic.GetSizePixel(); + maSize = awt::Size(aSize.getWidth(), aSize.getHeight()); + double f100thmmPerPixel = 100000.0 / static_cast< double >( maResolution.Width ); + maOriginalSize = awt::Size( + static_cast< sal_Int32 >( f100thmmPerPixel * maSize.Width ), + static_cast< sal_Int32 >( f100thmmPerPixel * maSize.Height ) ); + } + else + { + maOriginalSize = GetOriginalSize(); + if ( bIsPixelFormat ) + { + double fPixelsPer100thmm = static_cast< double >( maResolution.Width ) / 100000.0; + maSize = awt::Size( static_cast< sal_Int32 >( ( fPixelsPer100thmm * maOriginalSize.Width ) + 0.5 ), + static_cast< sal_Int32 >( ( fPixelsPer100thmm * maOriginalSize.Height ) + 0.5 ) ); + } + else + { + maSize = maOriginalSize; + } + } + setupControls(); + + // Size + mxLbSizeX->connect_changed( LINK( this, ExportDialog, SelectListBoxHdl ) ); + + if (mpSbCompression) + mpSbCompression->connect_value_changed(LINK(this, ExportDialog, SbCompressionUpdateHdl)); + if (mpNfCompression) + mpNfCompression->connect_value_changed(LINK(this, ExportDialog, SelectHdl)); + + mxMfSizeX->connect_value_changed( LINK( this, ExportDialog, UpdateHdlMtfSizeX ) ); + mxMfSizeY->connect_value_changed( LINK( this, ExportDialog, UpdateHdlMtfSizeY ) ); + + mxNfResolution->connect_value_changed( LINK( this, ExportDialog, UpdateHdlNfResolution ) ); + mxLbResolution->connect_changed( LINK( this, ExportDialog, SelectListBoxHdl ) ); + + mxLbColorDepth->connect_changed( LINK( this, ExportDialog, SelectListBoxHdl ) ); + + mxCbInterlaced->connect_toggled( LINK( this, ExportDialog, UpdateHdl ) ); + + mxCbLossless->connect_toggled( LINK( this, ExportDialog, UpdateHdlLossless ) ); + + mxCbSaveTransparency->connect_toggled( LINK( this, ExportDialog, UpdateHdl ) ); + + mxModifyDimension->connect_toggled( LINK( this, ExportDialog, UpdateLock ) ); + mxModifyResolution->connect_toggled( LINK( this, ExportDialog, UpdateLock ) ); + + mxCbEPSPreviewTIFF->connect_toggled( LINK( this, ExportDialog, UpdateHdl ) ); + mxCbEPSPreviewEPSI->connect_toggled( LINK( this, ExportDialog, UpdateHdl ) ); + + mxRbEPSCompressionLZW->connect_toggled( LINK( this, ExportDialog, UpdateHdl ) ); + mxRbEPSCompressionNone->connect_toggled( LINK( this, ExportDialog, UpdateHdl ) ); + + mxRbBinary->connect_toggled( LINK( this, ExportDialog, UpdateHdl ) ); + mxRbText->connect_toggled( LINK( this, ExportDialog, UpdateHdl ) ); + + // BMP + mxCbRLEEncoding->connect_toggled( LINK( this, ExportDialog, UpdateHdl ) ); + + // EPS + mxRbEPSLevel1->connect_toggled( LINK( this, ExportDialog, UpdateHdl ) ); + mxRbEPSLevel2->connect_toggled( LINK( this, ExportDialog, UpdateHdl ) ); + + mxBtnOK->connect_clicked( LINK( this, ExportDialog, OK ) ); + + updateControls(); +} + +void ExportDialog::setupSizeControls() +{ + sal_Int32 nUnit = mnInitialResolutionUnit; + if (nUnit == UNIT_DEFAULT) + nUnit = GetDefaultUnit(); + + if (!mbIsPixelFormat) + { + mxFtResolution->hide(); + mxNfResolution->hide(); + mxLbResolution->hide(); + mxLbSizeX->remove( UNIT_PIXEL ); // removing pixel + if ( nUnit >= UNIT_PIXEL ) + nUnit = UNIT_CM; + } + else if ( nUnit > UNIT_MAX_ID ) + nUnit = UNIT_PIXEL; + if ( nUnit < 0 ) + nUnit = UNIT_CM; + mxLbSizeX->set_active( static_cast< sal_uInt16 >( nUnit ) ); + + if ( !mbIsPixelFormat ) // TODO: (metafileresolutionsupport) should be supported for vector formats also... this makes + return; + +// sense eg for bitmap fillings in metafiles, to preserve high dpi output + // (atm without special vector support the bitmaps are rendered with 96dpi) + sal_Int32 nResolution = mpOptionsItem->ReadInt32("PixelExportResolution", 96); + if ( nResolution < 1 ) + nResolution = 96; + mxNfResolution->set_value( nResolution ); + + sal_Int32 nResolutionUnit = mpOptionsItem->ReadInt32("PixelExportResolutionUnit", 1); + if ( ( nResolutionUnit < 0 ) || ( nResolutionUnit > 2 ) ) + nResolutionUnit = 1; + mxLbResolution->set_active( static_cast< sal_uInt16 >( nResolutionUnit ) ); +} + +void ExportDialog::createFilterOptions() +{ + switch( mnFormat ) + { + case FORMAT_JPG : + { + sal_Int32 nColor = mpFilterOptionsItem->ReadInt32("ColorMode", 0); + if ( nColor == 1 ) + nColor = 0; + else + nColor = 1; + mxLbColorDepth->append_text( ms8BitGrayscale ); + mxLbColorDepth->append_text( ms24BitColor ); + mxLbColorDepth->set_active( nColor ); + mxColorDepth->show(); + + // Quality + mxJPGWEBPQuality->show(); + sal_Int32 nQuality = mpFilterOptionsItem->ReadInt32("Quality", 75); + if ((nQuality < 1 ) || (nQuality > 100)) + nQuality = 75; + mpSbCompression = mxSbJpgWebpCompression.get(); + mpNfCompression = mxNfJpgWebpCompression.get(); + mpSbCompression->set_range(1, 100); + mpNfCompression->set_range(1, 100); + mpNfCompression->set_value(nQuality); + mxCbLossless->hide(); // only for WebP + } + break; + case FORMAT_PNG : + { + // Compression 1..9 + mxPNGCompression->show(); + sal_Int32 nCompression = mpFilterOptionsItem->ReadInt32("Compression", 6); + if ( ( nCompression < 1 ) || ( nCompression > 9 ) ) + nCompression = 6; + + mpSbCompression = mxSbPngCompression.get(); + mpNfCompression = mxNfPngCompression.get(); + mpSbCompression->set_range(1, 9); + mpNfCompression->set_range(1, 9); + mpNfCompression->set_value(nCompression); + + // Interlaced + mxMode->show(); + mxCbInterlaced->set_active(mpFilterOptionsItem->ReadInt32("Interlaced", 0) != 0); + + // Transparency + mxDrawingObjects->show(); + mxCbSaveTransparency->set_active(mpFilterOptionsItem->ReadInt32("Translucent", 1) != 0); + } + break; + case FORMAT_BMP : + { + sal_Int32 nColor = mpFilterOptionsItem->ReadInt32("Color", 0); + if ( nColor == 0 ) + nColor = 6; + else + nColor--; + mxLbColorDepth->append_text( ms1BitThreshold ); + mxLbColorDepth->append_text( ms8BitGrayscale ); + mxLbColorDepth->append_text( ms8BitColorPalette ); + mxLbColorDepth->append_text( ms24BitColor ); + mxLbColorDepth->set_active( nColor ); + mxColorDepth->show(); + + // RLE coding + mxBMPCompression->show(); + mxCbRLEEncoding->set_active(mpFilterOptionsItem->ReadBool("RLE_Coding", true)); + } + break; + case FORMAT_GIF : + { + // Interlaced + mxMode->show(); + mxCbInterlaced->set_active(mpFilterOptionsItem->ReadInt32("Interlaced", 1) != 0); + + // Transparency + mxDrawingObjects->show(); + mxCbSaveTransparency->set_active(mpFilterOptionsItem->ReadInt32("Translucent", 1) != 0); + } + break; + case FORMAT_EPS : + { + mxEPSGrid->show(); + + sal_Int32 nPreview = mpFilterOptionsItem->ReadInt32("Preview", 0); + sal_Int32 nVersion = mpFilterOptionsItem->ReadInt32("Version", 2); + sal_Int32 nColor = mpFilterOptionsItem->ReadInt32("ColorFormat", 0); + sal_Int32 nCompr = mpFilterOptionsItem->ReadInt32("CompressionMode", 2); + + mpFilterOptionsItem->ReadInt32("TextMode", 0); + + mxCbEPSPreviewTIFF->set_active( ( nPreview & 1 ) != 0 ); + mxCbEPSPreviewEPSI->set_active( ( nPreview & 2 ) != 0 ); + + mxRbEPSLevel1->set_active( nVersion == 1 ); + mxRbEPSLevel2->set_active( nVersion == 2 ); + + mxRbEPSColorFormat1->set_active( nColor == 1 ); + mxRbEPSColorFormat2->set_active( nColor != 1 ); + + mxRbEPSCompressionLZW->set_active( nCompr == 1 ); + mxRbEPSCompressionNone->set_active( nCompr != 1 ); + } + break; + case FORMAT_WEBP : + { + // Quality + mxJPGWEBPQuality->show(); + sal_Int32 nQuality = mpFilterOptionsItem->ReadInt32("Quality", 75); + if ((nQuality < 1 ) || (nQuality > 100)) + nQuality = 75; + mpSbCompression = mxSbJpgWebpCompression.get(); + mpNfCompression = mxNfJpgWebpCompression.get(); + mpSbCompression->set_range(1, 100); + mpNfCompression->set_range(1, 100); + mpNfCompression->set_value(nQuality); + + // Lossless + mxCbLossless->set_active(mpFilterOptionsItem->ReadBool("Lossless", true)); + UpdateHdlLossless(*mxCbLossless); + } + break; + } +} + +void ExportDialog::setupControls() +{ + setupSizeControls(); + createFilterOptions(); + + if (mnMaxFilesizeForRealtimePreview || mbIsPixelFormat) + mxInfo->show(); +} + +static OUString ImpValueOfInKB( sal_Int64 rVal ) +{ + double fVal( static_cast<double>( rVal ) ); + fVal /= ( 1 << 10 ); + fVal += 0.05; + OUStringBuffer aVal( OUString::number( fVal ) ); + sal_Int32 nX( aVal.indexOf( '.' ) ); + if ( nX > 0 ) + aVal.setLength( nX + 2 ); + return aVal.makeStringAndClear(); +} + +void ExportDialog::updateControls() +{ + // Size Controls + if ( !mbIsPixelFormat ) + { + awt::Size aSize100thmm( maSize ); + Size aSize( OutputDevice::LogicToLogic( Size(aSize100thmm.Width * 100, aSize100thmm.Height * 100), + MapMode(MapUnit::Map100thMM), + MapMode( GetMapUnit( mxLbSizeX->get_active() ) ) ) ); + mxMfSizeX->set_value( aSize.Width() ); + mxMfSizeY->set_value( aSize.Height() ); + } + else + { + MapUnit aMapUnit( GetMapUnit( mxLbSizeX->get_active() ) ); + if ( aMapUnit == MapUnit::MapPixel ) + { // calculating pixel count via resolution and original graphic size + mxMfSizeX->set_digits( 0 ); + mxMfSizeY->set_digits( 0 ); + mxMfSizeX->set_value( maSize.Width ); + mxMfSizeY->set_value( maSize.Height ); + } + else + { + mxMfSizeX->set_digits( 2 ); + mxMfSizeY->set_digits( 2 ); + double fRatio; + switch( GetMapUnit( mxLbSizeX->get_active() ) ) + { + case MapUnit::MapInch : fRatio = static_cast< double >( maResolution.Width ) * 0.0254; break; + case MapUnit::MapMM : fRatio = static_cast< double >( maResolution.Width ) * 0.001; break; + case MapUnit::MapPoint :fRatio = ( static_cast< double >( maResolution.Width ) * 0.0254 ) / 72.0; break; + default: + case MapUnit::MapCM : fRatio = static_cast< double >( maResolution.Width ) * 0.01; break; + } + mxMfSizeX->set_value( static_cast< sal_Int32 >( ( static_cast< double >( maSize.Width * 100 ) / fRatio ) + 0.5 ) ); + mxMfSizeY->set_value( static_cast< sal_Int32 >( ( static_cast< double >( maSize.Height * 100 ) / fRatio ) + 0.5 ) ); + } + } + sal_Int32 nResolution = 0; + switch( mxLbResolution->get_active() ) + { + case 0 : nResolution = maResolution.Width / 100; break; // pixels / cm + case 2 : nResolution = maResolution.Width; break; // pixels / meter + default: + case 1 : nResolution = static_cast< sal_Int32 >(maResolution.Width * 0.0254); break; // pixels / inch + } + mxNfResolution->set_value( nResolution ); + + if (mpSbCompression && mpSbCompression->get_visible() && mpNfCompression) + mpSbCompression->set_value(mpNfCompression->get_value()); + + GetGraphicStream(); + + // updating estimated size + sal_Int64 nRealFileSize( mpTempStream->Tell() ); + if ( mbIsPixelFormat ) + { + OUString aEst( nRealFileSize ? msEstimatedSizePix2 : msEstimatedSizePix1 ); + sal_Int64 nRawFileSize( GetRawFileSize() ); + sal_Int32 nInd = aEst.indexOf( "%" ); + if (nInd != -1) + aEst = aEst.replaceAt( nInd, 2, ImpValueOfInKB( nRawFileSize ) ); + + if ( nRealFileSize && nInd != -1 ) + { + nInd = aEst.indexOf( "%", nInd ); + if (nInd != -1) + aEst = aEst.replaceAt( nInd, 2, ImpValueOfInKB( nRealFileSize ) ); + } + mxFtEstimatedSize->set_label( aEst ); + } + else + { + if ( mnMaxFilesizeForRealtimePreview ) + { + OUString aEst( msEstimatedSizeVec ); + sal_Int32 nInd = aEst.indexOf( "%" ); + if (nInd != -1) + aEst = aEst.replaceAt( nInd, 2, ImpValueOfInKB( nRealFileSize ) ); + mxFtEstimatedSize->set_label( aEst ); + } + } + + // EPS + if ( mxRbEPSLevel1->get_visible() ) + { + bool bEnabled = !mxRbEPSLevel1->get_active(); + mxRbEPSColorFormat1->set_sensitive( bEnabled ); + mxRbEPSColorFormat2->set_sensitive( bEnabled ); + mxRbEPSCompressionLZW->set_sensitive( bEnabled ); + mxRbEPSCompressionNone->set_sensitive( bEnabled ); + } +} + +ExportDialog::~ExportDialog() +{ +} + +/************************************************************************* +|* +|* stores values set in the ini-file +|* +\************************************************************************/ +IMPL_LINK_NOARG(ExportDialog, SelectHdl, weld::SpinButton&, void) +{ + updateControls(); +} + +IMPL_LINK_NOARG(ExportDialog, SelectListBoxHdl, weld::ComboBox&, void) +{ + updateControls(); +} + +IMPL_LINK_NOARG(ExportDialog, UpdateHdl, weld::Toggleable&, void) +{ + updateControls(); +} + +IMPL_LINK_NOARG(ExportDialog, UpdateHdlLossless, weld::Toggleable&, void) +{ + mpSbCompression->set_sensitive(!mxCbLossless->get_active()); + mpNfCompression->set_sensitive(!mxCbLossless->get_active()); + updateControls(); +} + +IMPL_LINK_NOARG(ExportDialog, UpdateLock, weld::Toggleable&, void) +{ + if (mxModifyResolution->get_active()) + { + mxMfSizeY->set_sensitive(false); + mxMfSizeX->set_sensitive(false); + mxNfResolution->set_sensitive(true); + } + else + { + mxMfSizeY->set_sensitive(true); + mxMfSizeX->set_sensitive(true); + mxNfResolution->set_sensitive(false); + } + updateControls(); +} + + +IMPL_LINK_NOARG(ExportDialog, UpdateHdlMtfSizeX, weld::SpinButton&, void) +{ + double fRatio = static_cast< double >( maOriginalSize.Height ) / maOriginalSize.Width; + + if ( mbIsPixelFormat ) + { + switch( GetMapUnit( mxLbSizeX->get_active() ) ) + { + case MapUnit::MapInch : maSize.Width = static_cast< sal_Int32 >( static_cast< double >( maResolution.Width ) * 0.0254 * mxMfSizeX->get_value() / 100.0 + 0.5 ); break; + case MapUnit::MapCM : maSize.Width = static_cast< sal_Int32 >( static_cast< double >( maResolution.Width ) * 0.01 * mxMfSizeX->get_value() / 100.0 + 0.5 ); break; + case MapUnit::MapMM : maSize.Width = static_cast< sal_Int32 >( static_cast< double >( maResolution.Width ) * 0.001 * mxMfSizeX->get_value() / 100.0 + 0.5 ); break; + case MapUnit::MapPoint : maSize.Width = static_cast< sal_Int32 >( static_cast< double >( maResolution.Width ) * 0.0254 * mxMfSizeX->get_value() / 100.0 * 72 + 0.5 ); break; + default: + case MapUnit::MapPixel : maSize.Width = mxMfSizeX->get_value(); break; + } + maSize.Height = static_cast< sal_Int32 >( fRatio * maSize.Width + 0.5 ); + } + else + { + Fraction aFract( 1, 100 ); + sal_Int32 nWidth = mxMfSizeX->get_value(); + sal_Int32 nHeight= static_cast< sal_Int32 >( nWidth * fRatio ); + const Size aSource( nWidth, nHeight ); + MapMode aSourceMapMode( GetMapUnit( mxLbSizeX->get_active() ),Point(), aFract, aFract ); + Size aDest(OutputDevice::LogicToLogic(aSource, aSourceMapMode, MapMode(MapUnit::Map100thMM))); + + maSize.Width = aDest.Width(); + maSize.Height = aDest.Height(); + } + updateControls(); +} + +IMPL_LINK_NOARG(ExportDialog, UpdateHdlMtfSizeY, weld::SpinButton&, void) +{ + double fRatio = static_cast< double >( maOriginalSize.Width ) / maOriginalSize.Height; + + if ( mbIsPixelFormat ) + { + switch( GetMapUnit( mxLbSizeX->get_active() ) ) + { + case MapUnit::MapInch : maSize.Height = static_cast< sal_Int32 >( static_cast< double >( maResolution.Height ) * 0.0254 * mxMfSizeY->get_value() / 100.0 + 0.5 ); break; + case MapUnit::MapCM : maSize.Height = static_cast< sal_Int32 >( static_cast< double >( maResolution.Height ) * 0.01 * mxMfSizeY->get_value() / 100.0 + 0.5 ); break; + case MapUnit::MapMM : maSize.Height = static_cast< sal_Int32 >( static_cast< double >( maResolution.Height ) * 0.001 * mxMfSizeY->get_value() / 100.0 + 0.5 ); break; + case MapUnit::MapPoint : maSize.Height = static_cast< sal_Int32 >( static_cast< double >( maResolution.Height ) * 0.0254 * mxMfSizeY->get_value() / 100.0 * 72 + 0.5 ); break; + default: + case MapUnit::MapPixel : maSize.Height = mxMfSizeY->get_value(); break; + } + maSize.Width = static_cast< sal_Int32 >( fRatio * maSize.Height + 0.5 ); + } + else + { + Fraction aFract( 1, 100 ); + sal_Int32 nHeight= mxMfSizeY->get_value(); + sal_Int32 nWidth = static_cast< sal_Int32 >( nHeight * fRatio ); + const Size aSource( nWidth, nHeight ); + MapMode aSourceMapMode( GetMapUnit( mxLbSizeX->get_active() ),Point(), aFract, aFract ); + Size aDest( OutputDevice::LogicToLogic(aSource, aSourceMapMode, MapMode(MapUnit::Map100thMM)) ); + + maSize.Height = aDest.Height(); + maSize.Width = aDest.Width(); + } + updateControls(); +} + +IMPL_LINK_NOARG(ExportDialog, UpdateHdlNfResolution, weld::SpinButton&, void) +{ + auto nResolution = mxNfResolution->get_value(); + if ( mxLbResolution->get_active() == 0 ) // pixels / cm + nResolution *= 100; + else if ( mxLbResolution->get_active() == 1 ) // pixels / inch + nResolution = static_cast< sal_Int32 >( ( ( static_cast< double >( nResolution ) + 0.5 ) / 0.0254 ) ); + maResolution.Width = nResolution; + maResolution.Height= nResolution; + + updateControls(); +} + +IMPL_LINK_NOARG(ExportDialog, SbCompressionUpdateHdl, weld::Scale&, void) +{ + mpNfCompression->set_value(mpSbCompression->get_value()); + updateControls(); +} + +IMPL_LINK_NOARG(ExportDialog, OK, weld::Button&, void) +{ + // writing config parameter + + mrFltCallPara.aFilterData = GetFilterData( true ); + m_xDialog->response(RET_OK); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/filter/exportdialog.hxx b/svtools/source/filter/exportdialog.hxx new file mode 100644 index 000000000..9b10d3411 --- /dev/null +++ b/svtools/source/filter/exportdialog.hxx @@ -0,0 +1,186 @@ +/* -*- 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 <FltCallDialogParameter.hxx> +#include <vcl/weld.hxx> +#include <tools/stream.hxx> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/drawing/XDrawPage.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <memory> + +/************************************************************************* +|* +|* dialog to set filter options for pixel formats +|* +\************************************************************************/ + +class FilterConfigItem; +class ExportDialog : public weld::GenericDialogController +{ +private: + + FltCallDialogParameter& mrFltCallPara; + + const css::uno::Reference< css::uno::XComponentContext > + mxContext; + const css::uno::Reference< css::lang::XComponent >& + mxSourceDocument; + const css::uno::Reference< css::graphic::XGraphic >& + mxGraphic; + + OUString msEstimatedSizePix1; + OUString msEstimatedSizePix2; + OUString msEstimatedSizeVec; + + OUString ms1BitThreshold; + OUString ms8BitGrayscale; + OUString ms8BitColorPalette; + OUString ms24BitColor; + + std::unique_ptr<FilterConfigItem> mpOptionsItem; + std::unique_ptr<FilterConfigItem> mpFilterOptionsItem; + + OUString maExt; + sal_Int16 mnFormat; + sal_Int32 mnMaxFilesizeForRealtimePreview; + + std::unique_ptr<SvMemoryStream> mpTempStream; + + css::awt::Size maOriginalSize; // the original graphic size in 1/100mm + css::awt::Size maSize; // for vector graphics it always contains the logical size in 1/100mm + + bool mbIsPixelFormat; + bool mbExportSelection; + bool mbGraphicsSource; // whether source document is graphics (Draw, Impress) or not (Calc, Writer) + + sal_Int32 mnInitialResolutionUnit; + + // for pixel graphics it always contains the pixel count + css::awt::Size maResolution; // it always contains the number of pixels per meter + + css::uno::Reference< css::drawing::XShape > + mxShape; + css::uno::Reference< css::drawing::XShapes > + mxShapes; + css::uno::Reference< css::drawing::XDrawPage > + mxPage; + + weld::Scale* mpSbCompression; + weld::SpinButton* mpNfCompression; + + std::unique_ptr<weld::SpinButton> mxMfSizeX; + std::unique_ptr<weld::ComboBox> mxLbSizeX; + std::unique_ptr<weld::SpinButton> mxMfSizeY; + std::unique_ptr<weld::Label> mxFtResolution; + std::unique_ptr<weld::SpinButton> mxNfResolution; + std::unique_ptr<weld::ComboBox> mxLbResolution; + + std::unique_ptr<weld::Widget> mxColorDepth; + std::unique_ptr<weld::ComboBox> mxLbColorDepth; + + std::unique_ptr<weld::Widget> mxJPGWEBPQuality; + std::unique_ptr<weld::Widget> mxPNGCompression; + + std::unique_ptr<weld::Scale> mxSbPngCompression; + std::unique_ptr<weld::SpinButton> mxNfPngCompression; + + std::unique_ptr<weld::Scale> mxSbJpgWebpCompression; + std::unique_ptr<weld::SpinButton> mxNfJpgWebpCompression; + + std::unique_ptr<weld::CheckButton> mxCbLossless; + + std::unique_ptr<weld::Widget> mxMode; + std::unique_ptr<weld::CheckButton> mxCbInterlaced; + + std::unique_ptr<weld::Widget> mxBMPCompression; + std::unique_ptr<weld::CheckButton> mxCbRLEEncoding; + + std::unique_ptr<weld::Widget> mxDrawingObjects; + std::unique_ptr<weld::CheckButton> mxCbSaveTransparency; + + std::unique_ptr<weld::Widget> mxEncoding; + std::unique_ptr<weld::RadioButton> mxRbBinary; + std::unique_ptr<weld::RadioButton> mxRbText; + + std::unique_ptr<weld::Widget> mxEPSGrid; + std::unique_ptr<weld::RadioButton> mxModifyDimension; + std::unique_ptr<weld::RadioButton> mxModifyResolution; + std::unique_ptr<weld::CheckButton> mxCbEPSPreviewTIFF; + std::unique_ptr<weld::CheckButton> mxCbEPSPreviewEPSI; + std::unique_ptr<weld::RadioButton> mxRbEPSLevel1; + std::unique_ptr<weld::RadioButton> mxRbEPSLevel2; + std::unique_ptr<weld::RadioButton> mxRbEPSColorFormat1; + std::unique_ptr<weld::RadioButton> mxRbEPSColorFormat2; + std::unique_ptr<weld::RadioButton> mxRbEPSCompressionLZW; + std::unique_ptr<weld::RadioButton> mxRbEPSCompressionNone; + + + std::unique_ptr<weld::Widget> mxInfo; + std::unique_ptr<weld::Label> mxFtEstimatedSize; + + std::unique_ptr<weld::Button> mxBtnOK; + + DECL_LINK(UpdateHdl, weld::Toggleable&, void); + DECL_LINK(UpdateLock, weld::Toggleable&, void); + DECL_LINK(SelectListBoxHdl, weld::ComboBox&, void); + DECL_LINK(SelectHdl, weld::SpinButton&, void); + DECL_LINK(UpdateHdlMtfSizeX, weld::SpinButton&, void); + DECL_LINK(UpdateHdlMtfSizeY, weld::SpinButton&, void); + DECL_LINK(UpdateHdlNfResolution, weld::SpinButton&, void); + DECL_LINK(SbCompressionUpdateHdl, weld::Scale&, void); + DECL_LINK(UpdateHdlLossless, weld::Toggleable&, void); + + DECL_LINK(OK, weld::Button&, void); + + void setupSizeControls(); + void createFilterOptions(); + void setupControls(); + void updateControls(); + + void GetGraphicSource(); + void GetGraphicStream(); + css::uno::Sequence< css::beans::PropertyValue > + GetFilterData( bool bUpdateConfig ); + + sal_uInt32 GetRawFileSize() const; + bool IsTempExportAvailable() const; + + css::awt::Size GetOriginalSize(); + + sal_Int32 GetDefaultUnit() const; + +public: + ExportDialog( FltCallDialogParameter& rPara, + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Reference< css::lang::XComponent >& rxSourceDocument, + bool bExportSelection, bool bIsExportVectorFormat, bool bGraphicsSource, + const css::uno::Reference< css::graphic::XGraphic >& rxGraphic); + virtual ~ExportDialog() override; +}; + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/graphic/renderer.cxx b/svtools/source/graphic/renderer.cxx new file mode 100644 index 000000000..bbd151c6b --- /dev/null +++ b/svtools/source/graphic/renderer.cxx @@ -0,0 +1,296 @@ +/* -*- 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 <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/awt/XDevice.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/graphic/XGraphicRenderer.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <tools/gen.hxx> +#include <vcl/svapp.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <comphelper/propertysethelper.hxx> +#include <comphelper/propertysetinfo.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weakagg.hxx> +#include <rtl/ref.hxx> +#include <vcl/GraphicObject.hxx> +#include <vcl/outdev.hxx> + +#define UNOGRAPHIC_DEVICE 1 +#define UNOGRAPHIC_DESTINATIONRECT 2 +#define UNOGRAPHIC_RENDERDATA 3 + +using namespace ::com::sun::star; + +namespace { + +class GraphicRendererVCL : public ::cppu::OWeakAggObject, + public css::lang::XServiceInfo, + public css::lang::XTypeProvider, + public ::comphelper::PropertySetHelper, + public css::graphic::XGraphicRenderer +{ + static rtl::Reference<::comphelper::PropertySetInfo> createPropertySetInfo(); + +public: + + GraphicRendererVCL(); + + // XInterface + virtual css::uno::Any SAL_CALL queryAggregation( const css::uno::Type & rType ) override; + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + // PropertySetHelper + virtual void _setPropertyValues( const comphelper::PropertyMapEntry** ppEntries, const css::uno::Any* pValues ) override; + virtual void _getPropertyValues( const comphelper::PropertyMapEntry** ppEntries, css::uno::Any* pValue ) override; + + // XGraphicRenderer + virtual void SAL_CALL render( const css::uno::Reference< css::graphic::XGraphic >& Graphic ) override; + +private: + + css::uno::Reference< css::awt::XDevice > mxDevice; + + VclPtr<OutputDevice> mpOutDev; + tools::Rectangle maDestRect; + css::uno::Any maRenderData; +}; + +GraphicRendererVCL::GraphicRendererVCL() : + ::comphelper::PropertySetHelper( createPropertySetInfo() ), + mpOutDev( nullptr ) +{ +} + +uno::Any SAL_CALL GraphicRendererVCL::queryAggregation( const uno::Type & rType ) +{ + uno::Any aAny; + + if( rType == cppu::UnoType<lang::XServiceInfo>::get()) + aAny <<= uno::Reference< lang::XServiceInfo >(this); + else if( rType == cppu::UnoType<lang::XTypeProvider>::get()) + aAny <<= uno::Reference< lang::XTypeProvider >(this); + else if( rType == cppu::UnoType<beans::XPropertySet>::get()) + aAny <<= uno::Reference< beans::XPropertySet >(this); + else if( rType == cppu::UnoType<beans::XPropertyState>::get()) + aAny <<= uno::Reference< beans::XPropertyState >(this); + else if( rType == cppu::UnoType<beans::XMultiPropertySet>::get()) + aAny <<= uno::Reference< beans::XMultiPropertySet >(this); + else if( rType == cppu::UnoType<graphic::XGraphicRenderer>::get()) + aAny <<= uno::Reference< graphic::XGraphicRenderer >(this); + else + aAny = OWeakAggObject::queryAggregation( rType ); + + return aAny; +} + + +uno::Any SAL_CALL GraphicRendererVCL::queryInterface( const uno::Type & rType ) +{ + return OWeakAggObject::queryInterface( rType ); +} + + +void SAL_CALL GraphicRendererVCL::acquire() + noexcept +{ + OWeakAggObject::acquire(); +} + + +void SAL_CALL GraphicRendererVCL::release() + noexcept +{ + OWeakAggObject::release(); +} + + +OUString SAL_CALL GraphicRendererVCL::getImplementationName() +{ + return "com.sun.star.comp.graphic.GraphicRendererVCL"; +} + +sal_Bool SAL_CALL GraphicRendererVCL::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + + +uno::Sequence< OUString > SAL_CALL GraphicRendererVCL::getSupportedServiceNames() +{ + return { "com.sun.star.graphic.GraphicRendererVCL" }; +} + + +uno::Sequence< uno::Type > SAL_CALL GraphicRendererVCL::getTypes() +{ + static const uno::Sequence< uno::Type > aTypes { + cppu::UnoType<uno::XAggregation>::get(), + cppu::UnoType<lang::XServiceInfo>::get(), + cppu::UnoType<lang::XTypeProvider>::get(), + cppu::UnoType<beans::XPropertySet>::get(), + cppu::UnoType<beans::XPropertyState>::get(), + cppu::UnoType<beans::XMultiPropertySet>::get(), + cppu::UnoType<graphic::XGraphicRenderer>::get() }; + return aTypes; +} + +uno::Sequence< sal_Int8 > SAL_CALL GraphicRendererVCL::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + + +rtl::Reference<::comphelper::PropertySetInfo> GraphicRendererVCL::createPropertySetInfo() +{ + static ::comphelper::PropertyMapEntry const aEntries[] = + { + { OUString("Device"), UNOGRAPHIC_DEVICE, cppu::UnoType<uno::Any>::get(), 0, 0 }, + { OUString("DestinationRect"), UNOGRAPHIC_DESTINATIONRECT, cppu::UnoType<awt::Rectangle>::get(), 0, 0 }, + { OUString("RenderData"), UNOGRAPHIC_RENDERDATA, cppu::UnoType<uno::Any>::get(), 0, 0 }, + }; + + return rtl::Reference<::comphelper::PropertySetInfo>( new ::comphelper::PropertySetInfo(aEntries) ); +} + + +void GraphicRendererVCL::_setPropertyValues( const comphelper::PropertyMapEntry** ppEntries, const uno::Any* pValues ) +{ + SolarMutexGuard aGuard; + + while( *ppEntries ) + { + switch( (*ppEntries)->mnHandle ) + { + case UNOGRAPHIC_DEVICE: + { + uno::Reference< awt::XDevice > xDevice; + + if( ( *pValues >>= xDevice ) && xDevice.is() ) + { + mxDevice = xDevice; + mpOutDev = VCLUnoHelper::GetOutputDevice( xDevice ); + } + else + { + mxDevice.clear(); + mpOutDev = nullptr; + } + } + break; + + case UNOGRAPHIC_DESTINATIONRECT: + { + awt::Rectangle aAWTRect; + + if( *pValues >>= aAWTRect ) + { + maDestRect = tools::Rectangle( Point( aAWTRect.X, aAWTRect.Y ), + Size( aAWTRect.Width, aAWTRect.Height ) ); + } + } + break; + + case UNOGRAPHIC_RENDERDATA: + { + maRenderData = *pValues; + } + break; + } + + ++ppEntries; + ++pValues; + } +} + + +void GraphicRendererVCL::_getPropertyValues( const comphelper::PropertyMapEntry** ppEntries, uno::Any* pValues ) +{ + SolarMutexGuard aGuard; + + while( *ppEntries ) + { + switch( (*ppEntries)->mnHandle ) + { + case UNOGRAPHIC_DEVICE: + { + if( mxDevice.is() ) + *pValues <<= mxDevice; + } + break; + + case UNOGRAPHIC_DESTINATIONRECT: + { + const awt::Rectangle aAWTRect( maDestRect.Left(), maDestRect.Top(), + maDestRect.GetWidth(), maDestRect.GetHeight() ); + + *pValues <<= aAWTRect; + } + break; + + case UNOGRAPHIC_RENDERDATA: + { + *pValues = maRenderData; + } + break; + } + + ++ppEntries; + ++pValues; + } +} + +void SAL_CALL GraphicRendererVCL::render( const uno::Reference< graphic::XGraphic >& rxGraphic ) +{ + if( mpOutDev && mxDevice.is() && rxGraphic.is() ) + { + Graphic aGraphic(rxGraphic); + if (!aGraphic.IsNone()) + { + GraphicObject aGraphicObject(aGraphic); + aGraphicObject.Draw(*mpOutDev, maDestRect.TopLeft(), maDestRect.GetSize()); + } + } +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_graphic_GraphicRendererVCL_get_implementation( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new GraphicRendererVCL); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/inc/unoiface.hxx b/svtools/source/inc/unoiface.hxx new file mode 100644 index 000000000..fbfdc1b61 --- /dev/null +++ b/svtools/source/inc/unoiface.hxx @@ -0,0 +1,29 @@ +/* -*- 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 + +namespace com::sun::star::util +{ +class XNumberFormatsSupplier; +} + +class SvNumberFormatsSupplierObj; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/java/javacontext.cxx b/svtools/source/java/javacontext.cxx new file mode 100644 index 000000000..a7d97f77d --- /dev/null +++ b/svtools/source/java/javacontext.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 <comphelper/lok.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Type.hxx> +#include <svtools/javacontext.hxx> +#include <svtools/javainteractionhandler.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::task; +namespace svt +{ + +JavaContext::JavaContext( const Reference< XCurrentContext > & ctx) + : m_aRefCount(0), + m_xNextContext( ctx ) +{ +} + +JavaContext::~JavaContext() +{ +} + +Any SAL_CALL JavaContext::queryInterface(const Type& aType ) +{ + if (aType == cppu::UnoType<XInterface>::get()) + return Any(Reference<XInterface>(static_cast<XInterface*>(this))); + else if (aType == cppu::UnoType<XCurrentContext>::get()) + return Any(Reference<XCurrentContext>( static_cast<XCurrentContext*>(this))); + return Any(); +} + +void SAL_CALL JavaContext::acquire( ) noexcept +{ + osl_atomic_increment( &m_aRefCount ); +} + +void SAL_CALL JavaContext::release( ) noexcept +{ + if (! osl_atomic_decrement( &m_aRefCount )) + delete this; +} + +Any SAL_CALL JavaContext::getValueByName( const OUString& Name) +{ + Any retVal; + + if ( Name == JAVA_INTERACTION_HANDLER_NAME ) + { + if ( !comphelper::LibreOfficeKit::isActive() ) + { + osl::MutexGuard aGuard(osl::Mutex::getGlobalMutex()); + if (!m_xHandler.is()) + m_xHandler.set( new JavaInteractionHandler ); + } + retVal <<= m_xHandler; + + } + else if( m_xNextContext.is() ) + { + // Call next context in chain if found + retVal = m_xNextContext->getValueByName( Name ); + } + return retVal; +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/java/javainteractionhandler.cxx b/svtools/source/java/javainteractionhandler.cxx new file mode 100644 index 000000000..c032f4114 --- /dev/null +++ b/svtools/source/java/javainteractionhandler.cxx @@ -0,0 +1,256 @@ +/* -*- 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 <svtools/strings.hrc> +#include <com/sun/star/task/XInteractionContinuation.hpp> +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/task/XInteractionRetry.hpp> +#include <com/sun/star/java/JavaNotFoundException.hpp> +#include <com/sun/star/java/InvalidJavaSettingsException.hpp> +#include <com/sun/star/java/JavaDisabledException.hpp> +#include <com/sun/star/java/JavaVMCreationFailureException.hpp> +#include <com/sun/star/java/RestartRequiredException.hpp> +#include <comphelper/processfactory.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <jvmfwk/framework.hxx> + +#include <svtools/restartdialog.hxx> +#include <svtools/svtresid.hxx> +#include <svtools/javainteractionhandler.hxx> +#include <unotools/configmgr.hxx> +#include <officecfg/Office/Common.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::task; + +namespace +{ +struct JavaEvents { + bool bDisabledHandled : 1; + bool bInvalidSettingsHandled : 1; + bool bNotFoundHandled : 1; + bool bVMCreationFailureHandled : 1; + bool bRestartRequiredHandled : 1; + sal_uInt16 nResult_JavaDisabled = RET_NO; + JavaEvents() + : bDisabledHandled(false) + , bInvalidSettingsHandled(false) + , bNotFoundHandled(false) + , bVMCreationFailureHandled(false) + , bRestartRequiredHandled(false) + {} +} g_JavaEvents; +} + +namespace svt +{ + +JavaInteractionHandler::JavaInteractionHandler() : + m_aRefCount(0) +{ +} + +JavaInteractionHandler::~JavaInteractionHandler() +{ +} + +Any SAL_CALL JavaInteractionHandler::queryInterface(const Type& aType ) +{ + if (aType == cppu::UnoType<XInterface>::get()) + return Any( static_cast<XInterface*>(this), aType); + else if (aType == cppu::UnoType<XInteractionHandler>::get()) + return Any( static_cast<XInteractionHandler*>(this), aType); + return Any(); +} + +void SAL_CALL JavaInteractionHandler::acquire( ) noexcept +{ + osl_atomic_increment( &m_aRefCount ); +} + +void SAL_CALL JavaInteractionHandler::release( ) noexcept +{ + if (! osl_atomic_decrement( &m_aRefCount )) + delete this; +} + +void SAL_CALL JavaInteractionHandler::handle( const Reference< XInteractionRequest >& Request ) +{ + Any anyExc = Request->getRequest(); + const Sequence< Reference< XInteractionContinuation > > aSeqCont = Request->getContinuations(); + + Reference< XInteractionAbort > abort; + Reference< XInteractionRetry > retry; + + for ( const auto& rCont : aSeqCont ) + { + abort.set( rCont, UNO_QUERY ); + if ( abort.is() ) + break; + } + + for ( const auto& rCont : aSeqCont ) + { + retry.set( rCont, UNO_QUERY ); + if ( retry.is() ) + break; + } + + css::java::JavaNotFoundException e1; + css::java::InvalidJavaSettingsException e2; + css::java::JavaDisabledException e3; + css::java::JavaVMCreationFailureException e4; + css::java::RestartRequiredException e5; + // Try to recover the Exception type in the any and + // react accordingly. + sal_uInt16 nResult = RET_CANCEL; + + if ( anyExc >>= e1 ) + { + SolarMutexGuard aSolarGuard; + if( !g_JavaEvents.bNotFoundHandled ) + { + // No suitable JRE found + OUString sPrimTex; + OUString urlLink(officecfg::Office::Common::Menus::InstallJavaURL::get() + // https://hub.libreoffice.org/InstallJava/ + "?LOlocale=" + utl::ConfigManager::getUILocale()); + g_JavaEvents.bNotFoundHandled = true; +#if defined(MACOSX) + sPrimTex = SvtResId(STR_WARNING_JAVANOTFOUND_MAC); +#elif defined(_WIN32) + sPrimTex = SvtResId(STR_WARNING_JAVANOTFOUND_WIN); +#if defined(_WIN64) + sPrimTex = sPrimTex.replaceAll("%BITNESS", "64"); +#else + sPrimTex = sPrimTex.replaceAll("%BITNESS", "32"); +#endif +#else + sPrimTex = SvtResId(STR_WARNING_JAVANOTFOUND); +#endif + sPrimTex = sPrimTex.replaceAll("%FAQLINK", urlLink); + std::unique_ptr<weld::MessageDialog> xWarningBox(Application::CreateMessageDialog( + nullptr, VclMessageType::Warning, VclButtonsType::Ok, sPrimTex)); + xWarningBox->set_title(SvtResId(STR_WARNING_JAVANOTFOUND_TITLE)); + nResult = xWarningBox->run(); + } + else + { + nResult = RET_OK; + } + } + else if ( anyExc >>= e2 ) + { + SolarMutexGuard aSolarGuard; + if( !g_JavaEvents.bInvalidSettingsHandled ) + { + // javavendors.xml was updated and Java has not been configured yet + g_JavaEvents.bInvalidSettingsHandled = true; +#ifdef MACOSX + OUString sWarning(SvtResId(STR_WARNING_INVALIDJAVASETTINGS_MAC)); +#else + OUString sWarning(SvtResId(STR_WARNING_INVALIDJAVASETTINGS)); +#endif + std::unique_ptr<weld::MessageDialog> xWarningBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Warning, VclButtonsType::Ok, sWarning)); + xWarningBox->set_title(SvtResId(STR_WARNING_INVALIDJAVASETTINGS_TITLE)); + nResult = xWarningBox->run(); + } + else + { + nResult = RET_OK; + } + } + else if ( anyExc >>= e3 ) + { + SolarMutexGuard aSolarGuard; + if( !g_JavaEvents.bDisabledHandled ) + { + g_JavaEvents.bDisabledHandled = true; + // Java disabled. Give user a chance to enable Java inside Office. + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(nullptr, "svt/ui/javadisableddialog.ui")); + std::unique_ptr<weld::MessageDialog> xQueryBox(xBuilder->weld_message_dialog("JavaDisabledDialog")); + nResult = xQueryBox->run(); + if ( nResult == RET_YES ) + { + jfw_setEnabled(true); + } + + g_JavaEvents.nResult_JavaDisabled = nResult; + + } + else + { + nResult = g_JavaEvents.nResult_JavaDisabled; + } + } + else if ( anyExc >>= e4 ) + { + SolarMutexGuard aSolarGuard; + if( !g_JavaEvents.bVMCreationFailureHandled ) + { + // Java not correctly installed, or damaged + g_JavaEvents.bVMCreationFailureHandled = true; +#ifdef MACOSX + OUString sWarning(SvtResId(STR_ERROR_JVMCREATIONFAILED_MAC)); +#else + OUString sWarning(SvtResId(STR_ERROR_JVMCREATIONFAILED)); +#endif + std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Warning, VclButtonsType::Ok, sWarning)); + xErrorBox->set_title(SvtResId(STR_ERROR_JVMCREATIONFAILED_TITLE)); + nResult = xErrorBox->run(); + } + else + { + nResult = RET_OK; + } + } + else if ( anyExc >>= e5 ) + { + SolarMutexGuard aSolarGuard; + if( !g_JavaEvents.bRestartRequiredHandled ) + { + // a new JRE was selected, but office needs to be restarted + //before it can be used. + g_JavaEvents.bRestartRequiredHandled = true; + svtools::executeRestartDialog( + comphelper::getProcessComponentContext(), nullptr, + svtools::RESTART_REASON_JAVA); + } + nResult = RET_OK; + } + + if ( nResult == RET_CANCEL || nResult == RET_NO) + { + // Unknown exception type or user wants to cancel + if ( abort.is() ) + abort->select(); + } + else // nResult == RET_OK + { + // User selected OK => retry Java usage + if ( retry.is() ) + retry->select(); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/acceleratorexecute.cxx b/svtools/source/misc/acceleratorexecute.cxx new file mode 100644 index 000000000..eb6d611fe --- /dev/null +++ b/svtools/source/misc/acceleratorexecute.cxx @@ -0,0 +1,491 @@ +/* -*- 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 <svtools/acceleratorexecute.hxx> + +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/ui/GlobalAcceleratorConfiguration.hpp> +#include <com/sun/star/ui/XUIConfigurationManager.hpp> +#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/awt/KeyModifier.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <cppuhelper/implbase.hxx> + +#include <vcl/evntpost.hxx> +#include <sal/log.hxx> +#include <vcl/lok.hxx> +#include <rtl/ref.hxx> + +namespace svt +{ + +namespace { + +class AsyncAccelExec : public cppu::WeakImplHelper<css::lang::XEventListener> +{ + private: + css::uno::Reference<css::lang::XComponent> m_xFrame; + css::uno::Reference< css::frame::XDispatch > m_xDispatch; + css::util::URL m_aURL; + vcl::EventPoster m_aAsyncCallback; + public: + + /** creates a new instance of this class, which can be used + one times only! + + This instance can be forced to execute its internal set request + asynchronous. After that it deletes itself! + */ + static rtl::Reference<AsyncAccelExec> createOneShotInstance(const css::uno::Reference<css::lang::XComponent>& xFrame, + const css::uno::Reference<css::frame::XDispatch>& xDispatch, + const css::util::URL& rURL); + + void execAsync(); + private: + + virtual void SAL_CALL disposing(const css::lang::EventObject&) override + { + m_xFrame->removeEventListener(this); + m_xFrame.clear(); + m_xDispatch.clear(); + } + + /** @short allow creation of instances of this class + by using our factory only! + */ + AsyncAccelExec(const css::uno::Reference<css::lang::XComponent>& xFrame, + const css::uno::Reference< css::frame::XDispatch >& xDispatch, + const css::util::URL& rURL); + + DECL_LINK(impl_ts_asyncCallback, LinkParamNone*, void); +}; + +} + +AcceleratorExecute::AcceleratorExecute() +{ +} + +AcceleratorExecute::~AcceleratorExecute() +{ + // does nothing real +} + + +std::unique_ptr<AcceleratorExecute> AcceleratorExecute::createAcceleratorHelper() +{ + return std::unique_ptr<AcceleratorExecute>(new AcceleratorExecute); +} + + +void AcceleratorExecute::init(const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Reference< css::frame::XFrame >& xEnv ) +{ + // SAFE -> ---------------------------------- + std::unique_lock aLock(m_aLock); + + // take over the uno service manager + m_xContext = rxContext; + + // specify our internal dispatch provider + // frame or desktop?! => document or global config. + bool bDesktopIsUsed = false; + m_xDispatcher.set(xEnv, css::uno::UNO_QUERY); + if (!m_xDispatcher.is()) + { + aLock.unlock(); + // <- SAFE ------------------------------ + + css::uno::Reference< css::frame::XDispatchProvider > xDispatcher(css::frame::Desktop::create(rxContext), css::uno::UNO_QUERY_THROW); + + // SAFE -> ------------------------------ + aLock.lock(); + + m_xDispatcher = xDispatcher; + bDesktopIsUsed = true; + } + + aLock.unlock(); + // <- SAFE ---------------------------------- + + // open all needed configuration objects + css::uno::Reference< css::ui::XAcceleratorConfiguration > xGlobalCfg; + css::uno::Reference< css::ui::XAcceleratorConfiguration > xModuleCfg; + css::uno::Reference< css::ui::XAcceleratorConfiguration > xDocCfg ; + + // global cfg + xGlobalCfg = css::ui::GlobalAcceleratorConfiguration::create(rxContext); + if (!bDesktopIsUsed) + { + // module cfg + xModuleCfg = AcceleratorExecute::st_openModuleConfig(rxContext, xEnv); + + // doc cfg + css::uno::Reference< css::frame::XController > xController; + css::uno::Reference< css::frame::XModel > xModel; + xController = xEnv->getController(); + if (xController.is()) + xModel = xController->getModel(); + if (xModel.is()) + xDocCfg = AcceleratorExecute::st_openDocConfig(xModel); + } + + // SAFE -> ------------------------------ + aLock.lock(); + + m_xGlobalCfg = xGlobalCfg; + m_xModuleCfg = xModuleCfg; + m_xDocCfg = xDocCfg ; + + aLock.unlock(); + // <- SAFE ---------------------------------- +} + + +bool AcceleratorExecute::execute(const vcl::KeyCode& aVCLKey) +{ + css::awt::KeyEvent aAWTKey = AcceleratorExecute::st_VCLKey2AWTKey(aVCLKey); + return execute(aAWTKey); +} + + +bool AcceleratorExecute::execute(const css::awt::KeyEvent& aAWTKey) +{ + OUString sCommand = impl_ts_findCommand(aAWTKey); + + // No Command found? Do nothing! User is not interested on any error handling .-) + // or for some reason m_xContext is NULL (which would crash impl_ts_getURLParser() + if (sCommand.isEmpty() || !m_xContext.is()) + { + return false; + } + + // SAFE -> ---------------------------------- + std::unique_lock aLock(m_aLock); + + css::uno::Reference< css::frame::XDispatchProvider > xProvider = m_xDispatcher; + + aLock.unlock(); + // <- SAFE ---------------------------------- + + // convert command in URL structure + css::uno::Reference< css::util::XURLTransformer > xParser = impl_ts_getURLParser(); + css::util::URL aURL; + aURL.Complete = sCommand; + xParser->parseStrict(aURL); + + // ask for dispatch object + css::uno::Reference< css::frame::XDispatch > xDispatch = xProvider->queryDispatch(aURL, OUString(), 0); + bool bRet = xDispatch.is(); + if ( bRet ) + { + // Note: Such instance can be used one times only and destroy itself afterwards .-) + css::uno::Reference<css::lang::XComponent> xFrame(xProvider, css::uno::UNO_QUERY); + if (vcl::lok::isUnipoll()) + { // tdf#130382 - all synchronous really. + try { + xDispatch->dispatch (aURL, css::uno::Sequence< css::beans::PropertyValue >()); + } + catch(const css::uno::Exception&ev) + { + SAL_INFO("svtools", "exception on key emission: " << ev.Message); + } + } + else + { + rtl::Reference<AsyncAccelExec> pExec = AsyncAccelExec::createOneShotInstance(xFrame, xDispatch, aURL); + pExec->execAsync(); + } + } + + return bRet; +} + + +css::awt::KeyEvent AcceleratorExecute::st_VCLKey2AWTKey(const vcl::KeyCode& aVCLKey) +{ + css::awt::KeyEvent aAWTKey; + aAWTKey.Modifiers = 0; + aAWTKey.KeyCode = static_cast<sal_Int16>(aVCLKey.GetCode()); + + if (aVCLKey.IsShift()) + aAWTKey.Modifiers |= css::awt::KeyModifier::SHIFT; + if (aVCLKey.IsMod1()) + aAWTKey.Modifiers |= css::awt::KeyModifier::MOD1; + if (aVCLKey.IsMod2()) + aAWTKey.Modifiers |= css::awt::KeyModifier::MOD2; + if (aVCLKey.IsMod3()) + aAWTKey.Modifiers |= css::awt::KeyModifier::MOD3; + return aAWTKey; +} + + +vcl::KeyCode AcceleratorExecute::st_AWTKey2VCLKey(const css::awt::KeyEvent& aAWTKey) +{ + bool bShift = ((aAWTKey.Modifiers & css::awt::KeyModifier::SHIFT) == css::awt::KeyModifier::SHIFT ); + bool bMod1 = ((aAWTKey.Modifiers & css::awt::KeyModifier::MOD1 ) == css::awt::KeyModifier::MOD1 ); + bool bMod2 = ((aAWTKey.Modifiers & css::awt::KeyModifier::MOD2 ) == css::awt::KeyModifier::MOD2 ); + bool bMod3 = ((aAWTKey.Modifiers & css::awt::KeyModifier::MOD3 ) == css::awt::KeyModifier::MOD3 ); + sal_uInt16 nKey = static_cast<sal_uInt16>(aAWTKey.KeyCode); + + return vcl::KeyCode(nKey, bShift, bMod1, bMod2, bMod3); +} + +OUString AcceleratorExecute::findCommand(const css::awt::KeyEvent& aKey) +{ + return impl_ts_findCommand(aKey); +} + +OUString AcceleratorExecute::impl_ts_findCommand(const css::awt::KeyEvent& aKey) +{ + // SAFE -> ---------------------------------- + std::unique_lock aLock(m_aLock); + + css::uno::Reference< css::ui::XAcceleratorConfiguration > xGlobalCfg = m_xGlobalCfg; + css::uno::Reference< css::ui::XAcceleratorConfiguration > xModuleCfg = m_xModuleCfg; + css::uno::Reference< css::ui::XAcceleratorConfiguration > xDocCfg = m_xDocCfg ; + + aLock.unlock(); + // <- SAFE ---------------------------------- + + OUString sCommand; + + try + { + if (xDocCfg.is()) + sCommand = xDocCfg->getCommandByKeyEvent(aKey); + if (!sCommand.isEmpty()) + return sCommand; + } + catch(const css::container::NoSuchElementException&) + {} + + try + { + if (xModuleCfg.is()) + sCommand = xModuleCfg->getCommandByKeyEvent(aKey); + if (!sCommand.isEmpty()) + return sCommand; + } + catch(const css::container::NoSuchElementException&) + {} + + try + { + if (xGlobalCfg.is()) + sCommand = xGlobalCfg->getCommandByKeyEvent(aKey); + if (!sCommand.isEmpty()) + return sCommand; + } + catch(const css::container::NoSuchElementException&) + {} + + // fall back to functional key codes + if( aKey.Modifiers == 0 ) + { + switch( aKey.KeyCode ) + { + case css::awt::Key::DELETE_TO_BEGIN_OF_LINE: + return ".uno:DelToStartOfLine"; + case css::awt::Key::DELETE_TO_END_OF_LINE: + return ".uno:DelToEndOfLine"; + case css::awt::Key::DELETE_TO_BEGIN_OF_PARAGRAPH: + return ".uno:DelToStartOfPara"; + case css::awt::Key::DELETE_TO_END_OF_PARAGRAPH: + return ".uno:DelToEndOfPara"; + case css::awt::Key::DELETE_WORD_BACKWARD: + return ".uno:DelToStartOfWord"; + case css::awt::Key::DELETE_WORD_FORWARD: + return ".uno:DelToEndOfWord"; + case css::awt::Key::INSERT_LINEBREAK: + return ".uno:InsertLinebreak"; + case css::awt::Key::INSERT_PARAGRAPH: + return ".uno:InsertPara"; + case css::awt::Key::MOVE_WORD_BACKWARD: + return ".uno:GoToPrevWord"; + case css::awt::Key::MOVE_WORD_FORWARD: + return ".uno:GoToNextWord"; + case css::awt::Key::MOVE_TO_BEGIN_OF_LINE: + return ".uno:GoToStartOfLine"; + case css::awt::Key::MOVE_TO_END_OF_LINE: + return ".uno:GoToEndOfLine"; + case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH: + return ".uno:GoToStartOfPara"; + case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH: + return ".uno:GoToEndOfPara"; + case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT: + return ".uno:GoToStartOfDoc"; + case css::awt::Key::MOVE_TO_END_OF_DOCUMENT: + return ".uno:GoToEndOfDoc"; + case css::awt::Key::SELECT_BACKWARD: + return ".uno:CharLeftSel"; + case css::awt::Key::SELECT_FORWARD: + return ".uno:CharRightSel"; + case css::awt::Key::SELECT_WORD_BACKWARD: + return ".uno:WordLeftSel"; + case css::awt::Key::SELECT_WORD_FORWARD: + return ".uno:WordRightSel"; + case css::awt::Key::SELECT_WORD: + return ".uno:SelectWord"; + case css::awt::Key::SELECT_LINE: + return OUString(); + case css::awt::Key::SELECT_PARAGRAPH: + return ".uno:SelectText"; + case css::awt::Key::SELECT_TO_BEGIN_OF_LINE: + return ".uno:StartOfLineSel"; + case css::awt::Key::SELECT_TO_END_OF_LINE: + return ".uno:EndOfLineSel"; + case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH: + return ".uno:StartOfParaSel"; + case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH: + return ".uno:EndOfParaSel"; + case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT: + return ".uno:StartOfDocumentSel"; + case css::awt::Key::SELECT_TO_END_OF_DOCUMENT: + return ".uno:EndOfDocumentSel"; + case css::awt::Key::SELECT_ALL: + return ".uno:SelectAll"; + default: + break; + } + } + + return OUString(); +} + + +css::uno::Reference< css::ui::XAcceleratorConfiguration > AcceleratorExecute::st_openModuleConfig(const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Reference< css::frame::XFrame >& xFrame) +{ + css::uno::Reference< css::frame::XModuleManager2 > xModuleDetection( + css::frame::ModuleManager::create(rxContext)); + + OUString sModule; + try + { + sModule = xModuleDetection->identify(xFrame); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { return css::uno::Reference< css::ui::XAcceleratorConfiguration >(); } + + css::uno::Reference< css::ui::XModuleUIConfigurationManagerSupplier > xUISupplier( + css::ui::theModuleUIConfigurationManagerSupplier::get(rxContext) ); + + css::uno::Reference< css::ui::XAcceleratorConfiguration > xAccCfg; + try + { + css::uno::Reference< css::ui::XUIConfigurationManager > xUIManager = xUISupplier->getUIConfigurationManager(sModule); + xAccCfg = xUIManager->getShortCutManager(); + } + catch(const css::container::NoSuchElementException&) + {} + return xAccCfg; +} + + +css::uno::Reference< css::ui::XAcceleratorConfiguration > AcceleratorExecute::st_openDocConfig(const css::uno::Reference< css::frame::XModel >& xModel) +{ + css::uno::Reference< css::ui::XAcceleratorConfiguration > xAccCfg; + css::uno::Reference< css::ui::XUIConfigurationManagerSupplier > xUISupplier(xModel, css::uno::UNO_QUERY); + if (xUISupplier.is()) + { + css::uno::Reference< css::ui::XUIConfigurationManager > xUIManager = xUISupplier->getUIConfigurationManager(); + xAccCfg = xUIManager->getShortCutManager(); + } + return xAccCfg; +} + + +css::uno::Reference< css::util::XURLTransformer > AcceleratorExecute::impl_ts_getURLParser() +{ + // SAFE -> ---------------------------------- + std::unique_lock aLock(m_aLock); + + if (m_xURLParser.is()) + return m_xURLParser; + css::uno::Reference< css::uno::XComponentContext > xContext = m_xContext; + + aLock.unlock(); + // <- SAFE ---------------------------------- + + css::uno::Reference< css::util::XURLTransformer > xParser = css::util::URLTransformer::create( xContext ); + + // SAFE -> ---------------------------------- + aLock.lock(); + m_xURLParser = xParser; + aLock.unlock(); + // <- SAFE ---------------------------------- + + return xParser; +} + +AsyncAccelExec::AsyncAccelExec(const css::uno::Reference<css::lang::XComponent>& xFrame, + const css::uno::Reference<css::frame::XDispatch>& xDispatch, + const css::util::URL& rURL) + : m_xFrame(xFrame) + , m_xDispatch(xDispatch) + , m_aURL(rURL) + , m_aAsyncCallback(LINK(this, AsyncAccelExec, impl_ts_asyncCallback)) +{ + acquire(); +} + +rtl::Reference<AsyncAccelExec> AsyncAccelExec::createOneShotInstance(const css::uno::Reference<css::lang::XComponent> &xFrame, + const css::uno::Reference< css::frame::XDispatch >& xDispatch, + const css::util::URL& rURL) +{ + rtl::Reference<AsyncAccelExec> pExec = new AsyncAccelExec(xFrame, xDispatch, rURL); + return pExec; +} + + +void AsyncAccelExec::execAsync() +{ + if (m_xFrame.is()) + m_xFrame->addEventListener(this); + m_aAsyncCallback.Post(); +} + +IMPL_LINK_NOARG(AsyncAccelExec, impl_ts_asyncCallback, LinkParamNone*, void) +{ + if (m_xDispatch.is()) + { + try + { + if (m_xFrame.is()) + m_xFrame->removeEventListener(this); + m_xDispatch->dispatch(m_aURL, css::uno::Sequence< css::beans::PropertyValue >()); + } + catch(const css::uno::Exception&) + { + } + } + release(); +} + +} // namespace svt + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/bindablecontrolhelper.cxx b/svtools/source/misc/bindablecontrolhelper.cxx new file mode 100644 index 000000000..ba5494975 --- /dev/null +++ b/svtools/source/misc/bindablecontrolhelper.cxx @@ -0,0 +1,144 @@ +/* -*- 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 <svtools/bindablecontrolhelper.hxx> +#include <com/sun/star/form/binding/XBindableValue.hpp> +#include <com/sun/star/form/binding/XValueBinding.hpp> +#include <com/sun/star/form/binding/XListEntrySink.hpp> +#include <com/sun/star/form/binding/XListEntrySource.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/table/CellRangeAddress.hpp> +#include <com/sun/star/sheet/XCellRangeReferrer.hpp> +#include <com/sun/star/sheet/XCellRangeAddressable.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/table/CellAddress.hpp> +#include <com/sun/star/beans/NamedValue.hpp> + + +namespace svt +{ + + +using namespace ::com::sun::star; + +static bool lcl_isNamedRange( const OUString& sAddress, const uno::Reference< frame::XModel >& xModel, css::table::CellRangeAddress& aAddress ) +{ + bool bRes = false; + uno::Reference< sheet::XCellRangeReferrer > xReferrer; + try + { + uno::Reference< beans::XPropertySet > xPropSet( xModel, uno::UNO_QUERY_THROW ); + uno::Reference< container::XNameAccess > xNamed( xPropSet->getPropertyValue( "NamedRanges" ), uno::UNO_QUERY_THROW ); + xReferrer.set ( xNamed->getByName( sAddress ), uno::UNO_QUERY ); + } + catch( uno::Exception& /*e*/ ) + { + // do nothing + } + if ( xReferrer.is() ) + { + uno::Reference< sheet::XCellRangeAddressable > xRangeAddressable( xReferrer->getReferredCells(), uno::UNO_QUERY ); + if ( xRangeAddressable.is() ) + { + aAddress = xRangeAddressable->getRangeAddress(); + bRes = true; + } + } + return bRes; +} + + +void +BindableControlHelper::ApplyListSourceAndBindableData( const css::uno::Reference< css::frame::XModel >& xModel, const css::uno::Reference< css::uno::XInterface >& rObj, const OUString& rsCtrlSource, const OUString& rsRowSource, sal_uInt16 nRefTab ) +{ +// XBindable etc. + uno::Reference< lang::XMultiServiceFactory > xFac; + if ( xModel.is() ) + xFac.set( xModel, uno::UNO_QUERY ); + uno::Reference< form::binding::XBindableValue > xBindable( rObj, uno::UNO_QUERY ); + if ( xFac.is() && rsCtrlSource.getLength() && xBindable.is() ) + { + + // OOo address structures + // RefCell - convert from XL + // pretend we converted the imported string address into the + // appropriate address structure + uno::Reference< beans::XPropertySet > xConvertor( xFac->createInstance( "com.sun.star.table.CellAddressConversion"), uno::UNO_QUERY ); + css::table::CellAddress aAddress; + if ( xConvertor.is() ) + { + // we need this service to properly convert XL notation also + // Should be easy to extend + xConvertor->setPropertyValue( "ReferenceSheet", uno::Any( nRefTab ) ); + xConvertor->setPropertyValue( "XLA1Representation", uno::Any( rsCtrlSource ) ); + xConvertor->getPropertyValue( "Address" ) >>= aAddress; + } + + beans::NamedValue aArg1; + aArg1.Name = "BoundCell"; + aArg1.Value <<= aAddress; + + uno::Sequence< uno::Any > aArgs{ uno::Any(aArg1) }; + uno::Reference< form::binding::XValueBinding > xBinding( xFac->createInstanceWithArguments( "com.sun.star.table.CellValueBinding", aArgs ), uno::UNO_QUERY ); + xBindable->setValueBinding( xBinding ); + } + else if ( xBindable.is() ) // reset it + xBindable->setValueBinding( uno::Reference< form::binding::XValueBinding >() ); + uno::Reference< form::binding::XListEntrySink > xListEntrySink( rObj, uno::UNO_QUERY ); + if ( xFac.is() && rsRowSource.getLength() && xListEntrySink.is() ) + { + + // OOo address structures + // RefCell - convert from XL + // pretend we converted the imported string address into the + // appropriate address structure + uno::Reference< beans::XPropertySet > xConvertor( xFac->createInstance( "com.sun.star.table.CellRangeAddressConversion"), uno::UNO_QUERY ); + css::table::CellRangeAddress aAddress; + if ( xConvertor.is() ) + { + if ( !lcl_isNamedRange( rsRowSource, xModel, aAddress ) ) + { + // we need this service to properly convert XL notation also + // Should be easy to extend + xConvertor->setPropertyValue( "ReferenceSheet", uno::Any( nRefTab ) ); + xConvertor->setPropertyValue( "XLA1Representation", uno::Any( rsRowSource ) ); + xConvertor->getPropertyValue( "Address" ) >>= aAddress; + } + } + + beans::NamedValue aArg1; + aArg1.Name = "CellRange"; + aArg1.Value <<= aAddress; + + uno::Sequence< uno::Any > aArgs{ uno::Any(aArg1) }; + uno::Reference< form::binding::XListEntrySource > xSource( xFac->createInstanceWithArguments( "com.sun.star.table.CellRangeListSource", aArgs ), uno::UNO_QUERY ); + xListEntrySink->setListEntrySource( xSource ); + } + else if ( xListEntrySink.is() ) // reset + xListEntrySink->setListEntrySource( uno::Reference< form::binding::XListEntrySource >() ); + +} + + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/cliplistener.cxx b/svtools/source/misc/cliplistener.cxx new file mode 100644 index 000000000..992d9efb9 --- /dev/null +++ b/svtools/source/misc/cliplistener.cxx @@ -0,0 +1,85 @@ +/* -*- 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 <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp> + +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> + +#include <svtools/cliplistener.hxx> +#include <vcl/transfer.hxx> + +using namespace ::com::sun::star; + + +TransferableClipboardListener::TransferableClipboardListener( const Link<TransferableDataHelper*,void>& rCallback ) : + aLink( rCallback ) +{ +} + +TransferableClipboardListener::~TransferableClipboardListener() +{ +} + +void SAL_CALL TransferableClipboardListener::disposing( const lang::EventObject& ) +{ +} + +void SAL_CALL TransferableClipboardListener::changedContents( + const datatransfer::clipboard::ClipboardEvent& rEventObject ) +{ + if ( aLink.IsSet() ) + { + const SolarMutexGuard aGuard; + + TransferableDataHelper aDataHelper( rEventObject.Contents ); + aLink.Call( &aDataHelper ); + } +} + +void TransferableClipboardListener::AddRemoveListener( vcl::Window* pWin, bool bAdd ) +{ + try + { + if ( pWin ) + { + uno::Reference<datatransfer::clipboard::XClipboard> xClipboard = pWin->GetClipboard(); + uno::Reference<datatransfer::clipboard::XClipboardNotifier> xClpbrdNtfr( xClipboard, uno::UNO_QUERY ); + if( xClpbrdNtfr.is() ) + { + uno::Reference<datatransfer::clipboard::XClipboardListener> xClipEvtLstnr( this ); + if( bAdd ) + xClpbrdNtfr->addClipboardListener( xClipEvtLstnr ); + else + xClpbrdNtfr->removeClipboardListener( xClipEvtLstnr ); + } + } + } + catch( const css::uno::Exception& ) + { + } +} + +void TransferableClipboardListener::ClearCallbackLink() +{ + aLink = Link<TransferableDataHelper*,void>(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/dialogclosedlistener.cxx b/svtools/source/misc/dialogclosedlistener.cxx new file mode 100644 index 000000000..c9e4c095e --- /dev/null +++ b/svtools/source/misc/dialogclosedlistener.cxx @@ -0,0 +1,59 @@ +/* -*- 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 <svtools/dialogclosedlistener.hxx> + + +namespace svt +{ + + + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::ui::dialogs; + + + //= DialogClosedListener + + + DialogClosedListener::DialogClosedListener() + { + } + + // XDialogClosedListener methods + void SAL_CALL DialogClosedListener::dialogClosed( const DialogClosedEvent& aEvent ) + { + if ( m_aDialogClosedLink.IsSet() ) + { + css::ui::dialogs::DialogClosedEvent aEvt( aEvent ); + m_aDialogClosedLink.Call( &aEvt ); + } + } + + // XEventListener methods + void SAL_CALL DialogClosedListener::disposing( const EventObject& ) + { + m_aDialogClosedLink = Link<css::ui::dialogs::DialogClosedEvent*,void>(); + } + + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/ehdl.cxx b/svtools/source/misc/ehdl.cxx new file mode 100644 index 000000000..dab1544ee --- /dev/null +++ b/svtools/source/misc/ehdl.cxx @@ -0,0 +1,298 @@ +/* -*- 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 <unotools/resmgr.hxx> +#include <vcl/stdtext.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <sal/log.hxx> + +#include <svtools/ehdl.hxx> +#include <svtools/svtresid.hxx> +#include <svtools/sfxecode.hxx> +#include <memory> +#include <errtxt.hrc> + +static DialogMask aWndFunc( + weld::Window *pWin, // Parent of the dialog + DialogMask nFlags, + const OUString &rErr, // error text + const OUString &rAction) // action text + +/* [Description] + + Draw an errorbox on the screen. Depending on nFlags + Error/Info etc. boxes with the requested buttons are shown. + + Returnvalue is the button pressed + + */ + + +{ + SolarMutexGuard aGuard; + + // determine necessary WinBits from the flags + VclButtonsType eButtonsType = VclButtonsType::NONE; + bool bAddRetry = false; + if ( (nFlags & (DialogMask::ButtonsCancel | DialogMask::ButtonsRetry)) == (DialogMask::ButtonsCancel | DialogMask::ButtonsRetry)) + { + bAddRetry = true; + eButtonsType = VclButtonsType::Cancel; + } + else if ( (nFlags & DialogMask::ButtonsOk) == DialogMask::ButtonsOk ) + eButtonsType = VclButtonsType::Ok; + else if ( (nFlags & DialogMask::ButtonsYesNo) == DialogMask::ButtonsYesNo ) + eButtonsType = VclButtonsType::YesNo; + + OUString aErr("$(ACTION)$(ERROR)"); + OUString aAction(rAction); + if ( !aAction.isEmpty() ) + aAction += ":\n"; + aErr = aErr.replaceAll("$(ACTION)", aAction); + aErr = aErr.replaceAll("$(ERROR)", rErr); + + VclMessageType eMessageType; + switch (nFlags & DialogMask(0xf000)) + { + case DialogMask::MessageError: + eMessageType = VclMessageType::Error; + break; + + case DialogMask::MessageWarning: + eMessageType = VclMessageType::Warning; + break; + + case DialogMask::MessageInfo: + eMessageType = VclMessageType::Info; + break; + + default: + { + SAL_WARN( "svtools.misc", "no MessBox type"); + return DialogMask::ButtonsOk; + } + } + + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin, + eMessageType, eButtonsType, aErr)); + + if (bAddRetry) + xBox->add_button(GetStandardText(StandardButtonType::Retry), RET_RETRY); + + switch(nFlags & DialogMask(0x0f00)) + { + case DialogMask::ButtonDefaultsOk: + xBox->set_default_response(RET_OK); + break; + case DialogMask::ButtonDefaultsCancel: + xBox->set_default_response(RET_CANCEL); + break; + case DialogMask::ButtonDefaultsYes: + xBox->set_default_response(RET_YES); + break; + case DialogMask::ButtonDefaultsNo: + xBox->set_default_response(RET_NO); + break; + default: + break; + } + + DialogMask nRet = DialogMask::NONE; + switch (xBox->run()) + { + case RET_OK: + nRet = DialogMask::ButtonsOk; + break; + case RET_CANCEL: + nRet = DialogMask::ButtonsCancel; + break; + case RET_RETRY: + nRet = DialogMask::ButtonsRetry; + break; + case RET_YES: + nRet = DialogMask::ButtonsYes; + break; + case RET_NO: + nRet = DialogMask::ButtonsNo; + break; + default: + SAL_WARN( "svtools.misc", "Unknown MessBox return value" ); + break; + } + + return nRet; +} + +SfxErrorHandler::SfxErrorHandler(const ErrMsgCode* pIdPs, ErrCodeArea lStartP, ErrCodeArea lEndP, const std::locale& rLocale) + : lStart(lStartP), lEnd(lEndP), pIds(pIdPs), aResLocale(rLocale) +{ + ErrorRegistry::RegisterDisplay(&aWndFunc); +} + +SfxErrorHandler::~SfxErrorHandler() +{ +} + +bool SfxErrorHandler::CreateString(const ErrorInfo *pErr, OUString &rStr) const + +/* [Description] + + Assemble error string for the ErrorInfo pErr. + + */ + +{ + ErrCode nErrCode(sal_uInt32(pErr->GetErrorCode()) & ERRCODE_ERROR_MASK); + if (pErr->GetErrorCode().GetArea() < lStart || lEnd < pErr->GetErrorCode().GetArea()) + return false; + if(GetErrorString(nErrCode, rStr)) + { + const StringErrorInfo *pStringInfo = dynamic_cast<const StringErrorInfo *>(pErr); + if(pStringInfo) + { + rStr = rStr.replaceAll("$(ARG1)", pStringInfo->GetErrorString()); + } + else + { + const TwoStringErrorInfo * pTwoStringInfo = dynamic_cast<const TwoStringErrorInfo* >(pErr); + if (pTwoStringInfo) + { + rStr = rStr.replaceAll("$(ARG1)", pTwoStringInfo->GetArg1()); + rStr = rStr.replaceAll("$(ARG2)", pTwoStringInfo->GetArg2()); + } + } + return true; + } + return false; +} + +void SfxErrorHandler::GetClassString(ErrCodeClass lClassId, OUString &rStr) + +/* [Description] + + Creates the string for the class of the error. Will always + be read from the resource of the Sfx. + + */ + +{ + for (const std::pair<TranslateId, ErrCodeClass>* pItem = RID_ERRHDL_CLASS; pItem->first; ++pItem) + { + if (pItem->second == lClassId) + { + rStr = SvtResId(pItem->first); + break; + } + } +} + +bool SfxErrorHandler::GetErrorString(ErrCode lErrId, OUString &rStr) const + +/* [Description] + + Creates the error string for the actual error + without its class + + */ + +{ + bool bRet = false; + rStr = "$(CLASS)$(ERROR)"; + + for (const ErrMsgCode* pItem = pIds; pItem->second; ++pItem) + { + if (pItem->second.StripWarningAndDynamic() == lErrId.StripWarningAndDynamic()) + { + rStr = rStr.replaceAll("$(ERROR)", Translate::get(pItem->first, aResLocale)); + bRet = true; + break; + } + } + + if( bRet ) + { + OUString aErrStr; + GetClassString(lErrId.GetClass(), aErrStr); + if(!aErrStr.isEmpty()) + aErrStr += ".\n"; + rStr = rStr.replaceAll("$(CLASS)",aErrStr); + } + + return bRet; +} + +SfxErrorContext::SfxErrorContext( + sal_uInt16 nCtxIdP, weld::Window *pWindow, const ErrMsgCode* pIdsP, const std::locale& rResLocaleP) +: ErrorContext(pWindow), nCtxId(nCtxIdP), pIds(pIdsP), aResLocale(rResLocaleP) +{ + if (!pIds) + pIds = RID_ERRCTX; +} + + +SfxErrorContext::SfxErrorContext( + sal_uInt16 nCtxIdP, const OUString &aArg1P, weld::Window *pWindow, + const ErrMsgCode* pIdsP, const std::locale& rResLocaleP) +: ErrorContext(pWindow), nCtxId(nCtxIdP), pIds(pIdsP), aResLocale(rResLocaleP), + aArg1(aArg1P) +{ + if (!pIds) + pIds = RID_ERRCTX; +} + +bool SfxErrorContext::GetString(ErrCode nErrId, OUString &rStr) + +/* [Description] + + Constructs the description of an error context + */ + +{ + bool bRet = false; + for (const ErrMsgCode* pItem = pIds; pItem->second; ++pItem) + { + if (sal_uInt32(pItem->second) == nCtxId) + { + rStr = Translate::get(pItem->first, aResLocale); + rStr = rStr.replaceAll("$(ARG1)", aArg1); + bRet = true; + break; + } + } + + SAL_WARN_IF(!bRet, "svtools.misc", "ErrorContext cannot find the resource"); + + if ( bRet ) + { + sal_uInt16 nId = nErrId.IsWarning() ? ERRCTX_WARNING : ERRCTX_ERROR; + for (const ErrMsgCode* pItem = RID_ERRCTX; pItem->second; ++pItem) + { + if (sal_uInt32(pItem->second) == nId) + { + rStr = rStr.replaceAll("$(ERR)", Translate::get(pItem->first, aResLocale)); + break; + } + } + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/embedhlp.cxx b/svtools/source/misc/embedhlp.cxx new file mode 100644 index 000000000..d50238d8e --- /dev/null +++ b/svtools/source/misc/embedhlp.cxx @@ -0,0 +1,1091 @@ +/* -*- 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 <libxml/xmlwriter.h> + +#include <svtools/embedhlp.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/outdev.hxx> +#include <vcl/gfxlink.hxx> +#include <vcl/TypeSerializer.hxx> +#include <bitmaps.hlst> + +#include <sal/log.hxx> +#include <comphelper/fileformat.h> +#include <comphelper/embeddedobjectcontainer.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <unotools/streamwrap.hxx> +#include <com/sun/star/chart2/XChartDocument.hpp> +#include <com/sun/star/chart2/XCoordinateSystem.hpp> +#include <com/sun/star/chart2/XCoordinateSystemContainer.hpp> +#include <com/sun/star/chart2/XDiagram.hpp> +#include <com/sun/star/chart2/XChartTypeContainer.hpp> +#include <com/sun/star/chart2/XChartType.hpp> +#include <tools/globname.hxx> +#include <comphelper/classids.hxx> +#include <com/sun/star/util/CloseVetoException.hpp> +#include <com/sun/star/util/XModifyListener.hpp> +#include <com/sun/star/util/XModifiable.hpp> +#include <com/sun/star/embed/Aspects.hpp> +#include <com/sun/star/embed/EmbedStates.hpp> +#include <com/sun/star/embed/NoVisualAreaSizeException.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/embed/XStateChangeListener.hpp> +#include <com/sun/star/embed/XLinkageSupport.hpp> +#include <com/sun/star/chart2/XDefaultSizeTransmitter.hpp> +#include <embeddedobj/embeddedupdate.hxx> +#include <cppuhelper/implbase.hxx> +#include <vcl/svapp.hxx> +#include <tools/diagnose_ex.h> +#include <tools/debug.hxx> +#include <sfx2/xmldump.hxx> +#include <memory> + +using namespace com::sun::star; + +namespace svt { + +namespace { + +class EmbedEventListener_Impl : public ::cppu::WeakImplHelper < embed::XStateChangeListener, + document::XEventListener, + util::XModifyListener, + util::XCloseListener > +{ +public: + EmbeddedObjectRef* pObject; + sal_Int32 nState; + + explicit EmbedEventListener_Impl( EmbeddedObjectRef* p ) : + pObject(p) + , nState(-1) + {} + + static rtl::Reference<EmbedEventListener_Impl> Create( EmbeddedObjectRef* ); + + virtual void SAL_CALL changingState( const lang::EventObject& aEvent, ::sal_Int32 nOldState, ::sal_Int32 nNewState ) override; + virtual void SAL_CALL stateChanged( const lang::EventObject& aEvent, ::sal_Int32 nOldState, ::sal_Int32 nNewState ) override; + virtual void SAL_CALL queryClosing( const lang::EventObject& Source, sal_Bool GetsOwnership ) override; + virtual void SAL_CALL notifyClosing( const lang::EventObject& Source ) override; + virtual void SAL_CALL notifyEvent( const document::EventObject& aEvent ) override; + virtual void SAL_CALL disposing( const lang::EventObject& aEvent ) override; + virtual void SAL_CALL modified( const css::lang::EventObject& aEvent ) override; +}; + +} + +rtl::Reference<EmbedEventListener_Impl> EmbedEventListener_Impl::Create( EmbeddedObjectRef* p ) +{ + rtl::Reference<EmbedEventListener_Impl> pRet(new EmbedEventListener_Impl( p )); + + if ( p->GetObject().is() ) + { + p->GetObject()->addStateChangeListener( pRet ); + + uno::Reference < util::XCloseable > xClose = p->GetObject(); + DBG_ASSERT( xClose.is(), "Object does not support XCloseable!" ); + if ( xClose.is() ) + xClose->addCloseListener( pRet ); + + uno::Reference < document::XEventBroadcaster > xBrd = p->GetObject(); + if ( xBrd.is() ) + xBrd->addEventListener( pRet ); + + pRet->nState = p->GetObject()->getCurrentState(); + if ( pRet->nState == embed::EmbedStates::RUNNING ) + { + uno::Reference < util::XModifiable > xMod( p->GetObject()->getComponent(), uno::UNO_QUERY ); + if ( xMod.is() ) + // listen for changes in running state (update replacements in case of changes) + xMod->addModifyListener( pRet ); + } + } + + return pRet; +} + +void SAL_CALL EmbedEventListener_Impl::changingState( const lang::EventObject&, + ::sal_Int32, + ::sal_Int32 ) +{ +} + +void SAL_CALL EmbedEventListener_Impl::stateChanged( const lang::EventObject&, + ::sal_Int32 nOldState, + ::sal_Int32 nNewState ) +{ + SolarMutexGuard aGuard; + nState = nNewState; + if ( !pObject ) + return; + + uno::Reference < util::XModifiable > xMod( pObject->GetObject()->getComponent(), uno::UNO_QUERY ); + if ( nNewState == embed::EmbedStates::RUNNING ) + { + bool bProtected = false; + if (pObject->GetIsProtectedHdl().IsSet()) + { + bProtected = pObject->GetIsProtectedHdl().Call(nullptr); + } + + // TODO/LATER: container must be set before! + // When is this event created? Who sets the new container when it changed? + if ((pObject->GetViewAspect() != embed::Aspects::MSOLE_ICON) + && nOldState != embed::EmbedStates::LOADED && !pObject->IsChart() && !bProtected) + // get new replacement after deactivation + pObject->UpdateReplacement(); + + if( pObject->IsChart() && nOldState == embed::EmbedStates::UI_ACTIVE ) + { + //create a new metafile replacement when leaving the edit mode + //for buggy documents where the old image looks different from the correct one + if( xMod.is() && !xMod->isModified() )//in case of modification a new replacement will be requested anyhow + pObject->UpdateReplacementOnDemand(); + } + + if ( xMod.is() && nOldState == embed::EmbedStates::LOADED ) + // listen for changes (update replacements in case of changes) + xMod->addModifyListener( this ); + } + else if ( nNewState == embed::EmbedStates::LOADED ) + { + // in loaded state we can't listen + if ( xMod.is() ) + xMod->removeModifyListener( this ); + } +} + +void SAL_CALL EmbedEventListener_Impl::modified( const lang::EventObject& ) +{ + SolarMutexGuard aGuard; + if ( !(pObject && pObject->GetViewAspect() != embed::Aspects::MSOLE_ICON) ) + return; + + if ( nState == embed::EmbedStates::RUNNING ) + { + // updates only necessary in non-active states + if( pObject->IsChart() ) + pObject->UpdateReplacementOnDemand(); + else + pObject->UpdateReplacement(); + } + else if ( nState == embed::EmbedStates::ACTIVE || + nState == embed::EmbedStates::UI_ACTIVE || + nState == embed::EmbedStates::INPLACE_ACTIVE ) + { + // in case the object is inplace or UI active the replacement image should be updated on demand + pObject->UpdateReplacementOnDemand(); + } +} + +void SAL_CALL EmbedEventListener_Impl::notifyEvent( const document::EventObject& aEvent ) +{ + SolarMutexGuard aGuard; + + if ( pObject && aEvent.EventName == "OnVisAreaChanged" && pObject->GetViewAspect() != embed::Aspects::MSOLE_ICON && !pObject->IsChart() ) + { + pObject->UpdateReplacement(); + } +} + +void SAL_CALL EmbedEventListener_Impl::queryClosing( const lang::EventObject& Source, sal_Bool ) +{ + // An embedded object can be shared between several objects (f.e. for undo purposes) + // the object will not be closed before the last "customer" is destroyed + // Now the EmbeddedObjectRef helper class works like a "lock" on the object + if ( pObject && pObject->IsLocked() && Source.Source == pObject->GetObject() ) + throw util::CloseVetoException(); +} + +void SAL_CALL EmbedEventListener_Impl::notifyClosing( const lang::EventObject& Source ) +{ + if ( pObject && Source.Source == pObject->GetObject() ) + { + pObject->Clear(); + pObject = nullptr; + } +} + +void SAL_CALL EmbedEventListener_Impl::disposing( const lang::EventObject& aEvent ) +{ + if ( pObject && aEvent.Source == pObject->GetObject() ) + { + pObject->Clear(); + pObject = nullptr; + } +} + +struct EmbeddedObjectRef_Impl +{ + uno::Reference <embed::XEmbeddedObject> mxObj; + + rtl::Reference<EmbedEventListener_Impl> mxListener; + OUString aPersistName; + OUString aMediaType; + comphelper::EmbeddedObjectContainer* pContainer; + std::unique_ptr<Graphic> pGraphic; + sal_Int64 nViewAspect; + bool bIsLocked:1; + bool bNeedUpdate:1; + bool bUpdating:1; + + // #i104867# + sal_uInt32 mnGraphicVersion; + awt::Size aDefaultSizeForChart_In_100TH_MM;//#i103460# charts do not necessarily have an own size within ODF files, in this case they need to use the size settings from the surrounding frame, which is made available with this member + + Link<LinkParamNone*, bool> m_aIsProtectedHdl; + + EmbeddedObjectRef_Impl() : + pContainer(nullptr), + nViewAspect(embed::Aspects::MSOLE_CONTENT), + bIsLocked(false), + bNeedUpdate(false), + bUpdating(false), + mnGraphicVersion(0), + aDefaultSizeForChart_In_100TH_MM(awt::Size(8000,7000)) + {} + + EmbeddedObjectRef_Impl( const EmbeddedObjectRef_Impl& r ) : + mxObj(r.mxObj), + aPersistName(r.aPersistName), + aMediaType(r.aMediaType), + pContainer(r.pContainer), + nViewAspect(r.nViewAspect), + bIsLocked(r.bIsLocked), + bNeedUpdate(r.bNeedUpdate), + bUpdating(r.bUpdating), + mnGraphicVersion(0), + aDefaultSizeForChart_In_100TH_MM(r.aDefaultSizeForChart_In_100TH_MM) + { + if (r.pGraphic && !r.bNeedUpdate) + pGraphic.reset( new Graphic(*r.pGraphic) ); + } + + void dumpAsXml(xmlTextWriterPtr pWriter) const + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("EmbeddedObjectRef_Impl")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mxObj")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("symbol"), + BAD_CAST(typeid(*mxObj).name())); + auto pComponent = dynamic_cast<sfx2::XmlDump*>(mxObj->getComponent().get()); + if (pComponent) + { + pComponent->dumpAsXml(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("pGraphic")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", pGraphic.get()); + if (pGraphic) + { + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("is-none"), + BAD_CAST(OString::boolean(pGraphic->IsNone()).getStr())); + } + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterEndElement(pWriter); + } +}; + +const uno::Reference <embed::XEmbeddedObject>& EmbeddedObjectRef::operator->() const +{ + return mpImpl->mxObj; +} + +const uno::Reference <embed::XEmbeddedObject>& EmbeddedObjectRef::GetObject() const +{ + return mpImpl->mxObj; +} + +EmbeddedObjectRef::EmbeddedObjectRef() : mpImpl(new EmbeddedObjectRef_Impl) {} + +EmbeddedObjectRef::EmbeddedObjectRef( const uno::Reference < embed::XEmbeddedObject >& xObj, sal_Int64 nAspect ) : + mpImpl(new EmbeddedObjectRef_Impl) +{ + mpImpl->nViewAspect = nAspect; + mpImpl->mxObj = xObj; + mpImpl->mxListener = EmbedEventListener_Impl::Create( this ); +} + +EmbeddedObjectRef::EmbeddedObjectRef( const EmbeddedObjectRef& rObj ) : + mpImpl(new EmbeddedObjectRef_Impl(*rObj.mpImpl)) +{ + mpImpl->mxListener = EmbedEventListener_Impl::Create( this ); +} + +EmbeddedObjectRef::~EmbeddedObjectRef() +{ + Clear(); +} + +void EmbeddedObjectRef::Assign( const uno::Reference < embed::XEmbeddedObject >& xObj, sal_Int64 nAspect ) +{ + DBG_ASSERT(!mpImpl->mxObj.is(), "Never assign an already assigned object!"); + + Clear(); + mpImpl->nViewAspect = nAspect; + mpImpl->mxObj = xObj; + mpImpl->mxListener = EmbedEventListener_Impl::Create( this ); + + //#i103460# + if ( IsChart() ) + { + uno::Reference < chart2::XDefaultSizeTransmitter > xSizeTransmitter( xObj, uno::UNO_QUERY ); + DBG_ASSERT( xSizeTransmitter.is(), "Object does not support XDefaultSizeTransmitter -> will cause #i103460#!" ); + if( xSizeTransmitter.is() ) + xSizeTransmitter->setDefaultSize( mpImpl->aDefaultSizeForChart_In_100TH_MM ); + } +} + +void EmbeddedObjectRef::Clear() +{ + if (mpImpl->mxObj.is() && mpImpl->mxListener.is()) + { + mpImpl->mxObj->removeStateChangeListener(mpImpl->mxListener); + + mpImpl->mxObj->removeCloseListener( mpImpl->mxListener ); + mpImpl->mxObj->removeEventListener( mpImpl->mxListener ); + + if ( mpImpl->bIsLocked ) + { + try + { + mpImpl->mxObj->changeState(embed::EmbedStates::LOADED); + mpImpl->mxObj->close( true ); + } + catch (const util::CloseVetoException&) + { + // there's still someone who needs the object! + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("svtools.misc", "Error on switching of the object to loaded state and closing"); + } + } + } + + if (mpImpl->mxListener.is()) + { + mpImpl->mxListener->pObject = nullptr; + mpImpl->mxListener.clear(); + } + + mpImpl->mxObj = nullptr; + mpImpl->pContainer = nullptr; + mpImpl->bIsLocked = false; + mpImpl->bNeedUpdate = false; +} + +bool EmbeddedObjectRef::is() const +{ + return mpImpl->mxObj.is(); +} + +void EmbeddedObjectRef::AssignToContainer( comphelper::EmbeddedObjectContainer* pContainer, const OUString& rPersistName ) +{ + mpImpl->pContainer = pContainer; + mpImpl->aPersistName = rPersistName; + + if ( mpImpl->pGraphic && !mpImpl->bNeedUpdate && pContainer ) + SetGraphicToContainer( *mpImpl->pGraphic, *pContainer, mpImpl->aPersistName, OUString() ); +} + +comphelper::EmbeddedObjectContainer* EmbeddedObjectRef::GetContainer() const +{ + return mpImpl->pContainer; +} + +sal_Int64 EmbeddedObjectRef::GetViewAspect() const +{ + return mpImpl->nViewAspect; +} + +void EmbeddedObjectRef::SetViewAspect( sal_Int64 nAspect ) +{ + mpImpl->nViewAspect = nAspect; +} + +void EmbeddedObjectRef::Lock( bool bLock ) +{ + mpImpl->bIsLocked = bLock; +} + +bool EmbeddedObjectRef::IsLocked() const +{ + return mpImpl->bIsLocked; +} + +void EmbeddedObjectRef::SetIsProtectedHdl(const Link<LinkParamNone*, bool>& rProtectedHdl) +{ + mpImpl->m_aIsProtectedHdl = rProtectedHdl; +} + +const Link<LinkParamNone*, bool> & EmbeddedObjectRef::GetIsProtectedHdl() const +{ + return mpImpl->m_aIsProtectedHdl; +} + +void EmbeddedObjectRef::GetReplacement( bool bUpdate ) +{ + Graphic aOldGraphic; + + if ( bUpdate ) + { + if (mpImpl->pGraphic) + aOldGraphic = *mpImpl->pGraphic; + + mpImpl->pGraphic.reset(); + mpImpl->aMediaType.clear(); + mpImpl->pGraphic.reset( new Graphic ); + mpImpl->mnGraphicVersion++; + } + else if ( !mpImpl->pGraphic ) + { + mpImpl->pGraphic.reset( new Graphic ); + mpImpl->mnGraphicVersion++; + } + else + { + OSL_FAIL("No update, but replacement exists already!"); + return; + } + + std::unique_ptr<SvStream> pGraphicStream(GetGraphicStream( bUpdate )); + if (!pGraphicStream && aOldGraphic.IsNone()) + { + // We have no old graphic, tried to get an updated one, but that failed. Try to get an old + // graphic instead of having no graphic at all. + pGraphicStream = GetGraphicStream(false); + SAL_WARN("svtools.misc", + "EmbeddedObjectRef::GetReplacement: failed to get updated graphic stream"); + } + + if ( pGraphicStream ) + { + GraphicFilter& rGF = GraphicFilter::GetGraphicFilter(); + if( mpImpl->pGraphic ) + rGF.ImportGraphic( *mpImpl->pGraphic, u"", *pGraphicStream ); + mpImpl->mnGraphicVersion++; + } + + // note that UpdateReplacementOnDemand which resets mpImpl->pGraphic to null may have been called + // e.g. when exporting ooo58458-1.odt to doc + if (bUpdate && (!mpImpl->pGraphic || mpImpl->pGraphic->IsNone()) && !aOldGraphic.IsNone()) + { + // We used to have an old graphic, tried to update and the update + // failed. Go back to the old graphic instead of having no graphic at + // all. + mpImpl->pGraphic.reset(new Graphic(aOldGraphic)); + SAL_WARN("svtools.misc", "EmbeddedObjectRef::GetReplacement: failed to update graphic"); + } +} + +const Graphic* EmbeddedObjectRef::GetGraphic() const +{ + try + { + if ( mpImpl->bNeedUpdate ) + // bNeedUpdate will be set to false while retrieving new replacement + const_cast < EmbeddedObjectRef* >(this)->GetReplacement(true); + else if ( !mpImpl->pGraphic ) + const_cast < EmbeddedObjectRef* >(this)->GetReplacement(false); + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.misc", "Something went wrong on getting the graphic"); + } + + return mpImpl->pGraphic.get(); +} + +Size EmbeddedObjectRef::GetSize( MapMode const * pTargetMapMode ) const +{ + MapMode aSourceMapMode( MapUnit::Map100thMM ); + Size aResult; + + if ( mpImpl->nViewAspect == embed::Aspects::MSOLE_ICON ) + { + const Graphic* pGraphic = GetGraphic(); + if ( pGraphic ) + { + aSourceMapMode = pGraphic->GetPrefMapMode(); + aResult = pGraphic->GetPrefSize(); + } + else + aResult = Size( 2500, 2500 ); + } + else + { + awt::Size aSize; + + if (mpImpl->mxObj.is()) + { + try + { + aSize = mpImpl->mxObj->getVisualAreaSize(mpImpl->nViewAspect); + } + catch(const embed::NoVisualAreaSizeException&) + { + SAL_WARN("svtools.misc", "EmbeddedObjectRef::GetSize: no visual area size"); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("svtools.misc", "Something went wrong on getting of the size of the object"); + } + + try + { + aSourceMapMode = MapMode(VCLUnoHelper::UnoEmbed2VCLMapUnit(mpImpl->mxObj->getMapUnit(mpImpl->nViewAspect))); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("svtools.misc", "Can not get the map mode"); + } + } + + if ( !aSize.Height && !aSize.Width ) + { + SAL_WARN("svtools.misc", "EmbeddedObjectRef::GetSize: empty size, defaulting to 5x5cm"); + aSize.Width = 5000; + aSize.Height = 5000; + } + + aResult = Size( aSize.Width, aSize.Height ); + } + + if ( pTargetMapMode ) + aResult = OutputDevice::LogicToLogic( aResult, aSourceMapMode, *pTargetMapMode ); + + return aResult; +} + +void EmbeddedObjectRef::SetGraphicStream( const uno::Reference< io::XInputStream >& xInGrStream, + const OUString& rMediaType ) +{ + mpImpl->pGraphic.reset( new Graphic ); + mpImpl->aMediaType = rMediaType; + mpImpl->mnGraphicVersion++; + + std::unique_ptr<SvStream> pGraphicStream(::utl::UcbStreamHelper::CreateStream( xInGrStream )); + + if ( pGraphicStream ) + { + GraphicFilter& rGF = GraphicFilter::GetGraphicFilter(); + rGF.ImportGraphic( *mpImpl->pGraphic, u"", *pGraphicStream ); + mpImpl->mnGraphicVersion++; + + if ( mpImpl->pContainer ) + { + pGraphicStream->Seek( 0 ); + uno::Reference< io::XInputStream > xInSeekGrStream = new ::utl::OSeekableInputStreamWrapper( pGraphicStream.get() ); + + mpImpl->pContainer->InsertGraphicStream( xInSeekGrStream, mpImpl->aPersistName, rMediaType ); + } + } + + mpImpl->bNeedUpdate = false; + +} + +void EmbeddedObjectRef::SetGraphic( const Graphic& rGraphic, const OUString& rMediaType ) +{ + mpImpl->pGraphic.reset( new Graphic( rGraphic ) ); + mpImpl->aMediaType = rMediaType; + mpImpl->mnGraphicVersion++; + + if ( mpImpl->pContainer ) + SetGraphicToContainer( rGraphic, *mpImpl->pContainer, mpImpl->aPersistName, rMediaType ); + + mpImpl->bNeedUpdate = false; +} + +std::unique_ptr<SvStream> EmbeddedObjectRef::GetGraphicStream( bool bUpdate ) const +{ + DBG_ASSERT( bUpdate || mpImpl->pContainer, "Can't retrieve current graphic!" ); + uno::Reference < io::XInputStream > xStream; + if ( mpImpl->pContainer && !bUpdate ) + { + SAL_INFO( "svtools.misc", "getting stream from container" ); + // try to get graphic stream from container storage + xStream = mpImpl->pContainer->GetGraphicStream(mpImpl->mxObj, &mpImpl->aMediaType); + if ( xStream.is() ) + { + const sal_Int32 nConstBufferSize = 32000; + std::unique_ptr<SvStream> pStream(new SvMemoryStream( 32000, 32000 )); + try + { + sal_Int32 nRead=0; + uno::Sequence < sal_Int8 > aSequence ( nConstBufferSize ); + do + { + nRead = xStream->readBytes ( aSequence, nConstBufferSize ); + pStream->WriteBytes(aSequence.getConstArray(), nRead); + } + while ( nRead == nConstBufferSize ); + pStream->Seek(0); + return pStream; + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.misc", "discarding broken embedded object preview"); + xStream.clear(); + } + } + } + + if ( !xStream.is() ) + { + SAL_INFO( "svtools.misc", "getting stream from object" ); + bool bUpdateAllowed(true); + const comphelper::EmbeddedObjectContainer* pContainer = GetContainer(); + + if(pContainer) + { + uno::Reference<embed::XLinkageSupport> const xLinkage( + mpImpl->mxObj, uno::UNO_QUERY); + if (xLinkage.is() && xLinkage->isLink()) + { + bUpdateAllowed = pContainer->getUserAllowsLinkUpdate(); + + } + } + + if (bUpdateAllowed) + { + // update wanted or no stream in container storage available + xStream = GetGraphicReplacementStream(mpImpl->nViewAspect, mpImpl->mxObj, &mpImpl->aMediaType); + + if(xStream.is()) + { + if (mpImpl->pContainer) + { + bool bInsertGraphicStream = true; + uno::Reference<io::XSeekable> xSeekable(xStream, uno::UNO_QUERY); + std::optional<sal_Int64> oPosition; + if (xSeekable.is()) + { + oPosition = xSeekable->getPosition(); + } + if (bUpdate) + { + std::unique_ptr<SvStream> pResult = utl::UcbStreamHelper::CreateStream(xStream); + if (pResult) + { + GraphicFilter& rGF = GraphicFilter::GetGraphicFilter(); + Graphic aGraphic; + rGF.ImportGraphic(aGraphic, u"", *pResult); + if (aGraphic.IsNone()) + { + // The graphic is not something we can understand, don't overwrite a + // potentially working previous graphic. + SAL_WARN("svtools.misc", "EmbeddedObjectRef::GetGraphicStream: failed to parse xStream"); + bInsertGraphicStream = false; + } + } + } + if (xSeekable.is() && oPosition.has_value()) + { + xSeekable->seek(*oPosition); + } + if (bInsertGraphicStream) + { + mpImpl->pContainer->InsertGraphicStream(xStream,mpImpl->aPersistName,mpImpl->aMediaType); + } + } + + std::unique_ptr<SvStream> pResult = ::utl::UcbStreamHelper::CreateStream( xStream ); + if (pResult && bUpdate) + mpImpl->bNeedUpdate = false; + + return pResult; + } + } + } + + return nullptr; +} + +void EmbeddedObjectRef::DrawPaintReplacement( const tools::Rectangle &rRect, const OUString &rText, OutputDevice *pOut ) +{ + MapMode aMM( MapUnit::MapAppFont ); + Size aAppFontSz = pOut->LogicToLogic( Size( 0, 8 ), &aMM, nullptr ); + vcl::Font aFnt( "Helvetica", aAppFontSz ); + aFnt.SetTransparent( true ); + aFnt.SetColor( COL_LIGHTRED ); + aFnt.SetWeight( WEIGHT_BOLD ); + aFnt.SetFamily( FAMILY_SWISS ); + + pOut->Push(); + pOut->SetBackground(); + pOut->SetFont( aFnt ); + + Point aPt; + + // Now scale text such that it fits in the rectangle + // We start with the default size and decrease 1-AppFont + for( sal_uInt16 i = 8; i > 2; i-- ) + { + aPt.setX( (rRect.GetWidth() - pOut->GetTextWidth( rText )) / 2 ); + aPt.setY( (rRect.GetHeight() - pOut->GetTextHeight()) / 2 ); + + bool bTiny = false; + if( aPt.X() < 0 ) + { + bTiny = true; + aPt.setX( 0 ); + } + if( aPt.Y() < 0 ) + { + bTiny = true; + aPt.setY( 0 ); + } + if( bTiny ) + { + // decrease for small images + aFnt.SetFontSize( Size( 0, aAppFontSz.Height() * i / 8 ) ); + pOut->SetFont( aFnt ); + } + else + break; + } + + BitmapEx aBmp(BMP_PLUGIN); + tools::Long nHeight = rRect.GetHeight() - pOut->GetTextHeight(); + tools::Long nWidth = rRect.GetWidth(); + if(nHeight > 0 && nWidth > 0 && aBmp.GetSizePixel().Width() > 0) + { + aPt.setY( nHeight ); + Point aP = rRect.TopLeft(); + Size aBmpSize = aBmp.GetSizePixel(); + // fit bitmap in + if( nHeight * 10 / nWidth + > aBmpSize.Height() * 10 / aBmpSize.Width() ) + { + // adjust to the width + // keep proportions + tools::Long nH = nWidth * aBmpSize.Height() / aBmpSize.Width(); + // center + aP.AdjustY((nHeight - nH) / 2 ); + nHeight = nH; + } + else + { + // adjust to the height + // keep proportions + tools::Long nW = nHeight * aBmpSize.Width() / aBmpSize.Height(); + // center + aP.AdjustX((nWidth - nW) / 2 ); + nWidth = nW; + } + + pOut->DrawBitmapEx(aP, Size( nWidth, nHeight ), aBmp); + } + + pOut->IntersectClipRegion( rRect ); + aPt += rRect.TopLeft(); + pOut->DrawText( aPt, rText ); + pOut->Pop(); +} + +void EmbeddedObjectRef::DrawShading( const tools::Rectangle &rRect, OutputDevice *pOut ) +{ + GDIMetaFile * pMtf = pOut->GetConnectMetaFile(); + if( pMtf && pMtf->IsRecord() ) + return; + + pOut->Push(); + pOut->SetLineColor( COL_BLACK ); + + Size aPixSize = pOut->LogicToPixel( rRect.GetSize() ); + aPixSize.AdjustWidth( -1 ); + aPixSize.AdjustHeight( -1 ); + Point aPixViewPos = pOut->LogicToPixel( rRect.TopLeft() ); + sal_Int32 nMax = aPixSize.Width() + aPixSize.Height(); + for( sal_Int32 i = 5; i < nMax; i += 5 ) + { + Point a1( aPixViewPos ), a2( aPixViewPos ); + if( i > aPixSize.Width() ) + a1 += Point( aPixSize.Width(), i - aPixSize.Width() ); + else + a1 += Point( i, 0 ); + if( i > aPixSize.Height() ) + a2 += Point( i - aPixSize.Height(), aPixSize.Height() ); + else + a2 += Point( 0, i ); + + pOut->DrawLine( pOut->PixelToLogic( a1 ), pOut->PixelToLogic( a2 ) ); + } + + pOut->Pop(); + +} + +bool EmbeddedObjectRef::TryRunningState( const uno::Reference < embed::XEmbeddedObject >& xEmbObj ) +{ + if ( !xEmbObj.is() ) + return false; + + try + { + if ( xEmbObj->getCurrentState() == embed::EmbedStates::LOADED ) + xEmbObj->changeState( embed::EmbedStates::RUNNING ); + } + catch (const uno::Exception&) + { + return false; + } + + return true; +} + +void EmbeddedObjectRef::SetGraphicToContainer( const Graphic& rGraphic, + comphelper::EmbeddedObjectContainer& aContainer, + const OUString& aName, + const OUString& aMediaType ) +{ + SvMemoryStream aStream; + aStream.SetVersion( SOFFICE_FILEFORMAT_CURRENT ); + + auto pGfxLink = rGraphic.GetSharedGfxLink(); + if (pGfxLink && pGfxLink->IsNative()) + { + if (pGfxLink->ExportNative(aStream)) + { + aStream.Seek(0); + uno::Reference <io::XInputStream> xStream = new ::utl::OSeekableInputStreamWrapper(aStream); + aContainer.InsertGraphicStream(xStream, aName, aMediaType); + } + else + OSL_FAIL("Export of graphic is failed!"); + } + else + { + TypeSerializer aSerializer(aStream); + aSerializer.writeGraphic(rGraphic); + if (aStream.GetError() == ERRCODE_NONE) + { + aStream.Seek(0); + uno::Reference <io::XInputStream> xStream = new ::utl::OSeekableInputStreamWrapper(aStream); + aContainer.InsertGraphicStream(xStream, aName, aMediaType); + } + else + OSL_FAIL("Export of graphic is failed!"); + } +} + +uno::Reference< io::XInputStream > EmbeddedObjectRef::GetGraphicReplacementStream( + sal_Int64 nViewAspect, + const uno::Reference< embed::XEmbeddedObject >& xObj, + OUString* pMediaType ) + noexcept +{ + return ::comphelper::EmbeddedObjectContainer::GetGraphicReplacementStream(nViewAspect,xObj,pMediaType); +} + +bool EmbeddedObjectRef::IsChart(const css::uno::Reference < css::embed::XEmbeddedObject >& xObj) +{ + SvGlobalName aObjClsId(xObj->getClassID()); + return SvGlobalName(SO3_SCH_CLASSID_30) == aObjClsId + || SvGlobalName(SO3_SCH_CLASSID_40) == aObjClsId + || SvGlobalName(SO3_SCH_CLASSID_50) == aObjClsId + || SvGlobalName(SO3_SCH_CLASSID_60) == aObjClsId; +} + +void EmbeddedObjectRef::UpdateReplacement( bool bUpdateOle ) +{ + if (mpImpl->bUpdating) + { + SAL_WARN("svtools.misc", "UpdateReplacement called while UpdateReplacement already underway"); + return; + } + mpImpl->bUpdating = true; + UpdateOleObject( bUpdateOle ); + GetReplacement(true); + UpdateOleObject( false ); + mpImpl->bUpdating = false; +} + +void EmbeddedObjectRef::UpdateOleObject( bool bUpdateOle ) +{ + embed::EmbeddedUpdate* pObj = dynamic_cast<embed::EmbeddedUpdate*> (GetObject().get()); + if( pObj ) + pObj->SetOleState( bUpdateOle ); +} + + +void EmbeddedObjectRef::UpdateReplacementOnDemand() +{ + mpImpl->pGraphic.reset(); + mpImpl->bNeedUpdate = true; + mpImpl->mnGraphicVersion++; + + if( mpImpl->pContainer ) + { + //remove graphic from container thus a new up to date one is requested on save + mpImpl->pContainer->RemoveGraphicStream( mpImpl->aPersistName ); + } +} + +bool EmbeddedObjectRef::IsChart() const +{ + //todo maybe for 3.0: + //if the changes work good for chart + //we should apply them for all own ole objects + + //#i83708# #i81857# #i79578# request an ole replacement image only if really necessary + //as this call can be very expensive and does block the user interface as long at it takes + + if (!mpImpl->mxObj.is()) + return false; + + return EmbeddedObjectRef::IsChart(mpImpl->mxObj); +} + +// MT: Only used for getting accessible attributes, which are not localized +OUString EmbeddedObjectRef::GetChartType() +{ + OUString Style; + if ( mpImpl->mxObj.is() ) + { + if ( IsChart() ) + { + if ( svt::EmbeddedObjectRef::TryRunningState( mpImpl->mxObj ) ) + { + uno::Reference< chart2::XChartDocument > xChart( mpImpl->mxObj->getComponent(), uno::UNO_QUERY ); + if (xChart.is()) + { + uno::Reference< chart2::XDiagram > xDiagram( xChart->getFirstDiagram()); + if( ! xDiagram.is()) + return OUString(); + uno::Reference< chart2::XCoordinateSystemContainer > xCooSysCnt( xDiagram, uno::UNO_QUERY_THROW ); + const uno::Sequence< uno::Reference< chart2::XCoordinateSystem > > aCooSysSeq( xCooSysCnt->getCoordinateSystems()); + // IA2 CWS. Unused: int nCoordinateCount = aCooSysSeq.getLength(); + bool bGetChartType = false; + for( const auto& rCooSys : aCooSysSeq ) + { + uno::Reference< chart2::XChartTypeContainer > xCTCnt( rCooSys, uno::UNO_QUERY_THROW ); + const uno::Sequence< uno::Reference< chart2::XChartType > > aChartTypes( xCTCnt->getChartTypes()); + int nDimesionCount = rCooSys->getDimension(); + if( nDimesionCount == 3 ) + Style += "3D "; + else + Style += "2D "; + for( const auto& rChartType : aChartTypes ) + { + OUString strChartType = rChartType->getChartType(); + if (strChartType == "com.sun.star.chart2.AreaChartType") + { + Style += "Areas"; + bGetChartType = true; + } + else if (strChartType == "com.sun.star.chart2.BarChartType") + { + Style += "Bars"; + bGetChartType = true; + } + else if (strChartType == "com.sun.star.chart2.ColumnChartType") + { + uno::Reference< beans::XPropertySet > xProp( rCooSys, uno::UNO_QUERY ); + if( xProp.is()) + { + bool bCurrent = false; + if( xProp->getPropertyValue( "SwapXAndYAxis" ) >>= bCurrent ) + { + if (bCurrent) + Style += "Bars"; + else + Style += "Columns"; + bGetChartType = true; + } + } + } + else if (strChartType == "com.sun.star.chart2.LineChartType") + { + Style += "Lines"; + bGetChartType = true; + } + else if (strChartType == "com.sun.star.chart2.ScatterChartType") + { + Style += "XY Chart"; + bGetChartType = true; + } + else if (strChartType == "com.sun.star.chart2.PieChartType") + { + Style += "Pies"; + bGetChartType = true; + } + else if (strChartType == "com.sun.star.chart2.NetChartType") + { + Style += "Radar"; + bGetChartType = true; + } + else if (strChartType == "com.sun.star.chart2.CandleStickChartType") + { + Style += "Candle Stick Chart"; + bGetChartType = true; + } + if (bGetChartType) + return Style; + } + } + } + } + } + } + return Style; +} + +// #i104867# +sal_uInt32 EmbeddedObjectRef::getGraphicVersion() const +{ + return mpImpl->mnGraphicVersion; +} + +void EmbeddedObjectRef::SetDefaultSizeForChart( const Size& rSizeIn_100TH_MM ) +{ + //#i103460# charts do not necessarily have an own size within ODF files, + //for this case they need to use the size settings from the surrounding frame, + //which is made available with this method + + mpImpl->aDefaultSizeForChart_In_100TH_MM = awt::Size( rSizeIn_100TH_MM.getWidth(), rSizeIn_100TH_MM.getHeight() ); + + uno::Reference<chart2::XDefaultSizeTransmitter> xSizeTransmitter(mpImpl->mxObj, uno::UNO_QUERY); + DBG_ASSERT( xSizeTransmitter.is(), "Object does not support XDefaultSizeTransmitter -> will cause #i103460#!" ); + if( xSizeTransmitter.is() ) + xSizeTransmitter->setDefaultSize( mpImpl->aDefaultSizeForChart_In_100TH_MM ); +} + +void EmbeddedObjectRef::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("EmbeddedObjectRef")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + mpImpl->dumpAsXml(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +} // namespace svt + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/embedtransfer.cxx b/svtools/source/misc/embedtransfer.cxx new file mode 100644 index 000000000..388a9f0f8 --- /dev/null +++ b/svtools/source/misc/embedtransfer.cxx @@ -0,0 +1,250 @@ +/* -*- 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 <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/embed/XEmbedPersist.hpp> +#include <com/sun/star/embed/NoVisualAreaSizeException.hpp> +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <com/sun/star/embed/Aspects.hpp> +#include <osl/diagnose.h> +#include <sot/exchange.hxx> +#include <svtools/embedtransfer.hxx> +#include <tools/mapunit.hxx> +#include <vcl/outdev.hxx> +#include <vcl/filter/SvmWriter.hxx> +#include <vcl/gdimtf.hxx> +#include <comphelper/fileformat.h> +#include <comphelper/propertysequence.hxx> +#include <comphelper/storagehelper.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <unotools/streamwrap.hxx> +#include <unotools/tempfile.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +#include <svtools/embedhlp.hxx> + +using namespace ::com::sun::star; + +SvEmbedTransferHelper::SvEmbedTransferHelper( const uno::Reference< embed::XEmbeddedObject >& xObj, + const Graphic* pGraphic, + sal_Int64 nAspect ) +: m_xObj( xObj ) +, m_pGraphic( pGraphic ? new Graphic( *pGraphic ) : nullptr ) +, m_nAspect( nAspect ) +{ + if( xObj.is() ) + { + TransferableObjectDescriptor aObjDesc; + + FillTransferableObjectDescriptor( aObjDesc, m_xObj, nullptr, m_nAspect ); + PrepareOLE( aObjDesc ); + } +} + + +SvEmbedTransferHelper::~SvEmbedTransferHelper() +{ +} + +void SvEmbedTransferHelper::SetParentShellID( const OUString& rShellID ) +{ + maParentShellID = rShellID; +} + + +void SvEmbedTransferHelper::AddSupportedFormats() +{ + AddFormat( SotClipboardFormatId::EMBED_SOURCE ); + AddFormat( SotClipboardFormatId::OBJECTDESCRIPTOR ); + AddFormat( SotClipboardFormatId::GDIMETAFILE ); + AddFormat( SotClipboardFormatId::BITMAP ); +} + + +bool SvEmbedTransferHelper::GetData( const css::datatransfer::DataFlavor& rFlavor, const OUString& rDestDoc ) +{ + bool bRet = false; + + if( m_xObj.is() ) + { + try + { + SotClipboardFormatId nFormat = SotExchange::GetFormat( rFlavor ); + if( HasFormat( nFormat ) ) + { + if( nFormat == SotClipboardFormatId::OBJECTDESCRIPTOR ) + { + TransferableObjectDescriptor aDesc; + FillTransferableObjectDescriptor( aDesc, m_xObj, m_pGraphic.get(), m_nAspect ); + bRet = SetTransferableObjectDescriptor( aDesc ); + } + else if( nFormat == SotClipboardFormatId::EMBED_SOURCE ) + { + try + { + // TODO/LATER: Probably the graphic should be copied here as well + // currently it is handled by the applications + utl::TempFile aTmp; + aTmp.EnableKillingFile(); + uno::Reference < embed::XEmbedPersist > xPers( m_xObj, uno::UNO_QUERY ); + if ( xPers.is() ) + { + uno::Reference < embed::XStorage > xStg = comphelper::OStorageHelper::GetTemporaryStorage(); + OUString aName( "Dummy" ); + SvStream* pStream = nullptr; + bool bDeleteStream = false; + uno::Sequence < beans::PropertyValue > aEmpty; + uno::Sequence<beans::PropertyValue> aObjArgs( comphelper::InitPropertySequence({ + { "SourceShellID", uno::Any(maParentShellID) }, + { "DestinationShellID", uno::Any(rDestDoc) } + })); + xPers->storeToEntry(xStg, aName, aEmpty, aObjArgs); + if ( xStg->isStreamElement( aName ) ) + { + uno::Reference < io::XStream > xStm = xStg->cloneStreamElement( aName ); + pStream = utl::UcbStreamHelper::CreateStream( xStm ).release(); + bDeleteStream = true; + } + else + { + pStream = aTmp.GetStream( StreamMode::STD_READWRITE ); + uno::Reference < embed::XStorage > xStor = comphelper::OStorageHelper::GetStorageFromStream( new utl::OStreamWrapper( *pStream ) ); + xStg->openStorageElement( aName, embed::ElementModes::READ )->copyToStorage( xStor ); + } + + const sal_uInt32 nLen = pStream->TellEnd(); + css::uno::Sequence< sal_Int8 > aSeq( nLen ); + + pStream->Seek( STREAM_SEEK_TO_BEGIN ); + pStream->ReadBytes(aSeq.getArray(), nLen); + if ( bDeleteStream ) + delete pStream; + + bRet = aSeq.hasElements(); + if( bRet ) + { + SetAny( uno::Any(aSeq) ); + } + } + else + { + //TODO/LATER: how to handle objects without persistence?! + } + } + catch ( uno::Exception& ) + { + } + } + else if ( nFormat == SotClipboardFormatId::GDIMETAFILE && m_pGraphic ) + { + SvMemoryStream aMemStm( 65535, 65535 ); + aMemStm.SetVersion( SOFFICE_FILEFORMAT_CURRENT ); + + const GDIMetaFile& aMetaFile = m_pGraphic->GetGDIMetaFile(); + SvmWriter aWriter( aMemStm ); + aWriter.Write( aMetaFile ); + uno::Any aAny; + aAny <<= uno::Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), + aMemStm.TellEnd() ); + SetAny( aAny ); + bRet = true; + } + else if ( ( nFormat == SotClipboardFormatId::BITMAP || nFormat == SotClipboardFormatId::PNG ) && m_pGraphic ) + { + bRet = SetBitmapEx( m_pGraphic->GetBitmapEx(), rFlavor ); + } + else if ( m_xObj.is() && ::svt::EmbeddedObjectRef::TryRunningState( m_xObj ) ) + { + uno::Reference< datatransfer::XTransferable > xTransferable( m_xObj->getComponent(), uno::UNO_QUERY ); + if ( xTransferable.is() ) + { + uno::Any aAny = xTransferable->getTransferData( rFlavor ); + SetAny( aAny ); + bRet = true; + } + } + } + } + catch( uno::Exception& ) + { + // Error handling? + } + } + + return bRet; +} + + +void SvEmbedTransferHelper::ObjectReleased() +{ + m_xObj.clear(); +} + +void SvEmbedTransferHelper::FillTransferableObjectDescriptor( TransferableObjectDescriptor& rDesc, + const css::uno::Reference< css::embed::XEmbeddedObject >& xObj, + const Graphic* pGraphic, + sal_Int64 nAspect ) +{ + //TODO/LATER: need TypeName to fill it into the Descriptor (will be shown in listbox) + css::datatransfer::DataFlavor aFlavor; + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::OBJECTDESCRIPTOR, aFlavor ); + + rDesc.maClassName = SvGlobalName( xObj->getClassID() ); + rDesc.maTypeName = aFlavor.HumanPresentableName; + + //TODO/LATER: the aspect size in the descriptor is wrong, unfortunately the stream + // representation of the descriptor allows only 4 bytes for the aspect + // so for internal transport something different should be found + rDesc.mnViewAspect = sal::static_int_cast<sal_uInt16>( nAspect ); + + Size aSize; + MapMode aMapMode( MapUnit::Map100thMM ); + if ( nAspect == embed::Aspects::MSOLE_ICON ) + { + if ( pGraphic ) + { + aMapMode = pGraphic->GetPrefMapMode(); + aSize = pGraphic->GetPrefSize(); + } + else + aSize = Size( 2500, 2500 ); + } + else + { + try + { + awt::Size aSz = xObj->getVisualAreaSize( rDesc.mnViewAspect ); + aSize = Size( aSz.Width, aSz.Height ); + } + catch( embed::NoVisualAreaSizeException& ) + { + OSL_FAIL( "Can not get visual area size!" ); + aSize = Size( 5000, 5000 ); + } + + // TODO/LEAN: getMapUnit can switch object to running state + aMapMode = MapMode( VCLUnoHelper::UnoEmbed2VCLMapUnit( xObj->getMapUnit( rDesc.mnViewAspect ) ) ); + } + + rDesc.maSize = OutputDevice::LogicToLogic( aSize, aMapMode, MapMode( MapUnit::Map100thMM ) ); + rDesc.maDragStartPos = Point(); + rDesc.maDisplayName.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/filechangedchecker.cxx b/svtools/source/misc/filechangedchecker.cxx new file mode 100644 index 000000000..dd7cf22b9 --- /dev/null +++ b/svtools/source/misc/filechangedchecker.cxx @@ -0,0 +1,114 @@ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> +#include <osl/file.hxx> + +#include <svtools/filechangedchecker.hxx> +#include <vcl/timer.hxx> + +FileChangedChecker::FileChangedChecker(const OUString& rFilename, + const ::std::function<void ()>& rCallback) + : mTimer("SVTools FileChangedChecker Timer") + , mFileName(rFilename) + , mLastModTime() + , mpCallback(rCallback) +{ + // Get the current last file modified Status + getCurrentModTime(mLastModTime); + + // associate the callback function for the Timer + mTimer.SetInvokeHandler(LINK(this, FileChangedChecker, TimerHandler)); + + // set timer interval + mTimer.SetTimeout(100); + + // start the timer + resetTimer(); +} + +FileChangedChecker::FileChangedChecker(const OUString& rFilename) + : mTimer("") + , mFileName(rFilename) + , mLastModTime() + , mpCallback(nullptr) +{ + // Get the current last file modified Status + getCurrentModTime(mLastModTime); +} + +void FileChangedChecker::resetTimer() +{ + if (mpCallback == nullptr) + return; + + // Start the Idle if it's not active + if (!mTimer.IsActive()) + mTimer.Start(); + + // Set lowest Priority + mTimer.SetPriority(TaskPriority::LOWEST); +} + +bool FileChangedChecker::getCurrentModTime(TimeValue& o_rValue) const +{ + // Need a Directory item to fetch file status + osl::DirectoryItem aItem; + if (osl::FileBase::E_None != osl::DirectoryItem::get(mFileName, aItem)) + return false; + + // Retrieve the status - we are only interested in last File + // Modified time + osl::FileStatus aStatus( osl_FileStatus_Mask_ModifyTime ); + if (osl::FileBase::E_None != aItem.getFileStatus(aStatus)) + return false; + + o_rValue = aStatus.getModifyTime(); + return true; +} + +bool FileChangedChecker::hasFileChanged(bool bUpdate) +{ + // Get the current file Status + TimeValue newTime={0,0}; + if( !getCurrentModTime(newTime) ) + return true; // well. hard to answer correctly here ... + + // Check if the seconds time stamp has any difference + // If so, then our file has changed meanwhile + if( newTime.Seconds != mLastModTime.Seconds || + newTime.Nanosec != mLastModTime.Nanosec ) + { + // Since the file has changed, set the new status as the file status and + // return True + if(bUpdate) + mLastModTime = newTime ; + + return true; + } + else + return false; +} + +IMPL_LINK_NOARG(FileChangedChecker, TimerHandler, Timer *, void) +{ + // If the file has changed, then update the graphic in the doc + SAL_INFO("svtools", "Timeout Called"); + if(hasFileChanged()) + { + SAL_INFO("svtools", "File modified"); + mpCallback(); + } + + // Reset the Timer in any case + resetTimer(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/imagemgr.cxx b/svtools/source/misc/imagemgr.cxx new file mode 100644 index 000000000..66f786dc1 --- /dev/null +++ b/svtools/source/misc/imagemgr.cxx @@ -0,0 +1,888 @@ +/* -*- 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 <svtools/imagemgr.hxx> +#include <tools/diagnose_ex.h> +#include <tools/urlobj.hxx> +#include <tools/debug.hxx> +#include <vcl/image.hxx> +#include <sot/storage.hxx> +#include <comphelper/classids.hxx> +#include <unotools/ucbhelper.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/document/XTypeDetection.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <ucbhelper/content.hxx> +#include <svtools/strings.hrc> +#include <svtools/svtresid.hxx> +#include <o3tl/string_view.hxx> +#include <bitmaps.hlst> +#include <strings.hxx> + +// globals ******************************************************************* + +#define NO_INDEX (-1) +#define CONTENT_HELPER ::utl::UCBContentHelper + +namespace { + +struct SvtExtensionResIdMapping_Impl +{ + const char* _pExt; + bool _bExt; + TranslateId pStrId; + SvImageId _nImgId; +}; + +} + +SvtExtensionResIdMapping_Impl const ExtensionMap_Impl[] = +{ + { "awk", true, STR_DESCRIPTION_SOURCEFILE, SvImageId::NONE }, + { "bas", true, STR_DESCRIPTION_SOURCEFILE, SvImageId::NONE }, + { "bat", true, STR_DESCRIPTION_BATCHFILE, SvImageId::NONE }, + { "bmk", false, STR_DESCRIPTION_BOOKMARKFILE, SvImageId::NONE }, + { "bmp", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::Bitmap }, + { "c", true, STR_DESCRIPTION_SOURCEFILE, SvImageId::NONE }, + { "cfg", false, STR_DESCRIPTION_CFGFILE, SvImageId::NONE }, + { "cmd", true, STR_DESCRIPTION_BATCHFILE, SvImageId::NONE }, + { "cob", true, STR_DESCRIPTION_SOURCEFILE, SvImageId::NONE }, + { "com", true, STR_DESCRIPTION_APPLICATION, SvImageId::NONE }, + { "cxx", true, STR_DESCRIPTION_SOURCEFILE, SvImageId::NONE }, + { "dbf", true, STR_DESCRIPTION_DATABASE_TABLE, SvImageId::Table }, + { "def", true, STR_DESCRIPTION_SOURCEFILE, SvImageId::NONE }, + { "dll", true, STR_DESCRIPTION_SYSFILE, SvImageId::NONE }, + { "doc", false, STR_DESCRIPTION_WORD_DOC, SvImageId::Writer }, + { "dot", false, STR_DESCRIPTION_WORD_DOC, SvImageId::WriterTemplate }, + { "docx", false, STR_DESCRIPTION_WORD_DOC, SvImageId::Writer }, + { "dxf", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::DXF }, + { "exe", true, STR_DESCRIPTION_APPLICATION, SvImageId::NONE }, + { "gif", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::GIF }, + { "h", true, STR_DESCRIPTION_SOURCEFILE, SvImageId::NONE }, + { "hlp", false, STR_DESCRIPTION_HELP_DOC, SvImageId::NONE }, + { "hrc", true, STR_DESCRIPTION_SOURCEFILE, SvImageId::NONE }, + { "htm", false, STR_DESCRIPTION_HTMLFILE, SvImageId::HTML }, + { "html", false, STR_DESCRIPTION_HTMLFILE, SvImageId::HTML }, + { "asp", false, STR_DESCRIPTION_HTMLFILE, SvImageId::HTML }, + { "hxx", true, STR_DESCRIPTION_SOURCEFILE, SvImageId::NONE }, + { "ini", false, STR_DESCRIPTION_CFGFILE, SvImageId::NONE }, + { "java", true, STR_DESCRIPTION_SOURCEFILE, SvImageId::NONE }, + { "jpeg", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::JPG }, + { "jpg", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::JPG }, + { "lha", true, STR_DESCRIPTION_ARCHIVFILE, SvImageId::NONE }, +#ifdef _WIN32 + { "lnk", false, {}, SvImageId::NONE }, +#endif + { "log", true, STR_DESCRIPTION_LOGFILE, SvImageId::NONE }, + { "lst", true, STR_DESCRIPTION_LOGFILE, SvImageId::NONE }, + { "met", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::MET }, + { "mml", false, STR_DESCRIPTION_MATHML_DOC, SvImageId::Math }, + { "mod", true, STR_DESCRIPTION_SOURCEFILE, SvImageId::NONE }, + { "odb", false, STR_DESCRIPTION_OO_DATABASE_DOC, SvImageId::OO_DatabaseDoc }, + { "odg", false, STR_DESCRIPTION_OO_DRAW_DOC, SvImageId::OO_DrawDoc }, + { "odf", false, STR_DESCRIPTION_OO_MATH_DOC, SvImageId::OO_MathDoc }, + { "odm", false, STR_DESCRIPTION_OO_GLOBAL_DOC, SvImageId::OO_GlobalDoc }, + { "odp", false, STR_DESCRIPTION_OO_IMPRESS_DOC, SvImageId::OO_ImpressDoc }, + { "ods", false, STR_DESCRIPTION_OO_CALC_DOC, SvImageId::OO_CalcDoc }, + { "odt", false, STR_DESCRIPTION_OO_WRITER_DOC, SvImageId::OO_WriterDoc }, + { "otg", false, STR_DESCRIPTION_OO_DRAW_TEMPLATE, SvImageId::OO_DrawTemplate }, + { "otp", false, STR_DESCRIPTION_OO_IMPRESS_TEMPLATE, SvImageId::OO_ImpressTemplate }, + { "ots", false, STR_DESCRIPTION_OO_CALC_TEMPLATE, SvImageId::OO_CalcTemplate }, + { "ott", false, STR_DESCRIPTION_OO_WRITER_TEMPLATE, SvImageId::OO_WriterTemplate }, + { "pas", true, STR_DESCRIPTION_SOURCEFILE, SvImageId::NONE }, + { "pcd", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::PCD }, + { "pct", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::PCT }, + { "pict", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::PCT }, + { "pcx", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::PCX }, + { "pl", true, STR_DESCRIPTION_SOURCEFILE, SvImageId::NONE }, + { "png", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::PNG }, + { "rar", true, STR_DESCRIPTION_ARCHIVFILE, SvImageId::NONE }, + { "rtf", false, STR_DESCRIPTION_WORD_DOC, SvImageId::Writer }, + { "sbl", false, {}, SvImageId::NONE }, + { "sch", false, {}, SvImageId::NONE }, + { "sda", false, { nullptr, STR_DESCRIPTION_SDRAW_DOC}, SvImageId::Draw }, + { "sdb", false, STR_DESCRIPTION_SDATABASE_DOC, SvImageId::Database }, + { "sdc", false, { nullptr, STR_DESCRIPTION_SCALC_DOC}, SvImageId::Calc }, + { "sdd", false, { nullptr, STR_DESCRIPTION_SIMPRESS_DOC}, SvImageId::Impress }, + { "sdp", false, { nullptr, STR_DESCRIPTION_SIMPRESS_DOC}, SvImageId::NONE }, + { "sds", false, { nullptr, STR_DESCRIPTION_SCHART_DOC}, SvImageId::NONE }, + { "sdw", false, { nullptr, STR_DESCRIPTION_SWRITER_DOC}, SvImageId::Writer }, + { "sga", false, {}, SvImageId::NONE }, + { "sgl", false, STR_DESCRIPTION_GLOBALDOC, SvImageId::GlobalDoc }, + { "shtml", false, STR_DESCRIPTION_HTMLFILE, SvImageId::HTML }, + { "sim", false, STR_DESCRIPTION_SIMAGE_DOC, SvImageId::SIM }, + { "smf", false, { nullptr, STR_DESCRIPTION_SMATH_DOC}, SvImageId::Math }, + { "src", true, STR_DESCRIPTION_SOURCEFILE, SvImageId::NONE }, + { "svh", false, STR_DESCRIPTION_HELP_DOC, SvImageId::NONE }, + { "svm", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::SVM }, + { "stc", false, STR_DESCRIPTION_CALC_TEMPLATE, SvImageId::CalcTemplate }, + { "std", false, STR_DESCRIPTION_DRAW_TEMPLATE, SvImageId::DrawTemplate }, + { "sti", false, STR_DESCRIPTION_IMPRESS_TEMPLATE, SvImageId::ImpressTemplate }, + { "stw", false, STR_DESCRIPTION_WRITER_TEMPLATE, SvImageId::WriterTemplate }, + { "sxc", false, STR_DESCRIPTION_SXCALC_DOC, SvImageId::Calc }, + { "sxd", false, STR_DESCRIPTION_SXDRAW_DOC, SvImageId::Draw }, + { "sxg", false, STR_DESCRIPTION_SXGLOBAL_DOC, SvImageId::GlobalDoc }, + { "sxi", false, STR_DESCRIPTION_SXIMPRESS_DOC, SvImageId::Impress }, + { "sxm", false, STR_DESCRIPTION_SXMATH_DOC, SvImageId::Math }, + { "sxs", false, STR_DESCRIPTION_SXCHART_DOC, SvImageId::NONE }, + { "sxw", false, STR_DESCRIPTION_SXWRITER_DOC, SvImageId::Writer }, + { "sys", true, STR_DESCRIPTION_SYSFILE, SvImageId::NONE }, + { "tif", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::TIFF }, + { "tiff", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::TIFF }, + { "txt", false, STR_DESCRIPTION_TEXTFILE, SvImageId::TextFile }, + { "url", false, STR_DESCRIPTION_LINK, SvImageId::NONE }, + { "vor", false, STR_DESCRIPTION_SOFFICE_TEMPLATE_DOC, SvImageId::WriterTemplate }, + { "vxd", true, STR_DESCRIPTION_SYSFILE, SvImageId::NONE }, + { "webp", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::WEBP }, + { "wmf", true, STR_DESCRIPTION_GRAPHIC_DOC, SvImageId::WMF }, + { "xls", false, STR_DESCRIPTION_EXCEL_DOC, SvImageId::Calc }, + { "xlt", false, STR_DESCRIPTION_EXCEL_TEMPLATE_DOC, SvImageId::CalcTemplate }, + { "xlsx", false, STR_DESCRIPTION_EXCEL_DOC, SvImageId::Calc }, + { "uu", true, STR_DESCRIPTION_ARCHIVFILE, SvImageId::NONE }, + { "uue", true, STR_DESCRIPTION_ARCHIVFILE, SvImageId::NONE }, + { "z", true, STR_DESCRIPTION_ARCHIVFILE, SvImageId::NONE }, + { "zip", true, STR_DESCRIPTION_ARCHIVFILE, SvImageId::NONE }, + { "zoo", true, STR_DESCRIPTION_ARCHIVFILE, SvImageId::NONE }, + { "gz", true, STR_DESCRIPTION_ARCHIVFILE, SvImageId::NONE }, + { "ppt", false, STR_DESCRIPTION_POWERPOINT, SvImageId::Impress }, + { "pot", false, STR_DESCRIPTION_POWERPOINT_TEMPLATE, SvImageId::ImpressTemplate }, + { "pps", false, STR_DESCRIPTION_POWERPOINT_SHOW, SvImageId::Impress }, + { "pptx", false, STR_DESCRIPTION_POWERPOINT, SvImageId::Impress }, + { "oxt", false, STR_DESCRIPTION_EXTENSION, SvImageId::Extension }, + { nullptr, false, {}, SvImageId::NONE } +}; + +namespace { + +struct SvtFactory2ExtensionMapping_Impl +{ + const char* _pFactory; + const char* _pExtension; +}; + +} + +// mapping from "private:factory" url to extension + +SvtFactory2ExtensionMapping_Impl const Fac2ExtMap_Impl[] = +{ + { "swriter", "odt" }, + { "swriter/web", "html" }, + { "swriter/GlobalDocument", "odm" }, + { "scalc", "ods" }, + { "simpress", "odp" }, + { "sdraw", "odg" }, + { "smath", "odf" }, + { "sdatabase", "odb" }, + { nullptr, nullptr } +}; + + +static OUString GetImageExtensionByFactory_Impl( const OUString& rURL ) +{ + INetURLObject aObj( rURL ); + OUString aPath = aObj.GetURLPath( INetURLObject::DecodeMechanism::NONE ); + OUString aExtension; + + if ( !aPath.isEmpty() ) + { + sal_uInt16 nIndex = 0; + while ( Fac2ExtMap_Impl[ nIndex ]._pFactory ) + { + if ( aPath.equalsAscii( Fac2ExtMap_Impl[ nIndex ]._pFactory ) ) + { + // extension found + aExtension = OUString::createFromAscii(Fac2ExtMap_Impl[ nIndex ]._pExtension); + // and return it + return aExtension; + } + ++nIndex; + } + } + + // no extension found, so use the type detection (performance brake) + + try + { + // get the TypeDetection service to access all registered types + css::uno::Reference < css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + css::uno::Reference < css::document::XTypeDetection > xTypeDetector( + xContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.TypeDetection", xContext), + css::uno::UNO_QUERY ); + + OUString aInternalType = xTypeDetector->queryTypeByURL( rURL ); + css::uno::Reference < css::container::XNameAccess > xAccess( xTypeDetector, css::uno::UNO_QUERY ); + css::uno::Sequence < css::beans::PropertyValue > aTypeProps; + if ( !aInternalType.isEmpty() && xAccess->hasByName( aInternalType ) ) + { + xAccess->getByName( aInternalType ) >>= aTypeProps; + for ( const css::beans::PropertyValue& rProp : std::as_const(aTypeProps) ) + { + if (rProp.Name == "Extensions") + { + css::uno::Sequence < OUString > aExtensions; + if ( ( rProp.Value >>= aExtensions ) && aExtensions.hasElements() ) + { + const OUString* pExtensions = aExtensions.getConstArray(); + aExtension = pExtensions[0]; + break; + } + } + } + } + } + catch( const css::uno::RuntimeException& ) + { + throw; // don't hide it! + } + catch( const css::uno::Exception& ) + { + // type detection failed -> no extension + } + + return aExtension; +} + +static sal_Int32 GetIndexOfExtension_Impl( const OUString& rExtension ) +{ + sal_Int32 nRet = NO_INDEX; + if ( !rExtension.isEmpty() ) + { + sal_Int32 nIndex = 0; + OUString aExt = rExtension.toAsciiLowerCase(); + while ( ExtensionMap_Impl[ nIndex ]._pExt ) + { + if ( aExt.equalsAscii( ExtensionMap_Impl[ nIndex ]._pExt ) ) + { + nRet = nIndex; + break; + } + ++nIndex; + } + } + + return nRet; +} + +static SvImageId GetImageId_Impl( const OUString& rExtension ) +{ + SvImageId nImage = SvImageId::File; + sal_Int32 nIndex = GetIndexOfExtension_Impl( rExtension ); + if ( nIndex != NO_INDEX ) + { + nImage = ExtensionMap_Impl[ nIndex ]._nImgId; + if ( nImage == SvImageId::NONE ) + nImage = SvImageId::File; + } + + return nImage; +} + +static bool GetVolumeProperties_Impl( ::ucbhelper::Content& rContent, svtools::VolumeInfo& rVolumeInfo ) +{ + bool bRet = false; + + try + { + bRet = ( ( rContent.getPropertyValue( "IsVolume" ) >>= rVolumeInfo.m_bIsVolume ) && + ( rContent.getPropertyValue( "IsRemote" ) >>= rVolumeInfo.m_bIsRemote ) && + ( rContent.getPropertyValue( "IsRemoveable" ) >>= rVolumeInfo.m_bIsRemoveable ) && + ( rContent.getPropertyValue( "IsFloppy" ) >>= rVolumeInfo.m_bIsFloppy ) && + ( rContent.getPropertyValue( "IsCompactDisc" ) >>= rVolumeInfo.m_bIsCompactDisc ) ); + } + catch( const css::uno::RuntimeException& ) + { + throw; // don't hide it! + } + catch( const css::uno::Exception& ) + { + // type detection failed -> no extension + } + + return bRet; +} + +static SvImageId GetFolderImageId_Impl( const OUString& rURL ) +{ + SvImageId nRet = SvImageId::Folder; + try + { + ::svtools::VolumeInfo aVolumeInfo; + ::ucbhelper::Content aCnt( rURL, css::uno::Reference< css::ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + if ( GetVolumeProperties_Impl( aCnt, aVolumeInfo ) ) + { + if ( aVolumeInfo.m_bIsRemote ) + nRet = SvImageId::NetworkDevice; + else if ( aVolumeInfo.m_bIsCompactDisc ) + nRet = SvImageId::CDRomDevice; + else if ( aVolumeInfo.m_bIsRemoveable ) + nRet = SvImageId::RemoveableDevice; + else if ( aVolumeInfo.m_bIsVolume ) + nRet = SvImageId::FixedDevice; + } + } + catch( const css::uno::RuntimeException& ) + { + throw; // don't hide it! + } + catch( const css::uno::Exception& ) + { + + } + return nRet; +} + +static bool isFolder( + OUString const & url, css::uno::Reference<css::ucb::XCommandEnvironment> const & env) +{ + try { + return ucbhelper::Content(url, env, comphelper::getProcessComponentContext()).isFolder(); + } catch (css::uno::RuntimeException &) { + throw; + } catch (css::ucb::CommandAbortedException &) { + assert(false); // this cannot happen + throw; + } catch (css::uno::Exception &) { + TOOLS_INFO_EXCEPTION("svtools.misc", "isFolder(" << url << ")"); + return false; + } +} + +static SvImageId GetImageId_Impl( + const INetURLObject& rObject, bool bDetectFolder, + css::uno::Reference<css::ucb::XCommandEnvironment> const & env ) +{ + OUString aExt, sURL = rObject.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + SvImageId nImage = SvImageId::File; + + if ( rObject.GetProtocol() == INetProtocol::PrivSoffice ) + { + std::u16string_view aURLPath = sURL.subView( strlen(URL_PREFIX_PRIV_SOFFICE) ); + std::u16string_view aType = o3tl::getToken(aURLPath, 0, '/' ); + if ( aType == u"factory" ) + { + // detect an image id for our "private:factory" urls + aExt = GetImageExtensionByFactory_Impl( sURL ); + if ( !aExt.isEmpty() ) + nImage = GetImageId_Impl( aExt ); + return nImage; + } + else if ( aType == u"image" ) + nImage = static_cast<SvImageId>(o3tl::toInt32(o3tl::getToken(aURLPath, 1, '/' ))); + } + else + { + aExt = rObject.getExtension(); + if ( aExt == "vor" ) + { + SvImageId nId = SvImageId::WriterTemplate; + try + { + tools::SvRef<SotStorage> aStorage = new SotStorage( sURL, StreamMode::STD_READ ); + if ( !aStorage->GetError() ) + { + SvGlobalName aGlobalName = aStorage->GetClassName(); + if ( aGlobalName == SvGlobalName(SO3_SC_CLASSID_50) || aGlobalName == SvGlobalName(SO3_SC_CLASSID_40) || aGlobalName == SvGlobalName(SO3_SC_CLASSID_30) ) + nId = SvImageId::CalcTemplate; + else if ( aGlobalName == SvGlobalName(SO3_SDRAW_CLASSID_50) ) + nId = SvImageId::DrawTemplate; + else if ( aGlobalName == SvGlobalName(SO3_SIMPRESS_CLASSID_50) || + aGlobalName == SvGlobalName(SO3_SIMPRESS_CLASSID_40) || aGlobalName == SvGlobalName(SO3_SIMPRESS_CLASSID_30) ) + nId = SvImageId::ImpressTemplate; + else if ( aGlobalName == SvGlobalName(SO3_SM_CLASSID_50) || aGlobalName == SvGlobalName(SO3_SM_CLASSID_40) || aGlobalName == SvGlobalName(SO3_SM_CLASSID_30) ) + nId = SvImageId::MathTemplate; + } + } + catch (const css::ucb::ContentCreationException&) + { + TOOLS_WARN_EXCEPTION("svtools.misc", "GetImageId_Impl"); + } + + return nId; + } + } + + if ( nImage == SvImageId::File && !sURL.isEmpty() ) + { + if ( bDetectFolder && isFolder( sURL, env ) ) + nImage = GetFolderImageId_Impl( sURL ); + else if ( !aExt.isEmpty() ) + nImage = GetImageId_Impl( aExt ); + } + return nImage; +} + +static TranslateId GetDescriptionId_Impl( const OUString& rExtension, bool& rbShowExt ) +{ + TranslateId pId; + sal_Int32 nIndex = GetIndexOfExtension_Impl( rExtension ); + if ( nIndex != NO_INDEX ) + { + pId = ExtensionMap_Impl[ nIndex ].pStrId; + rbShowExt = ExtensionMap_Impl[ nIndex ]._bExt; + } + + return pId; +} + +static OUString GetDescriptionByFactory_Impl( const OUString& rFactory ) +{ + TranslateId pResId; + if ( rFactory.startsWithIgnoreAsciiCase( "swriter" ) ) + pResId = STR_DESCRIPTION_FACTORY_WRITER; + else if ( rFactory.startsWithIgnoreAsciiCase( "scalc" ) ) + pResId = STR_DESCRIPTION_FACTORY_CALC; + else if ( rFactory.startsWithIgnoreAsciiCase( "simpress" ) ) + pResId = STR_DESCRIPTION_FACTORY_IMPRESS; + else if ( rFactory.startsWithIgnoreAsciiCase( "sdraw" ) ) + pResId = STR_DESCRIPTION_FACTORY_DRAW; + else if ( rFactory.startsWithIgnoreAsciiCase( "swriter/web" ) ) + pResId = STR_DESCRIPTION_FACTORY_WRITERWEB; + else if ( rFactory.startsWithIgnoreAsciiCase( "swriter/globaldocument" ) ) + pResId = STR_DESCRIPTION_FACTORY_GLOBALDOC; + else if ( rFactory.startsWithIgnoreAsciiCase( "smath" ) ) + pResId = STR_DESCRIPTION_FACTORY_MATH; + else if ( rFactory.startsWithIgnoreAsciiCase( "sdatabase" ) ) + pResId = STR_DESCRIPTION_FACTORY_DATABASE; + + if (pResId) + { + return SvtResId(pResId); + } + return OUString(); +} + +static TranslateId GetFolderDescriptionId_Impl( const OUString& rURL ) +{ + TranslateId pRet = STR_DESCRIPTION_FOLDER; + try + { + ::ucbhelper::Content aCnt( rURL, css::uno::Reference< css::ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + svtools::VolumeInfo aVolumeInfo; + if ( GetVolumeProperties_Impl( aCnt, aVolumeInfo ) ) + { + if ( aVolumeInfo.m_bIsRemote ) + pRet = STR_DESCRIPTION_REMOTE_VOLUME; + else if ( aVolumeInfo.m_bIsFloppy ) + pRet = STR_DESCRIPTION_FLOPPY_VOLUME; + else if ( aVolumeInfo.m_bIsCompactDisc ) + pRet = STR_DESCRIPTION_CDROM_VOLUME; + else if ( aVolumeInfo.m_bIsRemoveable || aVolumeInfo.m_bIsVolume ) + pRet = STR_DESCRIPTION_LOCALE_VOLUME; + } + } + catch( const css::uno::RuntimeException& ) + { + throw; // don't hide it! + } + catch( const css::uno::Exception& ) + { + + } + return pRet; +} + +static OUString GetImageNameFromList_Impl( SvImageId nImageId, vcl::ImageType eImageType ) +{ + if (eImageType == vcl::ImageType::Size32) + { + switch (nImageId) + { + case SvImageId::HTML: + return BMP_HTML_32; + case SvImageId::OO_DatabaseDoc: + return BMP_OO_DATABASE_DOC_32; + case SvImageId::OO_DrawDoc: + return BMP_OO_DRAW_DOC_32; + case SvImageId::OO_MathDoc: + return BMP_OO_MATH_DOC_32; + case SvImageId::OO_GlobalDoc: + return BMP_OO_GLOBAL_DOC_32; + case SvImageId::OO_ImpressDoc: + return BMP_OO_IMPRESS_DOC_32; + case SvImageId::OO_CalcDoc: + return BMP_OO_CALC_DOC_32; + case SvImageId::OO_WriterDoc: + return BMP_OO_WRITER_DOC_32; + case SvImageId::OO_WriterTemplate: + return BMP_OO_WRITER_TEMPLATE_32; + default: break; + } + } + else if (eImageType == vcl::ImageType::Size26) + { + switch (nImageId) + { + case SvImageId::Impress: + return BMP_IMPRESS_LC; + case SvImageId::Bitmap: + return BMP_BITMAP_LC; + case SvImageId::Calc: + return BMP_CALC_LC; + case SvImageId::CalcTemplate: + return BMP_CALCTEMPLATE_LC; + case SvImageId::Database: + return BMP_DATABASE_LC; + case SvImageId::ImpressTemplate: + return BMP_IMPRESSTEMPLATE_LC; + case SvImageId::GIF: + return BMP_GIF_LC; + case SvImageId::HTML: + return BMP_HTML_LC; + case SvImageId::JPG: + return BMP_JPG_LC; + case SvImageId::Math: + return BMP_MATH_LC; + case SvImageId::MathTemplate: + return BMP_MATHTEMPLATE_LC; + case SvImageId::File: + return BMP_FILE_LC; + case SvImageId::PCD: + return BMP_PCD_LC; + case SvImageId::PCT: + return BMP_PCT_LC; + case SvImageId::PCX: + return BMP_PCX_LC; + case SvImageId::SIM: + return BMP_SIM_LC; + case SvImageId::TextFile: + return BMP_TEXTFILE_LC; + case SvImageId::TIFF: + return BMP_TIFF_LC; + case SvImageId::WEBP: + return BMP_WEBP_LC; + case SvImageId::WMF: + return BMP_WMF_LC; + case SvImageId::Writer: + return BMP_WRITER_LC; + case SvImageId::WriterTemplate: + return BMP_WRITERTEMPLATE_LC; + case SvImageId::FixedDevice: + return BMP_FIXEDDEV_LC; + case SvImageId::RemoveableDevice: + return BMP_REMOVABLEDEV_LC; + case SvImageId::CDRomDevice: + return BMP_CDROMDEV_LC; + case SvImageId::NetworkDevice: + return BMP_NETWORKDEV_LC; + case SvImageId::Table: + return BMP_TABLE_LC; + case SvImageId::Folder: + return BMP_FOLDER_LC; + case SvImageId::DXF: + return BMP_DXF_LC; + case SvImageId::MET: + return BMP_MET_LC; + case SvImageId::PNG: + return BMP_PNG_LC; + case SvImageId::SVM: + return BMP_SVM_LC; + case SvImageId::GlobalDoc: + return BMP_GLOBAL_DOC_LC; + case SvImageId::Draw: + return BMP_DRAW_LC; + case SvImageId::DrawTemplate: + return BMP_DRAWTEMPLATE_LC; + case SvImageId::OO_DatabaseDoc: + return BMP_OO_DATABASE_DOC_LC; + case SvImageId::OO_DrawDoc: + return BMP_OO_DRAW_DOC_LC; + case SvImageId::OO_MathDoc: + return BMP_OO_MATH_DOC_LC; + case SvImageId::OO_GlobalDoc: + return BMP_OO_GLOBAL_DOC_LC; + case SvImageId::OO_ImpressDoc: + return BMP_OO_IMPRESS_DOC_LC; + case SvImageId::OO_CalcDoc: + return BMP_OO_CALC_DOC_LC; + case SvImageId::OO_WriterDoc: + return BMP_OO_WRITER_DOC_LC; + case SvImageId::OO_DrawTemplate: + return BMP_OO_DRAW_TEMPLATE_LC; + case SvImageId::OO_ImpressTemplate: + return BMP_OO_IMPRESS_TEMPLATE_LC; + case SvImageId::OO_CalcTemplate: + return BMP_OO_CALC_TEMPLATE_LC; + case SvImageId::OO_WriterTemplate: + return BMP_OO_WRITER_TEMPLATE_LC; + case SvImageId::Extension: + return BMP_EXTENSION_LC; + default: break; + } + } + else + { + switch (nImageId) + { + case SvImageId::Impress: + return BMP_IMPRESS_SC; + case SvImageId::Bitmap: + return BMP_BITMAP_SC; + case SvImageId::Calc: + return BMP_CALC_SC; + case SvImageId::CalcTemplate: + return BMP_CALCTEMPLATE_SC; + case SvImageId::Database: + return BMP_DATABASE_SC; + case SvImageId::ImpressTemplate: + return BMP_IMPRESSTEMPLATE_SC; + case SvImageId::GIF: + return BMP_GIF_SC; + case SvImageId::HTML: + return BMP_HTML_SC; + case SvImageId::JPG: + return BMP_JPG_SC; + case SvImageId::Math: + return BMP_MATH_SC; + case SvImageId::MathTemplate: + return BMP_MATHTEMPLATE_SC; + case SvImageId::File: + return BMP_FILE_SC; + case SvImageId::PCD: + return BMP_PCD_SC; + case SvImageId::PCT: + return BMP_PCT_SC; + case SvImageId::PCX: + return BMP_PCX_SC; + case SvImageId::SIM: + return BMP_SIM_SC; + case SvImageId::TextFile: + return BMP_TEXTFILE_SC; + case SvImageId::TIFF: + return BMP_TIFF_SC; + case SvImageId::WEBP: + return BMP_WEBP_SC; + case SvImageId::WMF: + return BMP_WMF_SC; + case SvImageId::Writer: + return BMP_WRITER_SC; + case SvImageId::WriterTemplate: + return BMP_WRITERTEMPLATE_SC; + case SvImageId::FixedDevice: + return BMP_FIXEDDEV_SC; + case SvImageId::RemoveableDevice: + return BMP_REMOVABLEDEV_SC; + case SvImageId::CDRomDevice: + return BMP_CDROMDEV_SC; + case SvImageId::NetworkDevice: + return BMP_NETWORKDEV_SC; + case SvImageId::Table: + return BMP_TABLE_SC; + case SvImageId::Folder: + return RID_BMP_FOLDER; + case SvImageId::DXF: + return BMP_DXF_SC; + case SvImageId::MET: + return BMP_MET_SC; + case SvImageId::PNG: + return BMP_PNG_SC; + case SvImageId::SVM: + return BMP_SVM_SC; + case SvImageId::GlobalDoc: + return BMP_GLOBAL_DOC_SC; + case SvImageId::Draw: + return BMP_DRAW_SC; + case SvImageId::DrawTemplate: + return BMP_DRAWTEMPLATE_SC; + case SvImageId::OO_DatabaseDoc: + return BMP_OO_DATABASE_DOC_SC; + case SvImageId::OO_DrawDoc: + return BMP_OO_DRAW_DOC_SC; + case SvImageId::OO_MathDoc: + return BMP_OO_MATH_DOC_SC; + case SvImageId::OO_GlobalDoc: + return BMP_OO_GLOBAL_DOC_SC; + case SvImageId::OO_ImpressDoc: + return BMP_OO_IMPRESS_DOC_SC; + case SvImageId::OO_CalcDoc: + return BMP_OO_CALC_DOC_SC; + case SvImageId::OO_WriterDoc: + return BMP_OO_WRITER_DOC_SC; + case SvImageId::OO_DrawTemplate: + return BMP_OO_DRAW_TEMPLATE_SC; + case SvImageId::OO_ImpressTemplate: + return BMP_OO_IMPRESS_TEMPLATE_SC; + case SvImageId::OO_CalcTemplate: + return BMP_OO_CALC_TEMPLATE_SC; + case SvImageId::OO_WriterTemplate: + return BMP_OO_WRITER_TEMPLATE_SC; + case SvImageId::Extension: + return BMP_EXTENSION_SC; + default: break; + } + } + + return OUString(); +} + +static Image GetImageFromList_Impl( SvImageId nImageId, vcl::ImageType eImageType) +{ + OUString sImageName(GetImageNameFromList_Impl(nImageId, eImageType)); + if (!sImageName.isEmpty()) + return Image(StockImage::Yes, sImageName); + return Image(); +} + +OUString SvFileInformationManager::GetDescription_Impl( const INetURLObject& rObject, bool bDetectFolder ) +{ + OUString sExtension(rObject.getExtension()); + OUString sDescription, sURL( rObject.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + TranslateId pResId; + bool bShowExt = false, bOnlyFile = false; + bool bFolder = bDetectFolder && CONTENT_HELPER::IsFolder( sURL ); + if ( !bFolder ) + { + bool bDetected = false; + + if ( rObject.GetProtocol() == INetProtocol::PrivSoffice ) + { + OUString aURLPath = sURL.copy( strlen(URL_PREFIX_PRIV_SOFFICE) ); + std::u16string_view aType = o3tl::getToken(aURLPath, 0, '/' ); + if ( aType == u"factory" ) + { + sDescription = GetDescriptionByFactory_Impl( aURLPath.copy( aURLPath.indexOf( '/' ) + 1 ) ); + bDetected = true; + } + } + + if (!bDetected) + { + // search a description by extension + bool bExt = !sExtension.isEmpty(); + if ( bExt ) + { + sExtension = sExtension.toAsciiLowerCase(); + pResId = GetDescriptionId_Impl( sExtension, bShowExt ); + } + if (!pResId) + { + pResId = STR_DESCRIPTION_FILE; + bOnlyFile = bExt; + } + } + } + else + pResId = GetFolderDescriptionId_Impl( sURL ); + + if (pResId) + { + if ( bOnlyFile ) + { + bShowExt = false; + sExtension = sExtension.toAsciiUpperCase(); + sDescription = sExtension + "-"; + } + sDescription += SvtResId(pResId); + } + + DBG_ASSERT( !sDescription.isEmpty(), "file without description" ); + + if ( bShowExt ) + { + sDescription += " (" + sExtension + ")"; + } + + return sDescription; +} + +OUString SvFileInformationManager::GetImageId(const INetURLObject& rObject, bool bBig) +{ + SvImageId nImage = GetImageId_Impl( + rObject, true, utl::UCBContentHelper::getDefaultCommandEnvironment() ); + DBG_ASSERT( nImage != SvImageId::NONE, "invalid ImageId" ); + return GetImageNameFromList_Impl(nImage, bBig ? vcl::ImageType::Size26 : vcl::ImageType::Size16); +} + +Image SvFileInformationManager::GetImage( + const INetURLObject& rObject, bool bBig, + css::uno::Reference<css::ucb::XCommandEnvironment> const & env) +{ + SvImageId nImage = GetImageId_Impl( rObject, true, env ); + DBG_ASSERT( nImage != SvImageId::NONE, "invalid ImageId" ); + return GetImageFromList_Impl(nImage, bBig ? vcl::ImageType::Size26 : vcl::ImageType::Size16); +} + +OUString SvFileInformationManager::GetFileImageId(const INetURLObject& rObject) +{ + SvImageId nImage = GetImageId_Impl( + rObject, false, utl::UCBContentHelper::getDefaultCommandEnvironment() ); + DBG_ASSERT( nImage != SvImageId::NONE, "invalid ImageId" ); + return GetImageNameFromList_Impl(nImage, vcl::ImageType::Size16); +} + +Image SvFileInformationManager::GetImageNoDefault(const INetURLObject& rObject, vcl::ImageType eImageType) +{ + SvImageId nImage = GetImageId_Impl( + rObject, true, utl::UCBContentHelper::getDefaultCommandEnvironment()); + DBG_ASSERT( nImage != SvImageId::NONE, "invalid ImageId" ); + + if ( nImage == SvImageId::File ) + return Image(); + + return GetImageFromList_Impl(nImage, eImageType); +} + +OUString SvFileInformationManager::GetFolderImageId( const svtools::VolumeInfo& rInfo ) +{ + SvImageId nImage = SvImageId::Folder; + DBG_ASSERT( nImage != SvImageId::NONE, "invalid ImageId" ); + + if ( rInfo.m_bIsRemote ) + nImage = SvImageId::NetworkDevice; + else if ( rInfo.m_bIsCompactDisc ) + nImage = SvImageId::CDRomDevice; + else if ( rInfo.m_bIsRemoveable || rInfo.m_bIsFloppy ) + nImage = SvImageId::RemoveableDevice; + else if ( rInfo.m_bIsVolume ) + nImage = SvImageId::FixedDevice; + + return GetImageNameFromList_Impl(nImage, vcl::ImageType::Size16); +} + +OUString SvFileInformationManager::GetDescription( const INetURLObject& rObject ) +{ + return SvFileInformationManager::GetDescription_Impl( rObject, true ); +} + +OUString SvFileInformationManager::GetFileDescription( const INetURLObject& rObject ) +{ + return SvFileInformationManager::GetDescription_Impl( rObject, false ); +} + +OUString SvFileInformationManager::GetFolderDescription( const svtools::VolumeInfo& rInfo ) +{ + TranslateId pResId = STR_DESCRIPTION_FOLDER; + if ( rInfo.m_bIsRemote ) + pResId = STR_DESCRIPTION_REMOTE_VOLUME; + else if ( rInfo.m_bIsFloppy ) + pResId = STR_DESCRIPTION_FLOPPY_VOLUME; + else if ( rInfo.m_bIsCompactDisc ) + pResId = STR_DESCRIPTION_CDROM_VOLUME; + else if ( rInfo.m_bIsRemoveable || rInfo.m_bIsVolume ) + pResId = STR_DESCRIPTION_LOCALE_VOLUME; + + return SvtResId(pResId); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/imageresourceaccess.cxx b/svtools/source/misc/imageresourceaccess.cxx new file mode 100644 index 000000000..66c458c91 --- /dev/null +++ b/svtools/source/misc/imageresourceaccess.cxx @@ -0,0 +1,166 @@ +/* -*- 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 <svtools/imageresourceaccess.hxx> + +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/graphic/GraphicProvider.hpp> +#include <com/sun/star/graphic/XGraphicProvider.hpp> +#include <com/sun/star/io/XStream.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> +#include <tools/stream.hxx> +#include <tools/diagnose_ex.h> +#include <unotools/streamwrap.hxx> +#include <cppuhelper/implbase.hxx> + +namespace svt::GraphicAccess +{ + +using namespace ::utl; +using namespace css; + +typedef ::cppu::WeakImplHelper<io::XStream, io::XSeekable> StreamSupplier_Base; + +namespace { + +class StreamSupplier : public StreamSupplier_Base +{ +private: + uno::Reference<io::XInputStream> m_xInput; + uno::Reference<io::XOutputStream> m_xOutput; + uno::Reference<io::XSeekable> m_xSeekable; + +public: + StreamSupplier(uno::Reference<io::XInputStream> const & rxInput, uno::Reference<io::XOutputStream> const & rxOutput); + +protected: + // XStream + virtual uno::Reference<io::XInputStream> SAL_CALL getInputStream() override; + virtual uno::Reference<io::XOutputStream> SAL_CALL getOutputStream() override; + + // XSeekable + virtual void SAL_CALL seek(sal_Int64 location) override; + virtual sal_Int64 SAL_CALL getPosition() override; + virtual sal_Int64 SAL_CALL getLength() override; +}; + +} + +StreamSupplier::StreamSupplier(uno::Reference<io::XInputStream> const & rxInput, uno::Reference<io::XOutputStream> const & rxOutput) + : m_xInput(rxInput) + , m_xOutput(rxOutput) +{ + m_xSeekable.set(m_xInput, uno::UNO_QUERY); + if (!m_xSeekable.is()) + m_xSeekable.set(m_xOutput, uno::UNO_QUERY); + OSL_ENSURE(m_xSeekable.is(), "StreamSupplier::StreamSupplier: at least one of both must be seekable!"); +} + +uno::Reference<io::XInputStream> SAL_CALL StreamSupplier::getInputStream() +{ + return m_xInput; +} + +uno::Reference<io::XOutputStream> SAL_CALL StreamSupplier::getOutputStream() +{ + return m_xOutput; +} + +void SAL_CALL StreamSupplier::seek(sal_Int64 nLocation) +{ + if (!m_xSeekable.is()) + throw io::NotConnectedException(); + m_xSeekable->seek(nLocation); +} + +sal_Int64 SAL_CALL StreamSupplier::getPosition() +{ + if (!m_xSeekable.is()) + throw io::NotConnectedException(); + return m_xSeekable->getPosition(); +} + +sal_Int64 SAL_CALL StreamSupplier::getLength() +{ + if (!m_xSeekable.is()) + throw io::NotConnectedException(); + + return m_xSeekable->getLength(); +} + +bool isSupportedURL(std::u16string_view rURL) +{ + return o3tl::starts_with(rURL, u"private:resource/") + || o3tl::starts_with(rURL, u"private:graphicrepository/") + || o3tl::starts_with(rURL, u"private:standardimage/") + || o3tl::starts_with(rURL, u"vnd.sun.star.extension://"); +} + +std::unique_ptr<SvStream> getImageStream(uno::Reference<uno::XComponentContext> const & rxContext, OUString const & rImageResourceURL) +{ + std::unique_ptr<SvMemoryStream> pMemBuffer; + + try + { + // get a GraphicProvider + uno::Reference<graphic::XGraphicProvider> xProvider = css::graphic::GraphicProvider::create(rxContext); + + // let it create a graphic from the given URL + uno::Sequence<beans::PropertyValue> aMediaProperties{ comphelper::makePropertyValue( + "URL", rImageResourceURL) }; + uno::Reference<graphic::XGraphic> xGraphic(xProvider->queryGraphic(aMediaProperties)); + + OSL_ENSURE(xGraphic.is(), "GraphicAccess::getImageStream: the provider did not give us a graphic object!"); + if (!xGraphic.is()) + return pMemBuffer; + + // copy the graphic to an in-memory buffer + pMemBuffer.reset(new SvMemoryStream); + uno::Reference<io::XStream> xBufferAccess = new StreamSupplier( + new OSeekableInputStreamWrapper(*pMemBuffer), + new OSeekableOutputStreamWrapper(*pMemBuffer)); + + aMediaProperties = { comphelper::makePropertyValue("OutputStream", xBufferAccess), + comphelper::makePropertyValue("MimeType", OUString("image/png")) }; + xProvider->storeGraphic(xGraphic, aMediaProperties); + + pMemBuffer->Seek(0); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("svtools", "GraphicAccess::getImageStream"); + pMemBuffer.reset(); + } + + return pMemBuffer; +} + +uno::Reference<io::XInputStream> getImageXStream(uno::Reference<uno::XComponentContext> const & rxContext, OUString const & rImageResourceURL) +{ + return new OSeekableInputStreamWrapper(getImageStream(rxContext, rImageResourceURL).release(), true); // take ownership +} + +} // namespace svt::GraphicAccess + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/langhelp.cxx b/svtools/source/misc/langhelp.cxx new file mode 100644 index 000000000..ff48868e1 --- /dev/null +++ b/svtools/source/misc/langhelp.cxx @@ -0,0 +1,166 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include <comphelper/sequence.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <officecfg/Office/Common.hxx> +#include <officecfg/System.hxx> +#include <org/freedesktop/PackageKit/SyncDbusSessionHelper.hpp> +#include <rtl/ustring.hxx> +#include <svtools/langhelp.hxx> +#include <tools/diagnose_ex.h> +#include <vcl/idle.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <config_langs.h> +#include <config_vendor.h> + +void localizeWebserviceURI( OUString& rURI ) +{ + OUString aLang = Application::GetSettings().GetUILanguageTag().getLanguage(); + if ( aLang.equalsIgnoreAsciiCase("pt") + && Application::GetSettings().GetUILanguageTag().getCountry().equalsIgnoreAsciiCase("br") ) + { + aLang = "pt-br"; + } + if ( aLang.equalsIgnoreAsciiCase("zh") ) + { + if ( Application::GetSettings().GetUILanguageTag().getCountry().equalsIgnoreAsciiCase("cn") ) + aLang = "zh-cn"; + if ( Application::GetSettings().GetUILanguageTag().getCountry().equalsIgnoreAsciiCase("tw") ) + aLang = "zh-tw"; + } + + rURI += aLang; +} + +OUString getInstalledLocaleForLanguage(css::uno::Sequence<OUString> const & installed, OUString const & locale) +{ + if (locale.isEmpty()) + return OUString(); // do not attempt to resolve anything + + if (comphelper::findValue(installed, locale) != -1) + return locale; + + std::vector<OUString> fallbacks(LanguageTag(locale).getFallbackStrings(false)); + auto pf = std::find_if(fallbacks.begin(), fallbacks.end(), + [&installed](const OUString& rf) { return comphelper::findValue(installed, rf) != -1; }); + if (pf != fallbacks.end()) + return *pf; + return OUString(); +} + +static std::unique_ptr<Idle> xLangpackInstaller; + +namespace { + +class InstallLangpack : public Idle +{ + std::vector<OUString> m_aPackages; +public: + explicit InstallLangpack(std::vector<OUString>&& rPackages) + : Idle("install langpack") + , m_aPackages(std::move(rPackages)) + { + SetPriority(TaskPriority::LOWEST); + } + + virtual void Invoke() override + { + vcl::Window* pTopWindow = Application::GetActiveTopWindow(); + if (!pTopWindow) + pTopWindow = Application::GetFirstTopLevelWindow(); + if (!pTopWindow) + { + Start(); + return; + } + try + { + using namespace org::freedesktop::PackageKit; + css::uno::Reference<XSyncDbusSessionHelper> xSyncDbusSessionHelper(SyncDbusSessionHelper::create(comphelper::getProcessComponentContext())); + xSyncDbusSessionHelper->InstallPackageNames(comphelper::containerToSequence(m_aPackages), OUString()); + } + catch (const css::uno::Exception&) + { + TOOLS_INFO_EXCEPTION("svl", "trying to install a LibreOffice langpack"); + } + xLangpackInstaller.reset(); + } +}; + +} + +OUString getInstalledLocaleForSystemUILanguage(const css::uno::Sequence<OUString>& rLocaleElementNames, bool bRequestInstallIfMissing, const OUString& rPreferredLocale) +{ + OUString wantedLocale(rPreferredLocale); + if (wantedLocale.isEmpty()) + wantedLocale = officecfg::System::L10N::UILocale::get(); + + OUString locale = getInstalledLocaleForLanguage(rLocaleElementNames, wantedLocale); + if (bRequestInstallIfMissing && locale.isEmpty() && !wantedLocale.isEmpty() && !Application::IsHeadlessModeEnabled() && + officecfg::Office::Common::PackageKit::EnableLangpackInstallation::get()) + { + LanguageTag aWantedTag(wantedLocale); + if (aWantedTag.getLanguage() != "en") + { + // Get the list of langpacks that this build was configured to include + std::vector<OUString> aPackages; + OUString const sAvailableLocales(WITH_LANG); + std::vector<OUString> aAvailable; + sal_Int32 nIndex = 0; + do + { + aAvailable.emplace_back(sAvailableLocales.getToken(0, ' ', nIndex)); + } + while (nIndex >= 0); + // See which one matches the desired ui locale + OUString install = getInstalledLocaleForLanguage(comphelper::containerToSequence(aAvailable), wantedLocale); + if (!install.isEmpty() && install != "en-US") + { + std::string_view sVendor(OOO_VENDOR); + if (sVendor == "Red Hat, Inc." || sVendor == "The Fedora Project") + { + // langpack is the typical Fedora/RHEL naming convention + LanguageType eType = aWantedTag.getLanguageType(); + if (MsLangId::isSimplifiedChinese(eType)) + aPackages.emplace_back("libreoffice-langpack-zh-Hans"); + else if (MsLangId::isTraditionalChinese(eType)) + aPackages.emplace_back("libreoffice-langpack-zh-Hant"); + else if (install == "pt") + aPackages.emplace_back("libreoffice-langpack-pt-PT"); + else + aPackages.emplace_back("libreoffice-langpack-" + install); + } + else if (sVendor == "The Document Foundation/Debian" || sVendor == "The Document Foundation, Debian and Ubuntu") + { + // l10n is the typical Debian/Ubuntu naming convention + aPackages.emplace_back("libreoffice-l10n-" + install); + } + } + if (!aPackages.empty()) + { + xLangpackInstaller.reset(new InstallLangpack(std::move(aPackages))); + xLangpackInstaller->Start(); + } + } + } + if (locale.isEmpty()) + locale = getInstalledLocaleForLanguage(rLocaleElementNames, "en-US"); + if (locale.isEmpty() && rLocaleElementNames.hasElements()) + locale = rLocaleElementNames[0]; + return locale; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/langtab.cxx b/svtools/source/misc/langtab.cxx new file mode 100644 index 000000000..00e676d14 --- /dev/null +++ b/svtools/source/misc/langtab.cxx @@ -0,0 +1,307 @@ +/* -*- 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 <string_view> + +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/i18n/DirectionProperty.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Any.h> + +#include <i18nlangtag/lang.h> +#include <i18nlangtag/mslangid.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <i18nlangtag/languagetagicu.hxx> + +#include <sal/log.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <svtools/svtresid.hxx> +#include <svtools/langtab.hxx> +#include <unotools/syslocale.hxx> +#include <unotools/charclass.hxx> +#include <tools/resary.hxx> +#include <officecfg/VCL.hxx> +#include <langtab.hrc> + +using namespace ::com::sun::star; + +namespace { + +class SvtLanguageTableImpl +{ +private: + std::vector<std::pair<OUString, LanguageType>> m_aStrings; +public: + + SvtLanguageTableImpl(); + + bool HasType( const LanguageType eType ) const; + OUString GetString( const LanguageType eType ) const; + LanguageType GetType( std::u16string_view rStr ) const; + sal_uInt32 GetEntryCount() const; + LanguageType GetTypeAtIndex( sal_uInt32 nIndex ) const; + sal_uInt32 AddItem(const OUString& rLanguage, const LanguageType eType) + { + m_aStrings.emplace_back(rLanguage, eType); + return m_aStrings.size(); + } + LanguageType GetValue(sal_uInt32 nIndex) const + { + return (nIndex < m_aStrings.size()) ? m_aStrings[nIndex].second : LANGUAGE_DONTKNOW; + } + sal_uInt32 FindIndex(LanguageType nValue) const + { + const size_t nItems = m_aStrings.size(); + for (size_t i = 0; i < nItems; ++i) + { + if (m_aStrings[i].second == nValue) + return i; + } + return RESARRAY_INDEX_NOTFOUND; + } +}; + +struct theLanguageTable : public rtl::Static< SvtLanguageTableImpl, theLanguageTable > {}; +} + +OUString ApplyLreOrRleEmbedding( const OUString &rText ) +{ + const sal_Int32 nLen = rText.getLength(); + if (nLen == 0) + return OUString(); + + constexpr sal_Unicode cLRE_Embedding = 0x202A; // the start char of an LRE embedding + constexpr sal_Unicode cRLE_Embedding = 0x202B; // the start char of an RLE embedding + constexpr sal_Unicode cPopDirectionalFormat = 0x202C; // the unicode PDF (POP_DIRECTIONAL_FORMAT) char that terminates an LRE/RLE embedding + + // check if there are already embedding characters at the strings start + // if so change nothing + const sal_Unicode cChar = rText[0]; + if (cChar == cLRE_Embedding || cChar == cRLE_Embedding) + return rText; + + // since we only call the function getCharacterDirection + // it does not matter which locale the CharClass is for. + // Thus we can readily make use of SvtSysLocale::GetCharClass() + // which should come at no cost... + SvtSysLocale aSysLocale; + const CharClass &rCharClass = aSysLocale.GetCharClass(); + + // we should look for the first non-neutral LTR or RTL character + // and use that to determine the embedding of the whole text... + // Thus we can avoid to check every character of the text. + bool bFound = false; + bool bIsRtlText = false; + for (sal_Int32 i = 0; i < nLen && !bFound; ++i) + { + i18n::DirectionProperty nDirection = rCharClass.getCharacterDirection( rText, i ); + switch (nDirection) + { + case i18n::DirectionProperty_LEFT_TO_RIGHT : + case i18n::DirectionProperty_LEFT_TO_RIGHT_EMBEDDING : + case i18n::DirectionProperty_LEFT_TO_RIGHT_OVERRIDE : + case i18n::DirectionProperty_EUROPEAN_NUMBER : + case i18n::DirectionProperty_ARABIC_NUMBER : // yes! arabic numbers are written from left to right + { + bIsRtlText = false; + bFound = true; + break; + } + + case i18n::DirectionProperty_RIGHT_TO_LEFT : + case i18n::DirectionProperty_RIGHT_TO_LEFT_ARABIC : + case i18n::DirectionProperty_RIGHT_TO_LEFT_EMBEDDING : + case i18n::DirectionProperty_RIGHT_TO_LEFT_OVERRIDE : + { + bIsRtlText = true; + bFound = true; + break; + } + + default: + { + // nothing to be done, character is considered to be neutral we need to look further ... + } + } + } + + sal_Unicode cStart = cLRE_Embedding; // default is to use LRE embedding characters + if (bIsRtlText) + cStart = cRLE_Embedding; // then use RLE embedding + + // add embedding start and end chars to the text if the direction could be determined + OUString aRes( rText ); + if (bFound) + { + aRes = OUStringChar(cStart) + aRes + + OUStringChar(cPopDirectionalFormat); + } + + return aRes; +} + +static OUString lcl_getDescription( const LanguageTag& rTag ) +{ + OUString aStr( LanguageTagIcu::getDisplayName( rTag, Application::GetSettings().GetUILanguageTag())); + if (aStr.isEmpty() || aStr == rTag.getBcp47()) + { + // Place in curly brackets, so all on-the-fly tags without display name + // are grouped together at the top of a listbox (but behind the + // "[None]" entry), and not sprinkled all over, which alphabetically + // might make sense in an English UI only anyway. Also a visual + // indicator that it is a programmatical name, IMHO. + return OUString::Concat("{") + aStr + "}"; + } + else + { + // The ICU display name might be identical to a predefined name or even + // to another tag's ICU name; clarify that this is a generated name and + // append the language tag in curly brackets to distinguish. + return aStr + " {" + rTag.getBcp47() + "}"; + } +} + +SvtLanguageTableImpl::SvtLanguageTableImpl() +{ + for (size_t i = 0; i < SAL_N_ELEMENTS(STR_ARR_SVT_LANGUAGE_TABLE); ++i) + { + m_aStrings.emplace_back(SvtResId(STR_ARR_SVT_LANGUAGE_TABLE[i].first), STR_ARR_SVT_LANGUAGE_TABLE[i].second); + } + + auto xNA = officecfg::VCL::ExtraLanguages::get(); + const uno::Sequence <OUString> rElementNames = xNA->getElementNames(); + for (const OUString& rBcp47 : rElementNames) + { + OUString aName; + sal_Int32 nType = 0; + uno::Reference <container::XNameAccess> xNB; + xNA->getByName(rBcp47) >>= xNB; + bool bSuccess = (xNB->getByName("Name") >>= aName) && + (xNB->getByName("ScriptType") >>= nType); + if (bSuccess) + { + LanguageTag aLang(rBcp47); + LanguageType nLangType = aLang.getLanguageType(); + if (nType <= sal_Int32(LanguageTag::ScriptType::RTL) && nType > sal_Int32(LanguageTag::ScriptType::UNKNOWN)) + aLang.setScriptType(LanguageTag::ScriptType(nType)); + sal_uInt32 nPos = FindIndex(nLangType); + if (nPos == RESARRAY_INDEX_NOTFOUND) + AddItem((aName.isEmpty() ? lcl_getDescription(aLang) : aName), nLangType); + } + } +} + +bool SvtLanguageTableImpl::HasType( const LanguageType eType ) const +{ + LanguageType eLang = MsLangId::getReplacementForObsoleteLanguage( eType ); + sal_uInt32 nPos = FindIndex(eLang); + + return RESARRAY_INDEX_NOTFOUND != nPos && nPos < GetEntryCount(); +} + +bool SvtLanguageTable::HasLanguageType( const LanguageType eType ) +{ + return theLanguageTable::get().HasType( eType ); +} + +OUString SvtLanguageTableImpl::GetString( const LanguageType eType ) const +{ + const LanguageType nLang = MsLangId::getReplacementForObsoleteLanguage( eType); + const sal_uInt32 nPos = (eType == LANGUAGE_PROCESS_OR_USER_DEFAULT ? + FindIndex(LANGUAGE_SYSTEM) : FindIndex( nLang)); + + if ( RESARRAY_INDEX_NOTFOUND != nPos && nPos < GetEntryCount() ) + return m_aStrings[nPos].first; + + // Obtain from ICU, or a geeky but usable-in-a-pinch lang-tag. + OUString sLangTag( lcl_getDescription( LanguageTag(nLang))); + SAL_WARN("svtools.misc", "Language: 0x" + << std::hex << nLang + << " with unknown name, so returning lang-tag of: " + << sLangTag); + + // And add it to the table if it is an on-the-fly-id, which it usually is, + // so it is available in all subsequent language boxes. + if (LanguageTag::isOnTheFlyID( nLang)) + const_cast<SvtLanguageTableImpl*>(this)->AddItem( sLangTag, nLang); + + return sLangTag; +} + +OUString SvtLanguageTable::GetLanguageString( const LanguageType eType ) +{ + return theLanguageTable::get().GetString( eType ); +} + +LanguageType SvtLanguageTableImpl::GetType( std::u16string_view rStr ) const +{ + LanguageType eType = LANGUAGE_DONTKNOW; + sal_uInt32 nCount = GetEntryCount(); + + for ( sal_uInt32 i = 0; i < nCount; ++i ) + { + if (m_aStrings[i].first == rStr) + { + eType = GetValue(i); + break; + } + } + return eType; +} + +LanguageType SvtLanguageTable::GetLanguageType( std::u16string_view rStr ) +{ + return theLanguageTable::get().GetType( rStr ); +} + +sal_uInt32 SvtLanguageTableImpl::GetEntryCount() const +{ + return m_aStrings.size(); +} + +sal_uInt32 SvtLanguageTable::GetLanguageEntryCount() +{ + return theLanguageTable::get().GetEntryCount(); +} + + +LanguageType SvtLanguageTableImpl::GetTypeAtIndex( sal_uInt32 nIndex ) const +{ + LanguageType nType = LANGUAGE_DONTKNOW; + if (nIndex < GetEntryCount()) + nType = GetValue(nIndex); + return nType; +} + +LanguageType SvtLanguageTable::GetLanguageTypeAtIndex( sal_uInt32 nIndex ) +{ + return theLanguageTable::get().GetTypeAtIndex( nIndex); +} + + +sal_uInt32 SvtLanguageTable::AddLanguageTag( const LanguageTag& rLanguageTag ) +{ + return theLanguageTable::get().AddItem( lcl_getDescription(rLanguageTag), + rLanguageTag.getLanguageType()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/openfiledroptargetlistener.cxx b/svtools/source/misc/openfiledroptargetlistener.cxx new file mode 100644 index 000000000..61d20c0bb --- /dev/null +++ b/svtools/source/misc/openfiledroptargetlistener.cxx @@ -0,0 +1,210 @@ +/* -*- 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 <svtools/openfiledroptargetlistener.hxx> + +#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> + +#include <vcl/transfer.hxx> +#include <sot/filelist.hxx> + +#include <osl/file.hxx> +#include <vcl/svapp.hxx> + +OpenFileDropTargetListener::OpenFileDropTargetListener( const css::uno::Reference< css::uno::XComponentContext >& xContext, + const css::uno::Reference< css::frame::XFrame >& xFrame ) + : m_xContext ( xContext ) + , m_xTargetFrame ( xFrame ) +{ +} + + +OpenFileDropTargetListener::~OpenFileDropTargetListener() +{ + m_xTargetFrame.clear(); + m_xContext.clear(); +} + + +void SAL_CALL OpenFileDropTargetListener::disposing( const css::lang::EventObject& ) +{ + m_xTargetFrame.clear(); + m_xContext.clear(); +} + + +void SAL_CALL OpenFileDropTargetListener::drop( const css::datatransfer::dnd::DropTargetDropEvent& dtde ) +{ + const sal_Int8 nAction = dtde.DropAction; + + try + { + if ( css::datatransfer::dnd::DNDConstants::ACTION_NONE != nAction ) + { + TransferableDataHelper aHelper( dtde.Transferable ); + bool bFormatFound = false; + FileList aFileList; + + // at first check filelist format + if ( aHelper.GetFileList( SotClipboardFormatId::FILE_LIST, aFileList ) ) + { + sal_uLong i, nCount = aFileList.Count(); + for ( i = 0; i < nCount; ++i ) + implts_OpenFile( aFileList.GetFile(i) ); + bFormatFound = true; + } + + // then, if necessary, the file format + OUString aFilePath; + if ( !bFormatFound && aHelper.GetString( SotClipboardFormatId::SIMPLE_FILE, aFilePath ) ) + implts_OpenFile( aFilePath ); + } + dtde.Context->dropComplete( css::datatransfer::dnd::DNDConstants::ACTION_NONE != nAction ); + } + catch( const css::uno::Exception& ) + { + } +} + + +void SAL_CALL OpenFileDropTargetListener::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& dtdee ) +{ + try + { + implts_BeginDrag( dtdee.SupportedDataFlavors ); + } + catch( const css::uno::Exception& ) + { + } + + dragOver( dtdee ); +} + + +void SAL_CALL OpenFileDropTargetListener::dragExit( const css::datatransfer::dnd::DropTargetEvent& ) +{ + try + { + implts_EndDrag(); + } + catch( const css::uno::Exception& ) + { + } +} + + +void SAL_CALL OpenFileDropTargetListener::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& dtde ) +{ + try + { + bool bAccept = ( implts_IsDropFormatSupported( SotClipboardFormatId::SIMPLE_FILE ) || + implts_IsDropFormatSupported( SotClipboardFormatId::FILE_LIST ) ); + + if ( !bAccept ) + dtde.Context->rejectDrag(); + else + dtde.Context->acceptDrag( css::datatransfer::dnd::DNDConstants::ACTION_COPY ); + } + catch( const css::uno::Exception& ) + { + } +} + + +void SAL_CALL OpenFileDropTargetListener::dropActionChanged( const css::datatransfer::dnd::DropTargetDragEvent& ) +{ +} + +void OpenFileDropTargetListener::implts_BeginDrag( const css::uno::Sequence< css::datatransfer::DataFlavor >& rSupportedDataFlavors ) +{ + /* SAFE { */ + SolarMutexGuard aGuard; + + m_aFormats.clear(); + TransferableDataHelper::FillDataFlavorExVector(rSupportedDataFlavors, m_aFormats); + /* } SAFE */ +} + +void OpenFileDropTargetListener::implts_EndDrag() +{ + /* SAFE { */ + SolarMutexGuard aGuard; + + m_aFormats.clear(); + /* } SAFE */ +} + +bool OpenFileDropTargetListener::implts_IsDropFormatSupported( SotClipboardFormatId nFormat ) +{ + /* SAFE { */ + SolarMutexGuard aGuard; + + for (auto const& format : m_aFormats) + { + if (nFormat == format.mnSotId) + { + return true; + } + } + /* } SAFE */ + + return false; +} + +void OpenFileDropTargetListener::implts_OpenFile( const OUString& rFilePath ) +{ + OUString aFileURL; + if ( osl::FileBase::getFileURLFromSystemPath( rFilePath, aFileURL ) != osl::FileBase::E_None ) + aFileURL = rFilePath; + + ::osl::FileStatus aStatus( osl_FileStatus_Mask_FileURL ); + ::osl::DirectoryItem aItem; + if( ::osl::FileBase::E_None == ::osl::DirectoryItem::get( aFileURL, aItem ) && + ::osl::FileBase::E_None == aItem.getFileStatus( aStatus ) ) + aFileURL = aStatus.getFileURL(); + + // open file + /* SAFE { */ + SolarMutexGuard aGuard; + + css::uno::Reference< css::frame::XFrame > xTargetFrame( m_xTargetFrame.get(), css::uno::UNO_QUERY ); + css::uno::Reference< css::util::XURLTransformer > xParser ( css::util::URLTransformer::create(m_xContext) ); + + if (xTargetFrame.is() && xParser.is()) + { + css::util::URL aURL; + aURL.Complete = aFileURL; + xParser->parseStrict(aURL); + + css::uno::Reference < css::frame::XDispatchProvider > xProvider( xTargetFrame, css::uno::UNO_QUERY ); + // Create a new task or recycle an existing one + css::uno::Reference< css::frame::XDispatch > xDispatcher = xProvider->queryDispatch( aURL, "_default", 0 ); + if ( xDispatcher.is() ) + xDispatcher->dispatch( aURL, css::uno::Sequence < css::beans::PropertyValue >() ); + } + /* } SAFE */ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/sampletext.cxx b/svtools/source/misc/sampletext.cxx new file mode 100644 index 000000000..52351e51d --- /dev/null +++ b/svtools/source/misc/sampletext.cxx @@ -0,0 +1,1672 @@ +/* -*- 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 <svtools/sampletext.hxx> +#include <vcl/font.hxx> +#include <vcl/outdev.hxx> +#include <vcl/virdev.hxx> +#include <vcl/fontcharmap.hxx> +#include <i18nutil/unicode.hxx> +#include <sal/log.hxx> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <vector> +#include <map> + +// This should only be used when a commonly used font incorrectly declares its +// coverage. If you add a font here, please leave a note explaining the issue +// that caused it to be added +static UScriptCode lcl_getHardCodedScriptNameForFont (const OutputDevice &rDevice) +{ + const OUString &rName = rDevice.GetFont().GetFamilyName(); + + if (rName == "GB18030 Bitmap") + { + // As of OSX 10.9, the system font "GB18030 Bitmap" incorrectly declares + // that it only covers "Phoenician" when in fact it's a Chinese font. + return USCRIPT_HAN; + } + else if (rName == "BiauKai") + { + // "BiauKai" makes crazy claims to cover BUGINESE, SUNDANESE, etc + // but in fact it's a Traditional Chinese font. + return USCRIPT_TRADITIONAL_HAN; + } + else if (rName == "GungSeo" || rName == "PCMyungjo" || rName == "PilGi") + { + // These have no OS/2 tables, but we know they are Korean fonts. + return USCRIPT_KOREAN; + } + else if (rName == "Hei" || rName == "Kai") + { + // These have no OS/2 tables, but we know they are Chinese fonts. + return USCRIPT_HAN; + } + else if (rName.startsWith("Bangla ")) + { + // "Bangla Sangam MN" claims it supports MALAYALAM, but it doesn't + // "Bangla MN" claims just DEVANAGARI and not an additional BENGALI + return USCRIPT_BENGALI; + } + else if (rName.startsWith("Gurmukhi ")) + { + // "Gurmukhi MN" claims it supports TAMIL, but it doesn't + return USCRIPT_GURMUKHI; + } + else if (rName.startsWith("Kannada ")) + { + // "Kannada MN" claims it supports TAMIL, but it doesn't + return USCRIPT_KANNADA; + } + else if (rName.startsWith("Lao ")) + { + // "Lao Sangam MN" claims it supports TAMIL, but it doesn't + return USCRIPT_LAO; + } + else if (rName.startsWith("Malayalam ")) + { + // "Malayalam MN" claims it supports TAMIL, but it doesn't + return USCRIPT_MALAYALAM; + } + else if (rName.startsWith("Sinhala ")) + { + // "Sinhala MN" claims it supports CYRILLIC + return USCRIPT_SINHALA; + } + else if (rName.startsWith("Telugu ")) + { + // "Telugu MN" claims it supports TAMIL, but it doesn't + return USCRIPT_TELUGU; + } + else if (rName.startsWith("Myanmar ")) + { + return USCRIPT_MYANMAR; + } + else if (rName == "InaiMathi") + { + // "InaiMathi" claims it supports GOTHIC and CJK_UNIFIED_IDEOGRAPHS as well as + // TAMIL, but it doesn't + return USCRIPT_TAMIL; + } + else if (rName == "Hannotate TC" || rName == "HanziPen TC" || rName == "Heiti TC" || rName == "Weibei TC") + { + // These fonts claim support for ARMENIAN and a bunch of other stuff they don't support + return USCRIPT_TRADITIONAL_HAN; + } + else if (rName == "Hannotate SC" || rName == "HanziPen SC" || rName == "Heiti SC" || rName == "Weibei SC") + { + // These fonts claim support for ARMENIAN and a bunch of other stuff they don't support + return USCRIPT_SIMPLIFIED_HAN; + } + return USCRIPT_INVALID_CODE; +} + +bool isSymbolFont(const vcl::Font &rFont) +{ + return (rFont.GetCharSet() == RTL_TEXTENCODING_SYMBOL) || + rFont.GetFamilyName().equalsIgnoreAsciiCase("Apple Color Emoji") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("cmsy10") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("cmex10") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("esint10") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("feta26") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("jsMath-cmsy10") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("jsMath-cmex10") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("msam10") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("msbm10") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("wasy10") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("Denemo") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("GlyphBasic1") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("GlyphBasic2") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("GlyphBasic3") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("GlyphBasic4") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("Letters Laughing") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("MusiQwik") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("MusiSync") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("stmary10") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("Symbol") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("Webdings") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("Wingdings") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("Wingdings 2") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("Wingdings 3") || + rFont.GetFamilyName().equalsIgnoreAsciiCase("Bookshelf Symbol 7") || + rFont.GetFamilyName().startsWith("STIXIntegrals") || + rFont.GetFamilyName().startsWith("STIXNonUnicode") || + rFont.GetFamilyName().startsWith("STIXSize") || + rFont.GetFamilyName().startsWith("STIXVariants") || + IsStarSymbol(rFont.GetFamilyName()); +} + +bool canRenderNameOfSelectedFont(OutputDevice const &rDevice) +{ + const vcl::Font &rFont = rDevice.GetFont(); + return !isSymbolFont(rFont) && ( -1 == rDevice.HasGlyphs(rFont, rFont.GetFamilyName()) ); +} + +OUString makeShortRepresentativeSymbolTextForSelectedFont(OutputDevice const &rDevice) +{ + if (rDevice.GetFont().GetFamilyName() == "Symbol") + { + static constexpr OUStringLiteral aImplAppleSymbolText = + u"\u03BC\u2202\u2211\u220F\u03C0\u222B\u03A9\u221A"; + bool bHasSampleTextGlyphs + = (-1 == rDevice.HasGlyphs(rDevice.GetFont(), aImplAppleSymbolText)); + //It's the Apple version + if (bHasSampleTextGlyphs) + return aImplAppleSymbolText; + static constexpr OUStringLiteral aImplAdobeSymbolText = + u"\uF06D\uF0B6\uF0E5\uF0D5\uF070\uF0F2\uF057\uF0D6"; + return aImplAdobeSymbolText; + } + + const bool bOpenSymbol = IsStarSymbol(rDevice.GetFont().GetFamilyName()); + + if (!bOpenSymbol) + { + FontCharMapRef xFontCharMap; + bool bHasCharMap = rDevice.GetFontCharMap(xFontCharMap); + if( bHasCharMap ) + { + // use some sample characters available in the font + sal_Unicode aText[8]; + + // start just above the PUA used by most symbol fonts + sal_uInt32 cNewChar = 0xFF00; + + const int nMaxCount = SAL_N_ELEMENTS(aText) - 1; + int nSkip = xFontCharMap->GetCharCount() / nMaxCount; + if( nSkip > 10 ) + nSkip = 10; + else if( nSkip <= 0 ) + nSkip = 1; + for( int i = 0; i < nMaxCount; ++i ) + { + sal_uInt32 cOldChar = cNewChar; + for( int j = nSkip; --j >= 0; ) + cNewChar = xFontCharMap->GetPrevChar( cNewChar ); + if( cOldChar == cNewChar ) + break; + aText[ i ] = static_cast<sal_Unicode>(cNewChar); // TODO: support UCS4 samples + aText[ i+1 ] = 0; + } + + return OUString(aText); + } + } + + static const sal_Unicode aImplSymbolFontText[] = { + 0xF021,0xF032,0xF043,0xF054,0xF065,0xF076,0xF0B7,0xF0C8,0}; + static const sal_Unicode aImplStarSymbolText[] = { + 0x2702,0x2708,0x270D,0xE033,0x2211,0x2288,0}; + const sal_Unicode* pText = bOpenSymbol ? aImplStarSymbolText : aImplSymbolFontText; + OUString sSampleText(pText); + bool bHasSampleTextGlyphs = (-1 == rDevice.HasGlyphs(rDevice.GetFont(), sSampleText)); + return bHasSampleTextGlyphs ? sSampleText : OUString(); +} + +//These ones are typically for use in the font dropdown box beside the +//fontname, so say things roughly like "Script/Alphabet/Name-Of-Major-Language" + +//Here we don't always know the language of course, only the script that can be +//written with the font. Often that's one single language written in that +//script, or a handful of related languages where the name of the script is the +//same between languages, or the name in the major language is known by most +//readers of the minor languages, e.g. Yiddish is written with the HEBREW +//script as well, the vast majority of Yiddish readers will be able to read +//Hebrew as well. +OUString makeShortRepresentativeTextForScript(UScriptCode eScript) +{ + OUString sSampleText; + switch (eScript) + { + case USCRIPT_GREEK: + { + static constexpr OUStringLiteral aGrek = + u"\u0391\u03BB\u03C6\u03AC\u03B2\u03B7\u03C4\u03BF"; + sSampleText = aGrek; + break; + } + case USCRIPT_HEBREW: + { + static constexpr OUStringLiteral aHebr = + u"\u05D0\u05B8\u05DC\u05B6\u05E3\u05BE\u05D1\u05B5" + "\u05BC\u05D9\u05EA \u05E2\u05B4\u05D1\u05B0" + "\u05E8\u05B4\u05D9"; + sSampleText = aHebr; + break; + } + case USCRIPT_ARABIC: + { + static constexpr OUStringLiteral aArab = + u"\u0623\u0628\u062C\u062F\u064A\u0629 \u0639" + "\u0631\u0628\u064A\u0629"; + sSampleText = aArab; + break; + } + case USCRIPT_ARMENIAN: + { + static constexpr OUStringLiteral aArmenian = + u"\u0561\u0575\u0562\u0578\u0582\u0562\u0565" + "\u0576"; + sSampleText = aArmenian; + break; + } + case USCRIPT_DEVANAGARI: + { + static constexpr OUStringLiteral aDeva = + u"\u0926\u0947\u0935\u0928\u093E\u0917\u0930\u0940"; + sSampleText = aDeva; + break; + } + case USCRIPT_BENGALI: + { + static constexpr OUStringLiteral aBeng = + u"\u09AC\u09BE\u0982\u09B2\u09BE \u09B2\u09BF" + "\u09AA\u09BF"; + sSampleText = aBeng; + break; + } + case USCRIPT_GURMUKHI: + { + static constexpr OUStringLiteral aGuru = + u"\u0A17\u0A41\u0A30\u0A2E\u0A41\u0A16\u0A40"; + sSampleText = aGuru; + break; + } + case USCRIPT_GUJARATI: + { + static constexpr OUStringLiteral aGujr = + u"\u0A97\u0AC1\u0A9C\u0AB0\u0ABE\u0AA4\u0aC0 " + "\u0AB2\u0ABF\u0AAA\u0ABF"; + sSampleText = aGujr; + break; + } + case USCRIPT_ORIYA: + { + static constexpr OUStringLiteral aOrya = + u"\u0B09\u0B24\u0B4D\u0B15\u0B33 \u0B32\u0B3F" + "\u0B2A\u0B3F"; + sSampleText = aOrya; + break; + } + case USCRIPT_TAMIL: + { + static constexpr OUStringLiteral aTaml = + u"\u0B85\u0BB0\u0BBF\u0B9A\u0BCD\u0B9A\u0BC1\u0BB5" + "\u0B9F\u0BBF"; + sSampleText = aTaml; + break; + } + case USCRIPT_TELUGU: + { + static constexpr OUStringLiteral aTelu = + u"\u0C24\u0C46\u0C32\u0C41\u0C17\u0C41"; + sSampleText = aTelu; + break; + } + case USCRIPT_KANNADA: + { + static constexpr OUStringLiteral aKnda = + u"\u0C95\u0CA8\u0CCD\u0CA8\u0CA1 \u0CB2\u0CBF" + "\u0CAA\u0CBF"; + sSampleText = aKnda; + break; + } + case USCRIPT_MALAYALAM: + { + static constexpr OUStringLiteral aMlym = + u"\u0D2E\u0D32\u0D2F\u0D3E\u0D33\u0D32\u0D3F\u0D2A" + "\u0D3F"; + sSampleText = aMlym; + break; + } + case USCRIPT_THAI: + { + static constexpr OUStringLiteral aThai = + u"\u0E2D\u0E31\u0E01\u0E29\u0E23\u0E44\u0E17\u0E22"; + sSampleText = aThai; + break; + } + case USCRIPT_LAO: + { + static constexpr OUStringLiteral aLao = + u"\u0EAD\u0EB1\u0E81\u0EAA\u0EAD\u0E99\u0EA5\u0EB2" + "\u0EA7"; + sSampleText = aLao; + break; + } + case USCRIPT_GEORGIAN: + { + static constexpr OUStringLiteral aGeorgian = + u"\u10D3\u10D0\u10DB\u10EC\u10D4\u10E0\u10DA\u10DD" + "\u10D1\u10D0"; + sSampleText = aGeorgian; + break; + } + case USCRIPT_JAMO: + case USCRIPT_HANGUL: + case USCRIPT_KOREAN: + { + static constexpr OUStringLiteral aHang = + u"\uD55C\uAE00"; + sSampleText = aHang; + break; + } + case USCRIPT_TIBETAN: + { + static constexpr OUStringLiteral aTibt = + u"\u0F51\u0F56\u0F74\u0F0B\u0F45\u0F53\u0F0B"; + sSampleText = aTibt; + break; + } + case USCRIPT_SYRIAC: + { + static constexpr OUStringLiteral aSyri = + u"\u0723\u071B\u072A\u0722\u0713\u0720\u0710"; + sSampleText = aSyri; + break; + } + case USCRIPT_THAANA: + { + static constexpr OUStringLiteral aThaa = + u"\u078C\u07A7\u0782\u07A6"; + sSampleText = aThaa; + break; + } + case USCRIPT_SINHALA: + { + static constexpr OUStringLiteral aSinh = + u"\u0DC1\u0DD4\u0DAF\u0DCA\u0DB0 \u0DC3\u0DD2" + "\u0D82\u0DC4\u0DBD"; + sSampleText = aSinh; + break; + } + case USCRIPT_MYANMAR: + { + static constexpr OUStringLiteral aMymr = + u"\u1019\u103C\u1014\u103A\u1019\u102C\u1021\u1000" + "\u1039\u1001\u101B\u102C"; + sSampleText = aMymr; + break; + } + case USCRIPT_ETHIOPIC: + { + static constexpr OUStringLiteral aEthi = + u"\u130D\u12D5\u12DD"; + sSampleText = aEthi; + break; + } + case USCRIPT_CHEROKEE: + { + static constexpr OUStringLiteral aCher = + u"\u13D7\u13AA\u13EA\u13B6\u13D9\u13D7"; + sSampleText = aCher; + break; + } + case USCRIPT_KHMER: + { + static constexpr OUStringLiteral aKhmr = + u"\u17A2\u1780\u17D2\u1781\u179A\u1780\u17D2\u179A" + "\u1798\u1781\u17C1\u1798\u179A\u1797\u17B6\u179F" + "\u17B6"; + sSampleText = aKhmr; + break; + } + case USCRIPT_MONGOLIAN: + { + static constexpr OUStringLiteral aMongolian = + u"\u182A\u1822\u1834\u1822\u182D\u180C"; + sSampleText = aMongolian; + break; + } + case USCRIPT_TAGALOG: + { + static constexpr OUStringLiteral aTagalog = + u"\u170A\u170A\u170C\u1712"; + sSampleText = aTagalog; + break; + } + case USCRIPT_NEW_TAI_LUE: + { + static constexpr OUStringLiteral aTalu = + u"\u1991\u19BA\u199F\u19B9\u19C9"; + sSampleText = aTalu; + break; + } + case USCRIPT_TRADITIONAL_HAN: + { + static constexpr OUStringLiteral aHant = + u"\u7E41"; + sSampleText = aHant; + break; + } + case USCRIPT_SIMPLIFIED_HAN: + { + static constexpr OUStringLiteral aHans = + u"\u7B80"; + sSampleText = aHans; + break; + } + case USCRIPT_HAN: + { + static constexpr OUStringLiteral aSimplifiedAndTraditionalChinese = + u"\u7B80\u7E41"; + sSampleText = aSimplifiedAndTraditionalChinese; + break; + } + case USCRIPT_JAPANESE: + { + static constexpr OUStringLiteral aJpan = + u"\u65E5\u672C\u8A9E"; + sSampleText = aJpan; + break; + } + case USCRIPT_YI: + { + static constexpr OUStringLiteral aYiii = + u"\uA188\uA320\uA071\uA0B7"; + sSampleText = aYiii; + break; + } + case USCRIPT_PHAGS_PA: + { + static constexpr OUStringLiteral aPhag = + u"\uA84F\uA861\uA843 \uA863\uA861\uA859 " + u"\uA850\uA85C\uA85E"; + sSampleText = aPhag; + break; + } + case USCRIPT_TAI_LE: + { + static constexpr OUStringLiteral aTale = + u"\u1956\u196D\u1970\u1956\u196C\u1973\u1951\u1968" + "\u1952\u1970"; + sSampleText = aTale; + break; + } + case USCRIPT_LATIN: + sSampleText = "Lorem ipsum"; + break; + default: + break; + } + return sSampleText; +} + +static OUString makeRepresentativeTextForScript(UScriptCode eScript) +{ + OUString sSampleText; + switch (eScript) + { + case USCRIPT_TRADITIONAL_HAN: + case USCRIPT_SIMPLIFIED_HAN: + case USCRIPT_HAN: + { + //Three Character Classic + static constexpr OUStringLiteral aZh = + u"\u4EBA\u4E4B\u521D \u6027\u672C\u5584"; + sSampleText = aZh; + break; + } + case USCRIPT_JAPANESE: + { + //'Beautiful Japanese' + static constexpr OUStringLiteral aJa = + u"\u7F8E\u3057\u3044\u65E5\u672C\u8A9E"; + sSampleText = aJa; + break; + } + case USCRIPT_JAMO: + case USCRIPT_KOREAN: + case USCRIPT_HANGUL: + { + //The essential condition for... + static constexpr OUStringLiteral aKo = + u"\uD0A4\uC2A4\uC758 \uACE0\uC720\uC870" + "\uAC74\uC740"; + sSampleText = aKo; + break; + } + default: + break; + } + + if (sSampleText.isEmpty()) + sSampleText = makeShortRepresentativeTextForScript(eScript); + return sSampleText; +} + +OUString makeShortMinimalTextForScript(UScriptCode eScript) +{ + OUString sSampleText; + switch (eScript) + { + case USCRIPT_GREEK: + { + static constexpr OUStringLiteral aGrek = + u"\u0391\u0392"; + sSampleText = aGrek; + break; + } + case USCRIPT_HEBREW: + { + static constexpr OUStringLiteral aHebr = + u"\u05D0\u05D1"; + sSampleText = aHebr; + break; + } + default: + break; + } + return sSampleText; +} + +static OUString makeMinimalTextForScript(UScriptCode eScript) +{ + return makeShortMinimalTextForScript(eScript); +} + +//These ones are typically for use in the font preview window in format +//character + +//There we generally know the language. Though it's possible for the language to +//be "none". + +//Currently we fall back to makeShortRepresentativeTextForScript when we don't +//have suitable strings +static OUString makeRepresentativeTextForLanguage(LanguageType eLang) +{ + OUString sRet; + LanguageType pri = primary(eLang); + if( pri == primary(LANGUAGE_ARMENIAN) ) + sRet = makeRepresentativeTextForScript(USCRIPT_ARMENIAN); + else if( pri == primary(LANGUAGE_CHINESE) ) + sRet = makeRepresentativeTextForScript(USCRIPT_HAN); + else if( pri == primary(LANGUAGE_GREEK) ) + sRet = makeRepresentativeTextForScript(USCRIPT_GREEK); + else if( pri.anyOf( + primary(LANGUAGE_HEBREW), + primary(LANGUAGE_YIDDISH)) ) + sRet = makeRepresentativeTextForScript(USCRIPT_HEBREW); + else if( pri == primary(LANGUAGE_ARABIC_SAUDI_ARABIA) ) + sRet = makeRepresentativeTextForScript(USCRIPT_ARABIC); + else if( pri == primary(LANGUAGE_HINDI) ) + sRet = makeRepresentativeTextForScript(USCRIPT_DEVANAGARI); + else if( pri == primary(LANGUAGE_ASSAMESE) ) + { + static constexpr OUStringLiteral aAs = + u"\u0985\u09B8\u09AE\u09C0\u09AF\u09BC\u09BE" + " \u0986\u0996\u09F0"; + sRet = aAs; + } + else if( pri == primary(LANGUAGE_BENGALI) ) + sRet = makeRepresentativeTextForScript(USCRIPT_BENGALI); + else if( pri == primary(LANGUAGE_PUNJABI) ) + sRet = makeRepresentativeTextForScript(USCRIPT_GURMUKHI); + else if( pri == primary(LANGUAGE_GUJARATI) ) + sRet = makeRepresentativeTextForScript(USCRIPT_GUJARATI); + else if( pri == primary(LANGUAGE_ODIA) ) + sRet = makeRepresentativeTextForScript(USCRIPT_ORIYA); + else if( pri == primary(LANGUAGE_TAMIL) ) + sRet = makeRepresentativeTextForScript(USCRIPT_TAMIL); + else if( pri == primary(LANGUAGE_TELUGU) ) + sRet = makeRepresentativeTextForScript(USCRIPT_TELUGU); + else if( pri == primary(LANGUAGE_KANNADA) ) + sRet = makeRepresentativeTextForScript(USCRIPT_KANNADA); + else if( pri == primary(LANGUAGE_MALAYALAM) ) + sRet = makeRepresentativeTextForScript(USCRIPT_MALAYALAM); + else if( pri == primary(LANGUAGE_THAI) ) + sRet = makeRepresentativeTextForScript(USCRIPT_THAI); + else if( pri == primary(LANGUAGE_LAO) ) + sRet = makeRepresentativeTextForScript(USCRIPT_LAO); + else if( pri == primary(LANGUAGE_GEORGIAN) ) + sRet = makeRepresentativeTextForScript(USCRIPT_GEORGIAN); + else if( pri == primary(LANGUAGE_KOREAN) ) + sRet = makeRepresentativeTextForScript(USCRIPT_KOREAN); + else if( pri == primary(LANGUAGE_TIBETAN) ) + sRet = makeRepresentativeTextForScript(USCRIPT_TIBETAN); + else if( pri == primary(LANGUAGE_SYRIAC) ) + sRet = makeRepresentativeTextForScript(USCRIPT_SYRIAC); + else if( pri == primary(LANGUAGE_SINHALESE_SRI_LANKA) ) + sRet = makeRepresentativeTextForScript(USCRIPT_SINHALA); + else if( pri == primary(LANGUAGE_BURMESE) ) + sRet = makeRepresentativeTextForScript(USCRIPT_MYANMAR); + else if( pri == primary(LANGUAGE_AMHARIC_ETHIOPIA) ) + sRet = makeRepresentativeTextForScript(USCRIPT_ETHIOPIC); + else if( pri == primary(LANGUAGE_CHEROKEE_UNITED_STATES) ) + sRet = makeRepresentativeTextForScript(USCRIPT_CHEROKEE); + else if( pri == primary(LANGUAGE_KHMER) ) + sRet = makeRepresentativeTextForScript(USCRIPT_KHMER); + else if( pri == primary(LANGUAGE_MONGOLIAN_MONGOLIAN_LSO) ) + { + if (eLang.anyOf( + LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA, + LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA, + LANGUAGE_MONGOLIAN_MONGOLIAN_LSO)) + sRet = makeRepresentativeTextForScript(USCRIPT_MONGOLIAN); + } + else if( pri == primary(LANGUAGE_JAPANESE) ) + sRet = makeRepresentativeTextForScript(USCRIPT_JAPANESE); + else if( pri == primary(LANGUAGE_YI) ) + sRet = makeRepresentativeTextForScript(USCRIPT_YI); + else if( pri == primary(LANGUAGE_GAELIC_IRELAND) ) + { + static constexpr OUStringLiteral aGa = + u"T\u00E9acs Samplach"; + sRet = aGa; + } + + return sRet; +} + +namespace +{ +#if OSL_DEBUG_LEVEL > 0 + void lcl_dump_unicode_coverage(const std::optional<std::bitset<vcl::UnicodeCoverage::MAX_UC_ENUM>> &roIn) + { + if (!roIn) + { + SAL_INFO("svtools", "<NOTHING>"); + return; + } + auto & rIn(*roIn); + if (rIn.none()) + { + SAL_INFO("svtools", "<NONE>"); + return; + } + if (rIn[vcl::UnicodeCoverage::BASIC_LATIN]) + SAL_INFO("svtools", "BASIC_LATIN"); + if (rIn[vcl::UnicodeCoverage::LATIN_1_SUPPLEMENT]) + SAL_INFO("svtools", "LATIN_1_SUPPLEMENT"); + if (rIn[vcl::UnicodeCoverage::LATIN_EXTENDED_A]) + SAL_INFO("svtools", "LATIN_EXTENDED_A"); + if (rIn[vcl::UnicodeCoverage::LATIN_EXTENDED_B]) + SAL_INFO("svtools", "LATIN_EXTENDED_B"); + if (rIn[vcl::UnicodeCoverage::IPA_EXTENSIONS]) + SAL_INFO("svtools", "IPA_EXTENSIONS"); + if (rIn[vcl::UnicodeCoverage::SPACING_MODIFIER_LETTERS]) + SAL_INFO("svtools", "SPACING_MODIFIER_LETTERS"); + if (rIn[vcl::UnicodeCoverage::COMBINING_DIACRITICAL_MARKS]) + SAL_INFO("svtools", "COMBINING_DIACRITICAL_MARKS"); + if (rIn[vcl::UnicodeCoverage::GREEK_AND_COPTIC]) + SAL_INFO("svtools", "GREEK_AND_COPTIC"); + if (rIn[vcl::UnicodeCoverage::COPTIC]) + SAL_INFO("svtools", "COPTIC"); + if (rIn[vcl::UnicodeCoverage::CYRILLIC]) + SAL_INFO("svtools", "CYRILLIC"); + if (rIn[vcl::UnicodeCoverage::ARMENIAN]) + SAL_INFO("svtools", "ARMENIAN"); + if (rIn[vcl::UnicodeCoverage::HEBREW]) + SAL_INFO("svtools", "HEBREW"); + if (rIn[vcl::UnicodeCoverage::VAI]) + SAL_INFO("svtools", "VAI"); + if (rIn[vcl::UnicodeCoverage::ARABIC]) + SAL_INFO("svtools", "ARABIC"); + if (rIn[vcl::UnicodeCoverage::NKO]) + SAL_INFO("svtools", "NKO"); + if (rIn[vcl::UnicodeCoverage::DEVANAGARI]) + SAL_INFO("svtools", "DEVANAGARI"); + if (rIn[vcl::UnicodeCoverage::BENGALI]) + SAL_INFO("svtools", "BENGALI"); + if (rIn[vcl::UnicodeCoverage::GURMUKHI]) + SAL_INFO("svtools", "GURMUKHI"); + if (rIn[vcl::UnicodeCoverage::GUJARATI]) + SAL_INFO("svtools", "GUJARATI"); + if (rIn[vcl::UnicodeCoverage::ODIA]) + SAL_INFO("svtools", "ODIA"); + if (rIn[vcl::UnicodeCoverage::TAMIL]) + SAL_INFO("svtools", "TAMIL"); + if (rIn[vcl::UnicodeCoverage::TELUGU]) + SAL_INFO("svtools", "TELUGU"); + if (rIn[vcl::UnicodeCoverage::KANNADA]) + SAL_INFO("svtools", "KANNADA"); + if (rIn[vcl::UnicodeCoverage::MALAYALAM]) + SAL_INFO("svtools", "MALAYALAM"); + if (rIn[vcl::UnicodeCoverage::THAI]) + SAL_INFO("svtools", "THAI"); + if (rIn[vcl::UnicodeCoverage::LAO]) + SAL_INFO("svtools", "LAO"); + if (rIn[vcl::UnicodeCoverage::GEORGIAN]) + SAL_INFO("svtools", "GEORGIAN"); + if (rIn[vcl::UnicodeCoverage::BALINESE]) + SAL_INFO("svtools", "BALINESE"); + if (rIn[vcl::UnicodeCoverage::HANGUL_JAMO]) + SAL_INFO("svtools", "HANGUL_JAMO"); + if (rIn[vcl::UnicodeCoverage::LATIN_EXTENDED_ADDITIONAL]) + SAL_INFO("svtools", "LATIN_EXTENDED_ADDITIONAL"); + if (rIn[vcl::UnicodeCoverage::GREEK_EXTENDED]) + SAL_INFO("svtools", "GREEK_EXTENDED"); + if (rIn[vcl::UnicodeCoverage::GENERAL_PUNCTUATION]) + SAL_INFO("svtools", "GENERAL_PUNCTUATION"); + if (rIn[vcl::UnicodeCoverage::SUPERSCRIPTS_AND_SUBSCRIPTS]) + SAL_INFO("svtools", "SUPERSCRIPTS_AND_SUBSCRIPTS"); + if (rIn[vcl::UnicodeCoverage::CURRENCY_SYMBOLS]) + SAL_INFO("svtools", "CURRENCY_SYMBOLS"); + if (rIn[vcl::UnicodeCoverage::COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS]) + SAL_INFO("svtools", "COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS"); + if (rIn[vcl::UnicodeCoverage::LETTERLIKE_SYMBOLS]) + SAL_INFO("svtools", "LETTERLIKE_SYMBOLS"); + if (rIn[vcl::UnicodeCoverage::NUMBER_FORMS]) + SAL_INFO("svtools", "NUMBER_FORMS"); + if (rIn[vcl::UnicodeCoverage::ARROWS]) + SAL_INFO("svtools", "ARROWS"); + if (rIn[vcl::UnicodeCoverage::MATHEMATICAL_OPERATORS]) + SAL_INFO("svtools", "MATHEMATICAL_OPERATORS"); + if (rIn[vcl::UnicodeCoverage::MISCELLANEOUS_TECHNICAL]) + SAL_INFO("svtools", "MISCELLANEOUS_TECHNICAL"); + if (rIn[vcl::UnicodeCoverage::CONTROL_PICTURES]) + SAL_INFO("svtools", "CONTROL_PICTURES"); + if (rIn[vcl::UnicodeCoverage::OPTICAL_CHARACTER_RECOGNITION]) + SAL_INFO("svtools", "OPTICAL_CHARACTER_RECOGNITION"); + if (rIn[vcl::UnicodeCoverage::ENCLOSED_ALPHANUMERICS]) + SAL_INFO("svtools", "ENCLOSED_ALPHANUMERICS"); + if (rIn[vcl::UnicodeCoverage::BOX_DRAWING]) + SAL_INFO("svtools", "BOX_DRAWING"); + if (rIn[vcl::UnicodeCoverage::BLOCK_ELEMENTS]) + SAL_INFO("svtools", "BLOCK_ELEMENTS"); + if (rIn[vcl::UnicodeCoverage::GEOMETRIC_SHAPES]) + SAL_INFO("svtools", "GEOMETRIC_SHAPES"); + if (rIn[vcl::UnicodeCoverage::MISCELLANEOUS_SYMBOLS]) + SAL_INFO("svtools", "MISCELLANEOUS_SYMBOLS"); + if (rIn[vcl::UnicodeCoverage::DINGBATS]) + SAL_INFO("svtools", "DINGBATS"); + if (rIn[vcl::UnicodeCoverage::CJK_SYMBOLS_AND_PUNCTUATION]) + SAL_INFO("svtools", "CJK_SYMBOLS_AND_PUNCTUATION"); + if (rIn[vcl::UnicodeCoverage::HIRAGANA]) + SAL_INFO("svtools", "HIRAGANA"); + if (rIn[vcl::UnicodeCoverage::KATAKANA]) + SAL_INFO("svtools", "KATAKANA"); + if (rIn[vcl::UnicodeCoverage::BOPOMOFO]) + SAL_INFO("svtools", "BOPOMOFO"); + if (rIn[vcl::UnicodeCoverage::HANGUL_COMPATIBILITY_JAMO]) + SAL_INFO("svtools", "HANGUL_COMPATIBILITY_JAMO"); + if (rIn[vcl::UnicodeCoverage::PHAGS_PA]) + SAL_INFO("svtools", "PHAGS_PA"); + if (rIn[vcl::UnicodeCoverage::ENCLOSED_CJK_LETTERS_AND_MONTHS]) + SAL_INFO("svtools", "ENCLOSED_CJK_LETTERS_AND_MONTHS"); + if (rIn[vcl::UnicodeCoverage::CJK_COMPATIBILITY]) + SAL_INFO("svtools", "CJK_COMPATIBILITY"); + if (rIn[vcl::UnicodeCoverage::HANGUL_SYLLABLES]) + SAL_INFO("svtools", "HANGUL_SYLLABLES"); + if (rIn[vcl::UnicodeCoverage::NONPLANE_0]) + SAL_INFO("svtools", "NONPLANE_0"); + if (rIn[vcl::UnicodeCoverage::PHOENICIAN]) + SAL_INFO("svtools", "PHOENICIAN"); + if (rIn[vcl::UnicodeCoverage::CJK_UNIFIED_IDEOGRAPHS]) + SAL_INFO("svtools", "CJK_UNIFIED_IDEOGRAPHS"); + if (rIn[vcl::UnicodeCoverage::PRIVATE_USE_AREA_PLANE_0]) + SAL_INFO("svtools", "PRIVATE_USE_AREA_PLANE_0"); + if (rIn[vcl::UnicodeCoverage::CJK_STROKES]) + SAL_INFO("svtools", "CJK_STROKES"); + if (rIn[vcl::UnicodeCoverage::ALPHABETIC_PRESENTATION_FORMS]) + SAL_INFO("svtools", "ALPHABETIC_PRESENTATION_FORMS"); + if (rIn[vcl::UnicodeCoverage::ARABIC_PRESENTATION_FORMS_A]) + SAL_INFO("svtools", "ARABIC_PRESENTATION_FORMS_A"); + if (rIn[vcl::UnicodeCoverage::COMBINING_HALF_MARKS]) + SAL_INFO("svtools", "COMBINING_HALF_MARKS"); + if (rIn[vcl::UnicodeCoverage::VERTICAL_FORMS]) + SAL_INFO("svtools", "VERTICAL_FORMS"); + if (rIn[vcl::UnicodeCoverage::SMALL_FORM_VARIANTS]) + SAL_INFO("svtools", "SMALL_FORM_VARIANTS"); + if (rIn[vcl::UnicodeCoverage::ARABIC_PRESENTATION_FORMS_B]) + SAL_INFO("svtools", "ARABIC_PRESENTATION_FORMS_B"); + if (rIn[vcl::UnicodeCoverage::HALFWIDTH_AND_FULLWIDTH_FORMS]) + SAL_INFO("svtools", "HALFWIDTH_AND_FULLWIDTH_FORMS"); + if (rIn[vcl::UnicodeCoverage::SPECIALS]) + SAL_INFO("svtools", "SPECIALS"); + if (rIn[vcl::UnicodeCoverage::TIBETAN]) + SAL_INFO("svtools", "TIBETAN"); + if (rIn[vcl::UnicodeCoverage::SYRIAC]) + SAL_INFO("svtools", "SYRIAC"); + if (rIn[vcl::UnicodeCoverage::THAANA]) + SAL_INFO("svtools", "THAANA"); + if (rIn[vcl::UnicodeCoverage::SINHALA]) + SAL_INFO("svtools", "SINHALA"); + if (rIn[vcl::UnicodeCoverage::MYANMAR]) + SAL_INFO("svtools", "MYANMAR"); + if (rIn[vcl::UnicodeCoverage::ETHIOPIC]) + SAL_INFO("svtools", "ETHIOPIC"); + if (rIn[vcl::UnicodeCoverage::CHEROKEE]) + SAL_INFO("svtools", "CHEROKEE"); + if (rIn[vcl::UnicodeCoverage::UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS]) + SAL_INFO("svtools", "UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS"); + if (rIn[vcl::UnicodeCoverage::OGHAM]) + SAL_INFO("svtools", "OGHAM"); + if (rIn[vcl::UnicodeCoverage::RUNIC]) + SAL_INFO("svtools", "RUNIC"); + if (rIn[vcl::UnicodeCoverage::KHMER]) + SAL_INFO("svtools", "KHMER"); + if (rIn[vcl::UnicodeCoverage::MONGOLIAN]) + SAL_INFO("svtools", "MONGOLIAN"); + if (rIn[vcl::UnicodeCoverage::BRAILLE_PATTERNS]) + SAL_INFO("svtools", "BRAILLE_PATTERNS"); + if (rIn[vcl::UnicodeCoverage::YI_SYLLABLES]) + SAL_INFO("svtools", "YI_SYLLABLES"); + if (rIn[vcl::UnicodeCoverage::TAGALOG]) + SAL_INFO("svtools", "TAGALOG"); + if (rIn[vcl::UnicodeCoverage::OLD_ITALIC]) + SAL_INFO("svtools", "OLD_ITALIC"); + if (rIn[vcl::UnicodeCoverage::GOTHIC]) + SAL_INFO("svtools", "GOTHIC"); + if (rIn[vcl::UnicodeCoverage::DESERET]) + SAL_INFO("svtools", "DESERET"); + if (rIn[vcl::UnicodeCoverage::BYZANTINE_MUSICAL_SYMBOLS]) + SAL_INFO("svtools", "BYZANTINE_MUSICAL_SYMBOLS"); + if (rIn[vcl::UnicodeCoverage::MATHEMATICAL_ALPHANUMERIC_SYMBOLS]) + SAL_INFO("svtools", "MATHEMATICAL_ALPHANUMERIC_SYMBOLS"); + if (rIn[vcl::UnicodeCoverage::PRIVATE_USE_PLANE_15]) + SAL_INFO("svtools", "PRIVATE_USE_PLANE_15"); + if (rIn[vcl::UnicodeCoverage::VARIATION_SELECTORS]) + SAL_INFO("svtools", "VARIATION_SELECTORS"); + if (rIn[vcl::UnicodeCoverage::TAGS]) + SAL_INFO("svtools", "TAGS"); + if (rIn[vcl::UnicodeCoverage::LIMBU]) + SAL_INFO("svtools", "LIMBU"); + if (rIn[vcl::UnicodeCoverage::TAI_LE]) + SAL_INFO("svtools", "TAI_LE"); + if (rIn[vcl::UnicodeCoverage::NEW_TAI_LUE]) + SAL_INFO("svtools", "NEW_TAI_LUE"); + if (rIn[vcl::UnicodeCoverage::BUGINESE]) + SAL_INFO("svtools", "BUGINESE"); + if (rIn[vcl::UnicodeCoverage::GLAGOLITIC]) + SAL_INFO("svtools", "GLAGOLITIC"); + if (rIn[vcl::UnicodeCoverage::TIFINAGH]) + SAL_INFO("svtools", "TIFINAGH"); + if (rIn[vcl::UnicodeCoverage::YIJING_HEXAGRAM_SYMBOLS]) + SAL_INFO("svtools", "YIJING_HEXAGRAM_SYMBOLS"); + if (rIn[vcl::UnicodeCoverage::SYLOTI_NAGRI]) + SAL_INFO("svtools", "SYLOTI_NAGRI"); + if (rIn[vcl::UnicodeCoverage::LINEAR_B_SYLLABARY]) + SAL_INFO("svtools", "LINEAR_B_SYLLABARY"); + if (rIn[vcl::UnicodeCoverage::ANCIENT_GREEK_NUMBERS]) + SAL_INFO("svtools", "ANCIENT_GREEK_NUMBERS"); + if (rIn[vcl::UnicodeCoverage::UGARITIC]) + SAL_INFO("svtools", "UGARITIC"); + if (rIn[vcl::UnicodeCoverage::OLD_PERSIAN]) + SAL_INFO("svtools", "OLD_PERSIAN"); + if (rIn[vcl::UnicodeCoverage::SHAVIAN]) + SAL_INFO("svtools", "SHAVIAN"); + if (rIn[vcl::UnicodeCoverage::OSMANYA]) + SAL_INFO("svtools", "OSMANYA"); + if (rIn[vcl::UnicodeCoverage::CYPRIOT_SYLLABARY]) + SAL_INFO("svtools", "CYPRIOT_SYLLABARY"); + if (rIn[vcl::UnicodeCoverage::KHAROSHTHI]) + SAL_INFO("svtools", "KHAROSHTHI"); + if (rIn[vcl::UnicodeCoverage::TAI_XUAN_JING_SYMBOLS]) + SAL_INFO("svtools", "TAI_XUAN_JING_SYMBOLS"); + if (rIn[vcl::UnicodeCoverage::CUNEIFORM]) + SAL_INFO("svtools", "CUNEIFORM"); + if (rIn[vcl::UnicodeCoverage::COUNTING_ROD_NUMERALS]) + SAL_INFO("svtools", "COUNTING_ROD_NUMERALS"); + if (rIn[vcl::UnicodeCoverage::SUNDANESE]) + SAL_INFO("svtools", "SUNDANESE"); + if (rIn[vcl::UnicodeCoverage::LEPCHA]) + SAL_INFO("svtools", "LEPCHA"); + if (rIn[vcl::UnicodeCoverage::OL_CHIKI]) + SAL_INFO("svtools", "OL_CHIKI"); + if (rIn[vcl::UnicodeCoverage::SAURASHTRA]) + SAL_INFO("svtools", "SAURASHTRA"); + if (rIn[vcl::UnicodeCoverage::KAYAH_LI]) + SAL_INFO("svtools", "KAYAH_LI"); + if (rIn[vcl::UnicodeCoverage::REJANG]) + SAL_INFO("svtools", "REJANG"); + if (rIn[vcl::UnicodeCoverage::CHAM]) + SAL_INFO("svtools", "CHAM"); + if (rIn[vcl::UnicodeCoverage::ANCIENT_SYMBOLS]) + SAL_INFO("svtools", "ANCIENT_SYMBOLS"); + if (rIn[vcl::UnicodeCoverage::PHAISTOS_DISC]) + SAL_INFO("svtools", "PHAISTOS_DISC"); + if (rIn[vcl::UnicodeCoverage::CARIAN]) + SAL_INFO("svtools", "CARIAN"); + if (rIn[vcl::UnicodeCoverage::DOMINO_TILES]) + SAL_INFO("svtools", "DOMINO_TILES"); + if (rIn[vcl::UnicodeCoverage::RESERVED1]) + SAL_INFO("svtools", "RESERVED1"); + if (rIn[vcl::UnicodeCoverage::RESERVED2]) + SAL_INFO("svtools", "RESERVED2"); + if (rIn[vcl::UnicodeCoverage::RESERVED3]) + SAL_INFO("svtools", "RESERVED3"); + if (rIn[vcl::UnicodeCoverage::RESERVED4]) + SAL_INFO("svtools", "RESERVED4"); + if (!(rIn[vcl::UnicodeCoverage::RESERVED5])) + return; + + SAL_INFO("svtools", "RESERVED5"); + } + + void lcl_dump_codepage_coverage(const std::optional<std::bitset<vcl::CodePageCoverage::MAX_CP_ENUM>> &roIn) + { + if (!roIn) + { + SAL_INFO("svtools", "<NOTHING>"); + return; + } + auto & rIn(*roIn); + if (rIn.none()) + { + SAL_INFO("svtools", "<NONE>"); + return; + } + if (rIn[vcl::CodePageCoverage::CP1252]) + SAL_INFO("svtools", "CP1252"); + if (rIn[vcl::CodePageCoverage::CP1250]) + SAL_INFO("svtools", "CP1250"); + if (rIn[vcl::CodePageCoverage::CP1251]) + SAL_INFO("svtools", "CP1251"); + if (rIn[vcl::CodePageCoverage::CP1253]) + SAL_INFO("svtools", "CP1253"); + if (rIn[vcl::CodePageCoverage::CP1254]) + SAL_INFO("svtools", "CP1254"); + if (rIn[vcl::CodePageCoverage::CP1255]) + SAL_INFO("svtools", "CP1255"); + if (rIn[vcl::CodePageCoverage::CP1256]) + SAL_INFO("svtools", "CP1256"); + if (rIn[vcl::CodePageCoverage::CP1257]) + SAL_INFO("svtools", "CP1257"); + if (rIn[vcl::CodePageCoverage::CP1258]) + SAL_INFO("svtools", "CP1258"); + if (rIn[vcl::CodePageCoverage::CP874]) + SAL_INFO("svtools", "CP874"); + if (rIn[vcl::CodePageCoverage::CP932]) + SAL_INFO("svtools", "CP932"); + if (rIn[vcl::CodePageCoverage::CP936]) + SAL_INFO("svtools", "CP936"); + if (rIn[vcl::CodePageCoverage::CP949]) + SAL_INFO("svtools", "CP949"); + if (rIn[vcl::CodePageCoverage::CP950]) + SAL_INFO("svtools", "CP950"); + if (rIn[vcl::CodePageCoverage::CP1361]) + SAL_INFO("svtools", "CP1361"); + if (rIn[vcl::CodePageCoverage::CP869]) + SAL_INFO("svtools", "CP869"); + if (rIn[vcl::CodePageCoverage::CP866]) + SAL_INFO("svtools", "CP866"); + if (rIn[vcl::CodePageCoverage::CP865]) + SAL_INFO("svtools", "CP865"); + if (rIn[vcl::CodePageCoverage::CP864]) + SAL_INFO("svtools", "CP864"); + if (rIn[vcl::CodePageCoverage::CP863]) + SAL_INFO("svtools", "CP863"); + if (rIn[vcl::CodePageCoverage::CP862]) + SAL_INFO("svtools", "CP862"); + if (rIn[vcl::CodePageCoverage::CP861]) + SAL_INFO("svtools", "CP861"); + if (rIn[vcl::CodePageCoverage::CP860]) + SAL_INFO("svtools", "CP860"); + if (rIn[vcl::CodePageCoverage::CP857]) + SAL_INFO("svtools", "CP857"); + if (rIn[vcl::CodePageCoverage::CP855]) + SAL_INFO("svtools", "CP855"); + if (rIn[vcl::CodePageCoverage::CP852]) + SAL_INFO("svtools", "CP852"); + if (rIn[vcl::CodePageCoverage::CP775]) + SAL_INFO("svtools", "CP775"); + if (rIn[vcl::CodePageCoverage::CP737]) + SAL_INFO("svtools", "CP737"); + if (rIn[vcl::CodePageCoverage::CP780]) + SAL_INFO("svtools", "CP780"); + if (rIn[vcl::CodePageCoverage::CP850]) + SAL_INFO("svtools", "CP850"); + if (!(rIn[vcl::CodePageCoverage::CP437])) + return; + + SAL_INFO("svtools", "CP437"); + } +#endif + + std::bitset<vcl::UnicodeCoverage::MAX_UC_ENUM> getMaskByScriptType(sal_Int16 nScriptType) + { + std::bitset<vcl::UnicodeCoverage::MAX_UC_ENUM> aMask; + aMask.set(); + + for (size_t i = 0; i < vcl::UnicodeCoverage::MAX_UC_ENUM; ++i) + { + using vcl::UnicodeCoverage::UnicodeCoverageEnum; + UScriptCode eScriptCode = otCoverageToScript(static_cast<UnicodeCoverageEnum>(i)); + if (unicode::getScriptClassFromUScriptCode(eScriptCode) == nScriptType) + aMask.set(i, false); + } + + return aMask; + } + + //false for all bits considered "Latin" by LibreOffice + std::bitset<vcl::UnicodeCoverage::MAX_UC_ENUM> const & getLatinMask() + { + static std::bitset<vcl::UnicodeCoverage::MAX_UC_ENUM> s_Mask(getMaskByScriptType(css::i18n::ScriptType::LATIN)); + return s_Mask; + } + + //false for all bits considered "Asian" by LibreOffice + std::bitset<vcl::UnicodeCoverage::MAX_UC_ENUM> const & getCJKMask() + { + static std::bitset<vcl::UnicodeCoverage::MAX_UC_ENUM> s_Mask(getMaskByScriptType(css::i18n::ScriptType::ASIAN)); + return s_Mask; + } + + //false for all bits considered "Complex" by LibreOffice + std::bitset<vcl::UnicodeCoverage::MAX_UC_ENUM> const & getCTLMask() + { + static std::bitset<vcl::UnicodeCoverage::MAX_UC_ENUM> s_Mask(getMaskByScriptType(css::i18n::ScriptType::COMPLEX)); + return s_Mask; + } + + //false for all bits considered "WEAK" by LibreOffice + std::bitset<vcl::UnicodeCoverage::MAX_UC_ENUM> const & getWeakMask() + { + static std::bitset<vcl::UnicodeCoverage::MAX_UC_ENUM> s_Mask(getMaskByScriptType(css::i18n::ScriptType::WEAK)); + return s_Mask; + } + + //Nearly every font supports some basic Latin + std::bitset<vcl::UnicodeCoverage::MAX_UC_ENUM> getCommonLatnSubsetMask() + { + std::bitset<vcl::UnicodeCoverage::MAX_UC_ENUM> aMask; + aMask.set(); + aMask.set(vcl::UnicodeCoverage::BASIC_LATIN, false); + aMask.set(vcl::UnicodeCoverage::LATIN_1_SUPPLEMENT, false); + aMask.set(vcl::UnicodeCoverage::LATIN_EXTENDED_A, false); + aMask.set(vcl::UnicodeCoverage::LATIN_EXTENDED_B, false); + aMask.set(vcl::UnicodeCoverage::LATIN_EXTENDED_ADDITIONAL, false); + return aMask; + } + + template<size_t N> + size_t find_first(std::bitset<N> const& rSet) + { + for (size_t i = 0; i < N; ++i) + { + if (rSet.test(i)) + return i; + } + assert(false); // see current usage + return N; + } + + UScriptCode getScript(const vcl::FontCapabilities &rFontCapabilities) + { + using vcl::UnicodeCoverage::UnicodeCoverageEnum; + + std::bitset<vcl::UnicodeCoverage::MAX_UC_ENUM> aMasked; + if (rFontCapabilities.oUnicodeRange) + { + aMasked = *rFontCapabilities.oUnicodeRange & getWeakMask(); + } + + if (aMasked.count() == 1) + return otCoverageToScript(static_cast<UnicodeCoverageEnum>(find_first(aMasked))); + + if (aMasked[vcl::UnicodeCoverage::ARABIC]) + { + aMasked.set(vcl::UnicodeCoverage::ARABIC_PRESENTATION_FORMS_A, false); + aMasked.set(vcl::UnicodeCoverage::ARABIC_PRESENTATION_FORMS_B, false); + aMasked.set(vcl::UnicodeCoverage::NKO, false); + //Probably strongly tuned for Arabic + if (aMasked.count() == 1) + return USCRIPT_ARABIC; + if (aMasked.count() == 2 && aMasked[vcl::UnicodeCoverage::SYRIAC]) + return USCRIPT_SYRIAC; + } + + if (aMasked[vcl::UnicodeCoverage::DEVANAGARI]) + { + aMasked.set(vcl::UnicodeCoverage::DEVANAGARI, false); + //Probably strongly tuned for a single Indic script + if (aMasked.count() == 1) + return otCoverageToScript(static_cast<UnicodeCoverageEnum>(find_first(aMasked))); + } + + aMasked.set(vcl::UnicodeCoverage::GREEK_EXTENDED, false); + aMasked.set(vcl::UnicodeCoverage::GREEK_AND_COPTIC, false); + // tdf#88484 + // Some fonts set the Arabic Presentation Forms-B bit because they + // support U+FEFF (Zero Width Space) which happens to be in that block + // but it isn’t an Arabic code point. By the time we reach here we + // decided this isn’t an Arabic font, so it should be safe. + aMasked.set(vcl::UnicodeCoverage::ARABIC_PRESENTATION_FORMS_B, false); + if (aMasked.count() == 1) + return otCoverageToScript(static_cast<UnicodeCoverageEnum>(find_first(aMasked))); + + if (aMasked[vcl::UnicodeCoverage::CYRILLIC]) + { + //Probably strongly tuned for Georgian + if (aMasked.count() == 2 && aMasked[vcl::UnicodeCoverage::GEORGIAN]) + return USCRIPT_GEORGIAN; + } + + aMasked &= getCJKMask(); + + aMasked.set(vcl::UnicodeCoverage::CYRILLIC, false); + aMasked.set(vcl::UnicodeCoverage::THAI, false); + aMasked.set(vcl::UnicodeCoverage::DESERET, false); + aMasked.set(vcl::UnicodeCoverage::PHAGS_PA, false); + + //So, possibly a CJK font + if (!aMasked.count() && rFontCapabilities.oCodePageRange) + { + std::bitset<vcl::CodePageCoverage::MAX_CP_ENUM> aCJKCodePageMask; + aCJKCodePageMask.set(vcl::CodePageCoverage::CP932); + aCJKCodePageMask.set(vcl::CodePageCoverage::CP936); + aCJKCodePageMask.set(vcl::CodePageCoverage::CP949); + aCJKCodePageMask.set(vcl::CodePageCoverage::CP950); + aCJKCodePageMask.set(vcl::CodePageCoverage::CP1361); + std::bitset<vcl::CodePageCoverage::MAX_CP_ENUM> aMaskedCodePage = + *rFontCapabilities.oCodePageRange & aCJKCodePageMask; + //fold Korean + if (aMaskedCodePage[vcl::CodePageCoverage::CP1361]) + { + aMaskedCodePage.set(vcl::CodePageCoverage::CP949); + aMaskedCodePage.set(vcl::CodePageCoverage::CP1361, false); + } + + if (aMaskedCodePage.count() == 1) + { + if (aMaskedCodePage[vcl::CodePageCoverage::CP932]) + return USCRIPT_JAPANESE; + if (aMaskedCodePage[vcl::CodePageCoverage::CP949]) + return USCRIPT_KOREAN; + if (aMaskedCodePage[vcl::CodePageCoverage::CP936]) + return USCRIPT_SIMPLIFIED_HAN; + if (aMaskedCodePage[vcl::CodePageCoverage::CP950]) + return USCRIPT_TRADITIONAL_HAN; + } + + if (aMaskedCodePage.count()) + return USCRIPT_HAN; + } + + return USCRIPT_COMMON; + } +} + +const std::map<UScriptCode, std::vector<OUString>> distCjkMap = +{ + { USCRIPT_KOREAN, { " KR", "Korean"} }, // Korean + { USCRIPT_JAPANESE, {" JP", "Japanese"} } , // Japanese + { USCRIPT_SIMPLIFIED_HAN, {" SC", " GB", "S Chinese"} }, // Simplified Chinese Family + { USCRIPT_TRADITIONAL_HAN, {" TC", " HC", " TW", " HK", " MO", "T Chinese"} }// Traditional Chinese Family +}; +namespace +{ + UScriptCode attemptToDisambiguateHan(UScriptCode eScript, OutputDevice const &rDevice) + { + //If we're a CJK font, see if we seem to be tuned for C, J or K + if (eScript == USCRIPT_HAN) + { + const vcl::Font &rFont = rDevice.GetFont(); + + bool bKore = false, bJpan = false, bHant = false, bHans = false; + + static constexpr OUStringLiteral sKorean = u"\u4E6D\u4E76\u596C"; + if (-1 == rDevice.HasGlyphs(rFont, sKorean)) + bKore = true; + + static constexpr OUStringLiteral sJapanese = u"\u5968\u67A0\u9D8F"; + if (-1 == rDevice.HasGlyphs(rFont, sJapanese)) + bJpan = true; + + static constexpr OUStringLiteral sTraditionalChinese = u"\u555F\u96DE"; + if (-1 == rDevice.HasGlyphs(rFont, sTraditionalChinese)) + bHant = true; + + static constexpr OUStringLiteral sSimplifiedChinese = u"\u4E61\u542F\u5956"; + if (-1 == rDevice.HasGlyphs(rFont, sSimplifiedChinese)) + bHans = true; + + if (bKore && !bJpan && !bHans && !bHant) { + eScript = USCRIPT_KOREAN; + return eScript; + } + else if (bJpan && !bKore && !bHans && !bHant) { + eScript = USCRIPT_JAPANESE; + return eScript; + } + else if (bHans && !bHant && !bKore && !bJpan) { + eScript = USCRIPT_SIMPLIFIED_HAN; + return eScript; + } + else if (bHant && !bHans && !bKore && !bJpan) { + eScript = USCRIPT_TRADITIONAL_HAN; + return eScript; + } + + // for the last time, Check the ISO code strings or font specific strings + const OUString &rName = rDevice.GetFont().GetFamilyName(); + std::map<UScriptCode, std::vector<OUString>>::const_iterator distCjkMapIt; + for (distCjkMapIt = distCjkMap.begin(); distCjkMapIt != distCjkMap.end(); ++distCjkMapIt) { + std::vector<OUString> cjkCodeList = distCjkMapIt->second; + std::vector<OUString>::const_iterator cjkPtr; + for (cjkPtr = cjkCodeList.begin(); cjkPtr != cjkCodeList.end(); ++cjkPtr) { + if (rName.indexOf(*cjkPtr) > 0) { + return distCjkMapIt->first; + } + } + } + //otherwise fall-through as USCRIPT_HAN and expect a combined Hant/Hans preview + } + return eScript; + } +} + +OUString makeShortRepresentativeTextForSelectedFont(OutputDevice const &rDevice) +{ + UScriptCode eScript = lcl_getHardCodedScriptNameForFont(rDevice); + if (eScript == USCRIPT_INVALID_CODE) + { + vcl::FontCapabilities aFontCapabilities; + if (!rDevice.GetFontCapabilities(aFontCapabilities)) + return OUString(); + +#if OSL_DEBUG_LEVEL > 0 + lcl_dump_unicode_coverage(aFontCapabilities.oUnicodeRange); + lcl_dump_codepage_coverage(aFontCapabilities.oCodePageRange); +#endif + + if (aFontCapabilities.oUnicodeRange) + *aFontCapabilities.oUnicodeRange &= getCommonLatnSubsetMask(); + + //If this font is probably tuned to display a single non-Latin + //script and the font name is itself in Latin, then show a small + //chunk of representative text for that script + eScript = getScript(aFontCapabilities); + if (eScript == USCRIPT_COMMON) + return OUString(); + + eScript = attemptToDisambiguateHan(eScript, rDevice); + } + + OUString sSampleText = makeShortRepresentativeTextForScript(eScript); + bool bHasSampleTextGlyphs = (-1 == rDevice.HasGlyphs(rDevice.GetFont(), sSampleText)); + return bHasSampleTextGlyphs ? sSampleText : OUString(); +} + +UScriptCode otCoverageToScript(vcl::UnicodeCoverage::UnicodeCoverageEnum eOTCoverage) +{ + UScriptCode eRet = USCRIPT_COMMON; + switch (eOTCoverage) + { + case vcl::UnicodeCoverage::BASIC_LATIN: + case vcl::UnicodeCoverage::LATIN_1_SUPPLEMENT: + case vcl::UnicodeCoverage::LATIN_EXTENDED_A: + case vcl::UnicodeCoverage::LATIN_EXTENDED_B: + eRet = USCRIPT_LATIN; + break; + case vcl::UnicodeCoverage::COMBINING_DIACRITICAL_MARKS: + eRet = USCRIPT_INHERITED; + break; + case vcl::UnicodeCoverage::GREEK_AND_COPTIC: + eRet = USCRIPT_GREEK; + break; + case vcl::UnicodeCoverage::COPTIC: + eRet = USCRIPT_COPTIC; + break; + case vcl::UnicodeCoverage::CYRILLIC: + eRet = USCRIPT_CYRILLIC; + break; + case vcl::UnicodeCoverage::ARMENIAN: + eRet = USCRIPT_ARMENIAN; + break; + case vcl::UnicodeCoverage::HEBREW: + eRet = USCRIPT_HEBREW; + break; + case vcl::UnicodeCoverage::VAI: + eRet = USCRIPT_VAI; + break; + case vcl::UnicodeCoverage::ARABIC: + eRet = USCRIPT_ARABIC; + break; + case vcl::UnicodeCoverage::NKO: + eRet = USCRIPT_NKO; + break; + case vcl::UnicodeCoverage::DEVANAGARI: + eRet = USCRIPT_DEVANAGARI; + break; + case vcl::UnicodeCoverage::BENGALI: + eRet = USCRIPT_BENGALI; + break; + case vcl::UnicodeCoverage::GURMUKHI: + eRet = USCRIPT_GURMUKHI; + break; + case vcl::UnicodeCoverage::GUJARATI: + eRet = USCRIPT_GUJARATI; + break; + case vcl::UnicodeCoverage::ODIA: + eRet = USCRIPT_ORIYA; + break; + case vcl::UnicodeCoverage::TAMIL: + eRet = USCRIPT_TAMIL; + break; + case vcl::UnicodeCoverage::TELUGU: + eRet = USCRIPT_TELUGU; + break; + case vcl::UnicodeCoverage::KANNADA: + eRet = USCRIPT_KANNADA; + break; + case vcl::UnicodeCoverage::MALAYALAM: + eRet = USCRIPT_MALAYALAM; + break; + case vcl::UnicodeCoverage::THAI: + eRet = USCRIPT_THAI; + break; + case vcl::UnicodeCoverage::LAO: + eRet = USCRIPT_LAO; + break; + case vcl::UnicodeCoverage::GEORGIAN: + eRet = USCRIPT_GEORGIAN; + break; + case vcl::UnicodeCoverage::BALINESE: + eRet = USCRIPT_BALINESE; + break; + case vcl::UnicodeCoverage::HANGUL_JAMO: + eRet = USCRIPT_HANGUL; + break; + case vcl::UnicodeCoverage::LATIN_EXTENDED_ADDITIONAL: + eRet = USCRIPT_LATIN; + break; + case vcl::UnicodeCoverage::GREEK_EXTENDED: + eRet = USCRIPT_GREEK; + break; + case vcl::UnicodeCoverage::CURRENCY_SYMBOLS: + eRet = USCRIPT_SYMBOLS; + break; + case vcl::UnicodeCoverage::COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS: + eRet = USCRIPT_INHERITED; + break; + case vcl::UnicodeCoverage::LETTERLIKE_SYMBOLS: + case vcl::UnicodeCoverage::NUMBER_FORMS: + case vcl::UnicodeCoverage::ARROWS: + eRet = USCRIPT_SYMBOLS; + break; + case vcl::UnicodeCoverage::MATHEMATICAL_OPERATORS: + eRet = USCRIPT_MATHEMATICAL_NOTATION; + break; + case vcl::UnicodeCoverage::MISCELLANEOUS_TECHNICAL: + case vcl::UnicodeCoverage::OPTICAL_CHARACTER_RECOGNITION: + case vcl::UnicodeCoverage::BOX_DRAWING: + case vcl::UnicodeCoverage::BLOCK_ELEMENTS: + case vcl::UnicodeCoverage::GEOMETRIC_SHAPES: + case vcl::UnicodeCoverage::MISCELLANEOUS_SYMBOLS: + case vcl::UnicodeCoverage::DINGBATS: + case vcl::UnicodeCoverage::CJK_SYMBOLS_AND_PUNCTUATION: + eRet = USCRIPT_SYMBOLS; + break; + case vcl::UnicodeCoverage::HIRAGANA: + eRet = USCRIPT_HIRAGANA; + break; + case vcl::UnicodeCoverage::KATAKANA: + eRet = USCRIPT_KATAKANA; + break; + case vcl::UnicodeCoverage::BOPOMOFO: + eRet = USCRIPT_BOPOMOFO; + break; + case vcl::UnicodeCoverage::HANGUL_COMPATIBILITY_JAMO: + eRet = USCRIPT_HANGUL; + break; + case vcl::UnicodeCoverage::PHAGS_PA: + eRet = USCRIPT_PHAGS_PA; + break; + case vcl::UnicodeCoverage::ENCLOSED_CJK_LETTERS_AND_MONTHS: + eRet = USCRIPT_HANGUL; + break; + case vcl::UnicodeCoverage::CJK_COMPATIBILITY: + eRet = USCRIPT_HAN; + break; + case vcl::UnicodeCoverage::HANGUL_SYLLABLES: + eRet = USCRIPT_HANGUL; + break; + case vcl::UnicodeCoverage::PHOENICIAN: + eRet = USCRIPT_PHOENICIAN; + break; + case vcl::UnicodeCoverage::CJK_UNIFIED_IDEOGRAPHS: + case vcl::UnicodeCoverage::CJK_STROKES: + eRet = USCRIPT_HAN; + break; + case vcl::UnicodeCoverage::ARABIC_PRESENTATION_FORMS_A: + eRet = USCRIPT_ARABIC; + break; + case vcl::UnicodeCoverage::COMBINING_HALF_MARKS: + eRet = USCRIPT_INHERITED; + break; + case vcl::UnicodeCoverage::ARABIC_PRESENTATION_FORMS_B: + eRet = USCRIPT_ARABIC; + break; + case vcl::UnicodeCoverage::TIBETAN: + eRet = USCRIPT_TIBETAN; + break; + case vcl::UnicodeCoverage::SYRIAC: + eRet = USCRIPT_SYRIAC; + break; + case vcl::UnicodeCoverage::THAANA: + eRet = USCRIPT_THAANA; + break; + case vcl::UnicodeCoverage::SINHALA: + eRet = USCRIPT_SINHALA; + break; + case vcl::UnicodeCoverage::MYANMAR: + eRet = USCRIPT_MYANMAR; + break; + case vcl::UnicodeCoverage::ETHIOPIC: + eRet = USCRIPT_ETHIOPIC; + break; + case vcl::UnicodeCoverage::CHEROKEE: + eRet = USCRIPT_CHEROKEE; + break; + case vcl::UnicodeCoverage::UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS: + eRet = USCRIPT_CANADIAN_ABORIGINAL; + break; + case vcl::UnicodeCoverage::OGHAM: + eRet = USCRIPT_OGHAM; + break; + case vcl::UnicodeCoverage::RUNIC: + eRet = USCRIPT_RUNIC; + break; + case vcl::UnicodeCoverage::KHMER: + eRet = USCRIPT_KHMER; + break; + case vcl::UnicodeCoverage::MONGOLIAN: + eRet = USCRIPT_MONGOLIAN; + break; + case vcl::UnicodeCoverage::BRAILLE_PATTERNS: + eRet = USCRIPT_BRAILLE; + break; + case vcl::UnicodeCoverage::YI_SYLLABLES: + eRet = USCRIPT_YI; + break; + case vcl::UnicodeCoverage::TAGALOG: + eRet = USCRIPT_TAGALOG; + break; + case vcl::UnicodeCoverage::OLD_ITALIC: + eRet = USCRIPT_OLD_ITALIC; + break; + case vcl::UnicodeCoverage::GOTHIC: + eRet = USCRIPT_GOTHIC; + break; + case vcl::UnicodeCoverage::DESERET: + eRet = USCRIPT_DESERET; + break; + case vcl::UnicodeCoverage::BYZANTINE_MUSICAL_SYMBOLS: + case vcl::UnicodeCoverage::MATHEMATICAL_ALPHANUMERIC_SYMBOLS: + case vcl::UnicodeCoverage::PRIVATE_USE_PLANE_15: + eRet = USCRIPT_SYMBOLS; + break; + case vcl::UnicodeCoverage::VARIATION_SELECTORS: + eRet = USCRIPT_INHERITED; + break; + case vcl::UnicodeCoverage::TAGS: + eRet = USCRIPT_SYMBOLS; + break; + case vcl::UnicodeCoverage::LIMBU: + eRet = USCRIPT_LIMBU; + break; + case vcl::UnicodeCoverage::TAI_LE: + eRet = USCRIPT_TAI_LE; + break; + case vcl::UnicodeCoverage::NEW_TAI_LUE: + eRet = USCRIPT_NEW_TAI_LUE; + break; + case vcl::UnicodeCoverage::BUGINESE: + eRet = USCRIPT_BUGINESE; + break; + case vcl::UnicodeCoverage::GLAGOLITIC: + eRet = USCRIPT_GLAGOLITIC; + break; + case vcl::UnicodeCoverage::TIFINAGH: + eRet = USCRIPT_TIFINAGH; + break; + case vcl::UnicodeCoverage::YIJING_HEXAGRAM_SYMBOLS: + eRet = USCRIPT_SYMBOLS; + break; + case vcl::UnicodeCoverage::SYLOTI_NAGRI: + eRet = USCRIPT_SYLOTI_NAGRI; + break; + case vcl::UnicodeCoverage::LINEAR_B_SYLLABARY: + eRet = USCRIPT_LINEAR_B; + break; + case vcl::UnicodeCoverage::ANCIENT_GREEK_NUMBERS: + eRet = USCRIPT_GREEK; + break; + case vcl::UnicodeCoverage::UGARITIC: + eRet = USCRIPT_UGARITIC; + break; + case vcl::UnicodeCoverage::OLD_PERSIAN: + eRet = USCRIPT_OLD_PERSIAN; + break; + case vcl::UnicodeCoverage::SHAVIAN: + eRet = USCRIPT_SHAVIAN; + break; + case vcl::UnicodeCoverage::OSMANYA: + eRet = USCRIPT_OSMANYA; + break; + case vcl::UnicodeCoverage::CYPRIOT_SYLLABARY: + eRet = USCRIPT_CYPRIOT; + break; + case vcl::UnicodeCoverage::KHAROSHTHI: + eRet = USCRIPT_KHAROSHTHI; + break; + case vcl::UnicodeCoverage::CUNEIFORM: + eRet = USCRIPT_CUNEIFORM; + break; + case vcl::UnicodeCoverage::SUNDANESE: + eRet = USCRIPT_SUNDANESE; + break; + case vcl::UnicodeCoverage::LEPCHA: + eRet = USCRIPT_LEPCHA; + break; + case vcl::UnicodeCoverage::OL_CHIKI: + eRet = USCRIPT_OL_CHIKI; + break; + case vcl::UnicodeCoverage::SAURASHTRA: + eRet = USCRIPT_SAURASHTRA; + break; + case vcl::UnicodeCoverage::KAYAH_LI: + eRet = USCRIPT_KAYAH_LI; + break; + case vcl::UnicodeCoverage::REJANG: + eRet = USCRIPT_REJANG; + break; + case vcl::UnicodeCoverage::CHAM: + eRet = USCRIPT_CHAM; + break; + case vcl::UnicodeCoverage::CARIAN: + eRet = USCRIPT_CARIAN; + break; + case vcl::UnicodeCoverage::DOMINO_TILES: + case vcl::UnicodeCoverage::TAI_XUAN_JING_SYMBOLS: + case vcl::UnicodeCoverage::COUNTING_ROD_NUMERALS: + case vcl::UnicodeCoverage::ANCIENT_SYMBOLS: + case vcl::UnicodeCoverage::PHAISTOS_DISC: + eRet = USCRIPT_SYMBOLS; + break; + case vcl::UnicodeCoverage::IPA_EXTENSIONS: + case vcl::UnicodeCoverage::SPECIALS: + case vcl::UnicodeCoverage::HALFWIDTH_AND_FULLWIDTH_FORMS: + case vcl::UnicodeCoverage::VERTICAL_FORMS: + case vcl::UnicodeCoverage::SMALL_FORM_VARIANTS: + case vcl::UnicodeCoverage::ALPHABETIC_PRESENTATION_FORMS: + case vcl::UnicodeCoverage::PRIVATE_USE_AREA_PLANE_0: + case vcl::UnicodeCoverage::NONPLANE_0: + case vcl::UnicodeCoverage::ENCLOSED_ALPHANUMERICS: + case vcl::UnicodeCoverage::CONTROL_PICTURES: + case vcl::UnicodeCoverage::SUPERSCRIPTS_AND_SUBSCRIPTS: + case vcl::UnicodeCoverage::GENERAL_PUNCTUATION: + case vcl::UnicodeCoverage::SPACING_MODIFIER_LETTERS: + case vcl::UnicodeCoverage::RESERVED1: + case vcl::UnicodeCoverage::RESERVED2: + case vcl::UnicodeCoverage::RESERVED3: + case vcl::UnicodeCoverage::RESERVED4: + case vcl::UnicodeCoverage::RESERVED5: + case vcl::UnicodeCoverage::MAX_UC_ENUM: + break; + } + return eRet; +} + +OUString makeRepresentativeTextForFont(sal_Int16 nScriptType, const vcl::Font &rFont) +{ + OUString sRet(makeRepresentativeTextForLanguage(rFont.GetLanguage())); + + ScopedVclPtrInstance< VirtualDevice > aDevice; + if (sRet.isEmpty() || (-1 != aDevice->HasGlyphs(rFont, sRet))) + { + aDevice->SetFont(rFont); + vcl::FontCapabilities aFontCapabilities; + if (aDevice->GetFontCapabilities(aFontCapabilities)) + { +#if OSL_DEBUG_LEVEL > 0 + lcl_dump_unicode_coverage(aFontCapabilities.oUnicodeRange); +#endif + + if (aFontCapabilities.oUnicodeRange) + { + *aFontCapabilities.oUnicodeRange &= getWeakMask(); + + if (nScriptType != css::i18n::ScriptType::ASIAN) + { + *aFontCapabilities.oUnicodeRange &= getCJKMask(); + aFontCapabilities.oCodePageRange.reset(); + } + if (nScriptType != css::i18n::ScriptType::LATIN) + *aFontCapabilities.oUnicodeRange &= getLatinMask(); + if (nScriptType != css::i18n::ScriptType::COMPLEX) + *aFontCapabilities.oUnicodeRange &= getCTLMask(); + } + +#if OSL_DEBUG_LEVEL > 0 + SAL_INFO("svtools", "minimal"); + lcl_dump_unicode_coverage(aFontCapabilities.oUnicodeRange); + lcl_dump_codepage_coverage(aFontCapabilities.oCodePageRange); +#endif + + UScriptCode eScript = getScript(aFontCapabilities); + + if (nScriptType == css::i18n::ScriptType::ASIAN) + eScript = attemptToDisambiguateHan(eScript, *aDevice); + + sRet = makeRepresentativeTextForScript(eScript); + } + + if (sRet.isEmpty()) + { + if (nScriptType == css::i18n::ScriptType::COMPLEX) + { + sRet = makeRepresentativeTextForScript(USCRIPT_HEBREW); + if (-1 != aDevice->HasGlyphs(rFont, sRet)) + { + sRet = makeMinimalTextForScript(USCRIPT_HEBREW); + if (-1 != aDevice->HasGlyphs(rFont, sRet)) + sRet = makeRepresentativeTextForScript(USCRIPT_ARABIC); + } + } + else if (nScriptType == css::i18n::ScriptType::LATIN) + sRet = makeRepresentativeTextForScript(USCRIPT_LATIN); + } + } + + return sRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/stringtransfer.cxx b/svtools/source/misc/stringtransfer.cxx new file mode 100644 index 000000000..14b28dfe3 --- /dev/null +++ b/svtools/source/misc/stringtransfer.cxx @@ -0,0 +1,97 @@ +/* -*- 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 <rtl/ref.hxx> +#include <sot/exchange.hxx> +#include <svtools/stringtransfer.hxx> + + +namespace svt +{ + + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::datatransfer; + + + //= OStringTransferable + OStringTransferable::OStringTransferable(const OUString& rContent) + : TransferDataContainer() + , m_sContent(rContent) + { + } + + void OStringTransferable::AddSupportedFormats() + { + AddFormat(SotClipboardFormatId::STRING); + } + + void OStringTransferable::SetData(const OUString& rContent) + { + m_sContent = rContent; + ClearFormats(); // invalidate m_aAny so new data will take effect + } + + bool OStringTransferable::GetData( const DataFlavor& _rFlavor, const OUString& /*rDestDoc*/ ) + { + SotClipboardFormatId nFormat = SotExchange::GetFormat( _rFlavor ); + if (SotClipboardFormatId::STRING == nFormat) + return SetString( m_sContent ); + + return false; + } + + //= OStringTransfer + void OStringTransfer::CopyString( const OUString& _rContent, vcl::Window* _pWindow ) + { + rtl::Reference<OStringTransferable> pTransferable = new OStringTransferable( _rContent ); + pTransferable->CopyToClipboard( _pWindow ); + } + + bool OStringTransfer::PasteString( OUString& _rContent, vcl::Window* _pWindow ) + { + TransferableDataHelper aClipboardData = TransferableDataHelper::CreateFromSystemClipboard( _pWindow ); + + // check for a string format + const DataFlavorExVector& rFormats = aClipboardData.GetDataFlavorExVector(); + for (auto const& format : rFormats) + { + if (SotClipboardFormatId::STRING == format.mnSotId) + { + OUString sContent; + bool bSuccess = aClipboardData.GetString( SotClipboardFormatId::STRING, sContent ); + _rContent = sContent; + return bSuccess; + } + } + + return false; + } + + void OStringTransfer::StartStringDrag( const OUString& _rContent, vcl::Window* _pWindow, sal_Int8 _nDragSourceActions ) + { + rtl::Reference<OStringTransferable> pTransferable = new OStringTransferable( _rContent ); + pTransferable->StartDrag(_pWindow, _nDragSourceActions); + } + +} // namespace svt + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/svtresid.cxx b/svtools/source/misc/svtresid.cxx new file mode 100644 index 000000000..5d886a01b --- /dev/null +++ b/svtools/source/misc/svtresid.cxx @@ -0,0 +1,26 @@ +/* -*- 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 <svtools/svtresid.hxx> + +std::locale SvtResLocale() { return Translate::Create("svt"); } + +OUString SvtResId(TranslateId aId) { return Translate::get(aId, SvtResLocale()); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/templatefoldercache.cxx b/svtools/source/misc/templatefoldercache.cxx new file mode 100644 index 000000000..d92d22d62 --- /dev/null +++ b/svtools/source/misc/templatefoldercache.cxx @@ -0,0 +1,799 @@ +/* -*- 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 <osl/file.hxx> +#include <svtools/templatefoldercache.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/ucb/XDynamicResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/util/theOfficeInstallationDirectories.hpp> +#include <ucbhelper/content.hxx> +#include <osl/diagnose.h> +#include <rtl/ref.hxx> +#include <salhelper/simplereferenceobject.hxx> +#include <tools/time.hxx> +#include <tools/urlobj.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <unotools/pathoptions.hxx> + +#include <comphelper/processfactory.hxx> + +#include <mutex> +#include <vector> +#include <algorithm> + + +namespace svt +{ + + + using namespace ::utl; + using namespace ::com::sun::star; + using namespace ::com::sun::star::sdbc; + using namespace ::com::sun::star::ucb; + using namespace ::com::sun::star::uno; + + + //= helpers + + + static SvStream& WriteDateTime( SvStream& _rStorage, const util::DateTime& _rDate ) + { + sal_uInt16 hundredthSeconds = static_cast< sal_uInt16 >( _rDate.NanoSeconds / tools::Time::nanoPerCenti ); + _rStorage.WriteUInt16( hundredthSeconds ); + + _rStorage.WriteUInt16( _rDate.Seconds ); + _rStorage.WriteUInt16( _rDate.Minutes ); + _rStorage.WriteUInt16( _rDate.Hours ); + _rStorage.WriteUInt16( _rDate.Day ); + _rStorage.WriteUInt16( _rDate.Month ); + _rStorage.WriteInt16( _rDate.Year ); + + return _rStorage; + } + + + static SvStream& operator >> ( SvStream& _rStorage, util::DateTime& _rDate ) + { + sal_uInt16 hundredthSeconds; + _rStorage.ReadUInt16( hundredthSeconds ); + _rDate.NanoSeconds = static_cast< sal_uInt32 >( hundredthSeconds ) * tools::Time::nanoPerCenti; + + _rStorage.ReadUInt16( _rDate.Seconds ); + _rStorage.ReadUInt16( _rDate.Minutes ); + _rStorage.ReadUInt16( _rDate.Hours ); + _rStorage.ReadUInt16( _rDate.Day ); + _rStorage.ReadUInt16( _rDate.Month ); + _rStorage.ReadInt16( _rDate.Year ); + + return _rStorage; + } + + //= TemplateContent + + namespace { + + struct TemplateContent; + + } + + typedef ::std::vector< ::rtl::Reference< TemplateContent > > TemplateFolderContent; + typedef TemplateFolderContent::const_iterator ConstFolderIterator; + typedef TemplateFolderContent::iterator FolderIterator; + + namespace { + + /** a struct describing one content in one of the template dirs (or at least it's relevant aspects) + */ + struct TemplateContent : public ::salhelper::SimpleReferenceObject + { + public: + + private: + INetURLObject m_aURL; + util::DateTime m_aLastModified; // date of last modification as reported by UCP + TemplateFolderContent m_aSubContents; // sorted (by name) list of the children + + private: + void implResetDate( ) + { + m_aLastModified.NanoSeconds = m_aLastModified.Seconds = m_aLastModified.Minutes = m_aLastModified.Hours = 0; + m_aLastModified.Day = m_aLastModified.Month = m_aLastModified.Year = 0; + } + + private: + virtual ~TemplateContent() override; + + public: + explicit TemplateContent( const INetURLObject& _rURL ); + + // attribute access + OUString getURL( ) const { return m_aURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ); } + void setModDate( const util::DateTime& _rDate ) { m_aLastModified = _rDate; } + const util::DateTime& getModDate( ) const { return m_aLastModified; } + + TemplateFolderContent& getSubContents() { return m_aSubContents; } + const TemplateFolderContent& getSubContents() const { return m_aSubContents; } + + ConstFolderIterator end() const { return m_aSubContents.end(); } + TemplateFolderContent::size_type + size() const { return m_aSubContents.size(); } + + void push_back( const ::rtl::Reference< TemplateContent >& _rxNewElement ) + { m_aSubContents.push_back( _rxNewElement ); } + }; + + } + + TemplateContent::TemplateContent( const INetURLObject& _rURL ) + :m_aURL( _rURL ) + { + DBG_ASSERT( INetProtocol::NotValid != m_aURL.GetProtocol(), "TemplateContent::TemplateContent: invalid URL!" ); + implResetDate(); + } + + + TemplateContent::~TemplateContent() + { + } + + + //= stl helpers + + namespace { + + /// compares two TemplateContent by URL + struct TemplateContentURLLess + { + bool operator() ( const ::rtl::Reference< TemplateContent >& _rxLHS, const ::rtl::Reference< TemplateContent >& _rxRHS ) const + { + return _rxLHS->getURL() < _rxRHS->getURL(); + } + }; + + + /// sorts the sib contents of a TemplateFolderContent + struct SubContentSort + { + void operator() ( TemplateFolderContent& _rFolder ) const + { + // sort the directory by name + ::std::sort( + _rFolder.begin(), + _rFolder.end(), + TemplateContentURLLess() + ); + + // sort the sub directories by name + ::std::for_each( + _rFolder.begin(), + _rFolder.end(), + *this + ); + } + + void operator() ( const ::rtl::Reference< TemplateContent >& _rxContent ) const + { + if ( _rxContent.is() && _rxContent->size() ) + { + operator()( _rxContent->getSubContents() ); + } + } + }; + + /** does a deep compare of two template contents + */ + struct TemplateContentEqual + { + + bool operator() (const ::rtl::Reference< TemplateContent >& _rLHS, const ::rtl::Reference< TemplateContent >& _rRHS ) + { + if ( !_rLHS.is() || !_rRHS.is() ) + { + OSL_FAIL( "TemplateContentEqual::operator(): invalid contents!" ); + return true; + // this is not strictly true, in case only one is invalid - but this is a heavy error anyway + } + + if ( _rLHS->getURL() != _rRHS->getURL() ) + return false; + + if ( _rLHS->getModDate() != _rRHS->getModDate() ) + return false; + + if ( _rLHS->getSubContents().size() != _rRHS->getSubContents().size() ) + return false; + + if ( !_rLHS->getSubContents().empty() ) + { // there are children + // -> compare them + ::std::pair< FolderIterator, FolderIterator > aFirstDifferent = ::std::mismatch( + _rLHS->getSubContents().begin(), + _rLHS->getSubContents().end(), + _rRHS->getSubContents().begin(), + *this + ); + if ( aFirstDifferent.first != _rLHS->getSubContents().end() ) + return false;// the sub contents differ + } + + return true; + } + }; + + + /// base class for functors which act on a SvStream + struct StorageHelper + { + protected: + SvStream& m_rStorage; + explicit StorageHelper( SvStream& _rStorage ) : m_rStorage( _rStorage ) { } + }; + + + struct StoreContentURL : public StorageHelper + { + uno::Reference< util::XOfficeInstallationDirectories > m_xOfficeInstDirs; + + StoreContentURL( SvStream& _rStorage, + const uno::Reference< + util::XOfficeInstallationDirectories > & + xOfficeInstDirs ) + : StorageHelper( _rStorage ), m_xOfficeInstDirs( xOfficeInstDirs ) { } + + void operator() ( const ::rtl::Reference< TemplateContent >& _rxContent ) const + { + // use the base class operator with the local name of the content + OUString sURL = _rxContent->getURL(); + // #116281# Keep office installation relocatable. Never store + // any direct references to office installation directory. + sURL = m_xOfficeInstDirs->makeRelocatableURL( sURL ); + m_rStorage.WriteUniOrByteString( sURL, m_rStorage.GetStreamCharSet() ); + } + }; + + + /// functor which stores the complete content of a TemplateContent + struct StoreFolderContent : public StorageHelper + { + uno::Reference< util::XOfficeInstallationDirectories > m_xOfficeInstDirs; + + public: + StoreFolderContent( SvStream& _rStorage, + const uno::Reference< + util::XOfficeInstallationDirectories > & + xOfficeInstDirs ) + : StorageHelper( _rStorage ), m_xOfficeInstDirs( xOfficeInstDirs ) { } + + + void operator() ( const TemplateContent& _rContent ) const + { + // store the info about this content + WriteDateTime( m_rStorage, _rContent.getModDate() ); + + // store the info about the children + // the number + m_rStorage.WriteInt32( _rContent.size() ); + // their URLs ( the local name is not enough, since URL might be not a hierarchical one, "expand:" for example ) + ::std::for_each( + _rContent.getSubContents().begin(), + _rContent.getSubContents().end(), + StoreContentURL( m_rStorage, m_xOfficeInstDirs ) + ); + // their content + ::std::for_each( + _rContent.getSubContents().begin(), + _rContent.getSubContents().end(), + *this + ); + } + + + void operator() ( const ::rtl::Reference< TemplateContent >& _rxContent ) const + { + if ( _rxContent.is() ) + { + operator()( *_rxContent ); + } + } + }; + + + /// functor which reads a complete TemplateContent instance + struct ReadFolderContent : public StorageHelper + { + uno::Reference< util::XOfficeInstallationDirectories > m_xOfficeInstDirs; + + ReadFolderContent( SvStream& _rStorage, + const uno::Reference< + util::XOfficeInstallationDirectories > & + xOfficeInstDirs ) + : StorageHelper( _rStorage ), m_xOfficeInstDirs( xOfficeInstDirs ) { } + + + void operator() ( TemplateContent& _rContent ) const + { + // store the info about this content + util::DateTime aModDate; + m_rStorage >> aModDate; + _rContent.setModDate( aModDate ); + + // store the info about the children + // the number + sal_Int32 nChildren = 0; + m_rStorage.ReadInt32( nChildren ); + TemplateFolderContent& rChildren = _rContent.getSubContents(); + rChildren.resize( 0 ); + rChildren.reserve( nChildren ); + // initialize them with their (local) names + while ( nChildren-- ) + { + OUString sURL = m_rStorage.ReadUniOrByteString(m_rStorage.GetStreamCharSet()); + sURL = m_xOfficeInstDirs->makeAbsoluteURL( sURL ); + INetURLObject aChildURL( sURL ); + rChildren.push_back( new TemplateContent( aChildURL ) ); + } + + // their content + ::std::for_each( + _rContent.getSubContents().begin(), + _rContent.getSubContents().end(), + *this + ); + } + + + void operator() ( const ::rtl::Reference< TemplateContent >& _rxContent ) const + { + if ( _rxContent.is() ) + { + operator()( *_rxContent ); + } + } + }; + + } + + //= TemplateFolderCacheImpl + + class TemplateFolderCacheImpl + { + private: + TemplateFolderContent m_aPreviousState; // the current state of the template dirs (as found on the HD) + TemplateFolderContent m_aCurrentState; // the previous state of the template dirs (as found in the cache file) + + std::mutex m_aMutex; + // will be lazy inited; never access directly; use getOfficeInstDirs(). + uno::Reference< util::XOfficeInstallationDirectories > m_xOfficeInstDirs; + + std::unique_ptr<SvStream> m_pCacheStream; + bool m_bNeedsUpdate : 1; + bool m_bKnowState : 1; + bool m_bValidCurrentState : 1; + bool m_bAutoStoreState : 1; + + public: + explicit TemplateFolderCacheImpl( bool _bAutoStoreState ); + ~TemplateFolderCacheImpl( ); + + bool needsUpdate(); + void storeState(); + + private: + bool openCacheStream( bool _bForRead ); + void closeCacheStream( ); + + /// read the state of the dirs from the cache file + bool readPreviousState(); + /// read the current state of the dirs + bool readCurrentState(); + + static OUString implParseSmart( const OUString& _rPath ); + + bool implReadFolder( const ::rtl::Reference< TemplateContent >& _rxRoot ); + + static sal_Int32 getMagicNumber(); + static void normalize( TemplateFolderContent& _rState ); + + // @return <TRUE/> if the states equal + static bool equalStates( const TemplateFolderContent& _rLHS, const TemplateFolderContent& _rRHS ); + + // late initialize m_xOfficeInstDirs + const uno::Reference< util::XOfficeInstallationDirectories >& getOfficeInstDirs(); + }; + + + TemplateFolderCacheImpl::TemplateFolderCacheImpl( bool _bAutoStoreState ) + :m_bNeedsUpdate ( true ) + ,m_bKnowState ( false ) + ,m_bValidCurrentState ( false ) + ,m_bAutoStoreState ( _bAutoStoreState ) + { + } + + + TemplateFolderCacheImpl::~TemplateFolderCacheImpl( ) + { + // store the current state if possible and required + if ( m_bValidCurrentState && m_bAutoStoreState ) + storeState(); + + closeCacheStream( ); + } + + + sal_Int32 TemplateFolderCacheImpl::getMagicNumber() + { + return (sal_Int8('T') << 12) + | (sal_Int8('D') << 8) + | (sal_Int8('S') << 4) + | (sal_Int8('C')); + } + + + void TemplateFolderCacheImpl::normalize( TemplateFolderContent& _rState ) + { + SubContentSort()( _rState ); + } + + + bool TemplateFolderCacheImpl::equalStates( const TemplateFolderContent& _rLHS, const TemplateFolderContent& _rRHS ) + { + if ( _rLHS.size() != _rRHS.size() ) + return false; + + // as both arrays are sorted (by definition - this is a precondition of this method) + // we can simply go from the front to the back and compare the single elements + + ::std::pair< ConstFolderIterator, ConstFolderIterator > aFirstDifferent = ::std::mismatch( + _rLHS.begin(), + _rLHS.end(), + _rRHS.begin(), + TemplateContentEqual() + ); + + return aFirstDifferent.first == _rLHS.end(); + } + + + void TemplateFolderCacheImpl::storeState() + { + if ( !m_bValidCurrentState ) + readCurrentState( ); + + if ( !(m_bValidCurrentState && openCacheStream( false )) ) + return; + + m_pCacheStream->WriteInt32( getMagicNumber() ); + + // store the template root folders + // the size + m_pCacheStream->WriteInt32( m_aCurrentState.size() ); + // the complete URLs + ::std::for_each( + m_aCurrentState.begin(), + m_aCurrentState.end(), + StoreContentURL( *m_pCacheStream, getOfficeInstDirs() ) + ); + + // the contents + ::std::for_each( + m_aCurrentState.begin(), + m_aCurrentState.end(), + StoreFolderContent( *m_pCacheStream, getOfficeInstDirs() ) + ); + } + + + OUString TemplateFolderCacheImpl::implParseSmart( const OUString& _rPath ) + { + INetURLObject aParser; + aParser.SetSmartProtocol( INetProtocol::File ); + aParser.SetURL( _rPath ); + if ( INetProtocol::NotValid == aParser.GetProtocol() ) + { + OUString sURL; + osl::FileBase::getFileURLFromSystemPath( _rPath, sURL ); + aParser.SetURL( sURL ); + } + return aParser.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ); + } + + + void TemplateFolderCacheImpl::closeCacheStream( ) + { + m_pCacheStream.reset(); + } + + + bool TemplateFolderCacheImpl::implReadFolder( const ::rtl::Reference< TemplateContent >& _rxRoot ) + { + try + { + // create a content for the current folder root + Reference< XResultSet > xResultSet; + Sequence< OUString > aContentProperties{ "Title", "DateModified", "DateCreated", + "IsFolder" }; + + // get the set of sub contents in the folder + try + { + Reference< XDynamicResultSet > xDynResultSet; + + ::ucbhelper::Content aTemplateRoot( _rxRoot->getURL(), Reference< XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + xDynResultSet = aTemplateRoot.createDynamicCursor( aContentProperties ); + if ( xDynResultSet.is() ) + xResultSet = xDynResultSet->getStaticResultSet(); + } + catch( CommandAbortedException& ) + { + TOOLS_WARN_EXCEPTION( "svtools.misc", "" ); + return false; + } + catch( css::uno::Exception& ) + { + } + + // collect the infos about the sub contents + if ( xResultSet.is() ) + { + Reference< XRow > xRow( xResultSet, UNO_QUERY_THROW ); + Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY_THROW ); + + while ( xResultSet->next() ) + { + INetURLObject aSubContentURL( xContentAccess->queryContentIdentifierString() ); + + // a new content instance + ::rtl::Reference< TemplateContent > xChild = new TemplateContent( aSubContentURL ); + + // the modified date + xChild->setModDate( xRow->getTimestamp( 2 ) ); // date modified + if ( xRow->wasNull() ) + xChild->setModDate( xRow->getTimestamp( 3 ) ); // fallback: date created + + // push back this content + _rxRoot->push_back( xChild ); + + // is it a folder? + if ( xRow->getBoolean( 4 ) && !xRow->wasNull() ) + { // yes -> step down + ConstFolderIterator aNextLevelRoot = _rxRoot->end(); + --aNextLevelRoot; + implReadFolder( *aNextLevelRoot ); + } + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "svtools", "TemplateFolderCacheImpl::implReadFolder" ); + return false; + } + return true; + } + + + bool TemplateFolderCacheImpl::readCurrentState() + { + // reset + m_bValidCurrentState = false; + TemplateFolderContent aTemplateFolderContent; + m_aCurrentState.swap( aTemplateFolderContent ); + + // the template directories from the config + const SvtPathOptions aPathOptions; + const OUString& aDirs = aPathOptions.GetTemplatePath(); + + // loop through all the root-level template folders + sal_Int32 nIndex = 0; + do + { + OUString sTemplatePath( aDirs.getToken(0, ';', nIndex) ); + sTemplatePath = aPathOptions.ExpandMacros( sTemplatePath ); + + // Make sure excess ".." path segments (from expanding bootstrap + // variables in paths) are normalized in the same way they are + // normalized for paths read from the .templdir.cache file (where + // paths have gone through makeRelocatable URL on writing out and + // then through makeAbsoluteURL when reading back in), as otherwise + // equalStates() in needsUpdate() could erroneously consider + // m_aCurrentState and m_aPreviousState as different: + sTemplatePath = getOfficeInstDirs()->makeAbsoluteURL( + getOfficeInstDirs()->makeRelocatableURL(sTemplatePath)); + + // create a new entry + m_aCurrentState.push_back( new TemplateContent( INetURLObject( sTemplatePath ) ) ); + TemplateFolderContent::iterator aCurrentRoot = m_aCurrentState.end(); + --aCurrentRoot; + + if ( !implReadFolder( *aCurrentRoot ) ) + return false; + } + while ( nIndex >= 0 ); + + // normalize the array (which basically means "sort it") + normalize( m_aCurrentState ); + + m_bValidCurrentState = true; + return m_bValidCurrentState; + } + + + bool TemplateFolderCacheImpl::readPreviousState() + { + DBG_ASSERT( m_pCacheStream, "TemplateFolderCacheImpl::readPreviousState: not to be called without stream!" ); + + // reset + TemplateFolderContent aTemplateFolderContent; + m_aPreviousState.swap( aTemplateFolderContent ); + + // check the magic number + sal_Int32 nMagic = 0; + m_pCacheStream->ReadInt32( nMagic ); + DBG_ASSERT( getMagicNumber() == nMagic, "TemplateFolderCacheImpl::readPreviousState: invalid cache file!" ); + if ( getMagicNumber() != nMagic ) + return false; + + // the root directories + // their number + sal_Int32 nRootDirectories = 0; + m_pCacheStream->ReadInt32( nRootDirectories ); + // init empty TemplateContents with the URLs + m_aPreviousState.reserve( nRootDirectories ); + while ( nRootDirectories-- ) + { + OUString sURL = m_pCacheStream->ReadUniOrByteString(m_pCacheStream->GetStreamCharSet()); + // #116281# Keep office installation relocatable. Never store + // any direct references to office installation directory. + sURL = getOfficeInstDirs()->makeAbsoluteURL( sURL ); + m_aPreviousState.push_back( + new TemplateContent( INetURLObject(sURL) ) ); + } + + // read the contents of the root folders + ::std::for_each( + m_aPreviousState.begin(), + m_aPreviousState.end(), + ReadFolderContent( *m_pCacheStream, getOfficeInstDirs() ) + ); + + DBG_ASSERT( !m_pCacheStream->GetErrorCode(), "TemplateFolderCacheImpl::readPreviousState: unknown error during reading the state cache!" ); + + // normalize the array (which basically means "sort it") + normalize( m_aPreviousState ); + + return true; + } + + + bool TemplateFolderCacheImpl::openCacheStream( bool _bForRead ) + { + // close any old stream instance + closeCacheStream( ); + + // get the storage directory + OUString sStorageURL = implParseSmart( SvtPathOptions().GetStoragePath() ); + INetURLObject aStorageURL( sStorageURL ); + if ( INetProtocol::NotValid == aStorageURL.GetProtocol() ) + { + OSL_FAIL( "TemplateFolderCacheImpl::openCacheStream: invalid storage path!" ); + return false; + } + + // append our name + aStorageURL.Append( u".templdir.cache" ); + + // open the stream + m_pCacheStream = UcbStreamHelper::CreateStream( aStorageURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ), + _bForRead ? StreamMode::READ | StreamMode::NOCREATE : StreamMode::WRITE | StreamMode::TRUNC ); + DBG_ASSERT( m_pCacheStream, "TemplateFolderCacheImpl::openCacheStream: could not open/create the cache stream!" ); + if ( m_pCacheStream && m_pCacheStream->GetErrorCode() ) + { + m_pCacheStream.reset(); + } + + if ( m_pCacheStream ) + m_pCacheStream->SetStreamCharSet( RTL_TEXTENCODING_UTF8 ); + + return nullptr != m_pCacheStream; + } + + + bool TemplateFolderCacheImpl::needsUpdate() + { + if ( m_bKnowState ) + return m_bNeedsUpdate; + + m_bNeedsUpdate = true; + m_bKnowState = true; + + if ( readCurrentState() ) + { + // open the stream which contains the cached state of the directories + if ( openCacheStream( true ) ) + { // opening the stream succeeded + if ( readPreviousState() ) + { + m_bNeedsUpdate = !equalStates( m_aPreviousState, m_aCurrentState ); + } + else + { + closeCacheStream(); + } + } + } + return m_bNeedsUpdate; + } + + + const uno::Reference< util::XOfficeInstallationDirectories >& + TemplateFolderCacheImpl::getOfficeInstDirs() + { + if ( !m_xOfficeInstDirs.is() ) + { + std::lock_guard aGuard( m_aMutex ); + if ( !m_xOfficeInstDirs.is() ) + { + uno::Reference< uno::XComponentContext > xCtx( + comphelper::getProcessComponentContext() ); + m_xOfficeInstDirs = util::theOfficeInstallationDirectories::get(xCtx); + } + } + return m_xOfficeInstDirs; + } + + + //= TemplateFolderCache + + + TemplateFolderCache::TemplateFolderCache( bool _bAutoStoreState ) + :m_pImpl( new TemplateFolderCacheImpl( _bAutoStoreState ) ) + { + } + + + TemplateFolderCache::~TemplateFolderCache( ) + { + } + + + bool TemplateFolderCache::needsUpdate() + { + return m_pImpl->needsUpdate(); + } + + + void TemplateFolderCache::storeState() + { + m_pImpl->storeState(); + } + + +} // namespace sfx2 + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/misc/unitconv.cxx b/svtools/source/misc/unitconv.cxx new file mode 100644 index 000000000..395c1b4ac --- /dev/null +++ b/svtools/source/misc/unitconv.cxx @@ -0,0 +1,203 @@ +/* -*- 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 <o3tl/temporary.hxx> +#include <svtools/unitconv.hxx> +#include <tools/debug.hxx> +#include <tools/UnitConversion.hxx> +#include <vcl/outdev.hxx> +#include <vcl/weld.hxx> + +void SetFieldUnit(weld::MetricSpinButton& rField, FieldUnit eUnit, bool bAll) +{ + sal_Int64 nMin, nMax; + rField.get_range(nMin, nMax, FieldUnit::TWIP); + sal_Int64 nValue = rField.get_value(FieldUnit::TWIP); + nMin = rField.denormalize(nMin); + nMax = rField.denormalize(nMax); + nValue = rField.denormalize(nValue); + + if (!bAll) + { + switch (eUnit) + { + case FieldUnit::M: + case FieldUnit::KM: + eUnit = FieldUnit::CM; + break; + case FieldUnit::FOOT: + case FieldUnit::MILE: + eUnit = FieldUnit::INCH; + break; + default: //prevent warning + break; + } + } + + rField.set_unit(eUnit); + + if (FieldUnit::POINT == eUnit) + { + if (rField.get_digits() > 1) + rField.set_digits(1); + } + else + rField.set_digits(2); + + switch (eUnit) + { + // _CHAR and _LINE sets the step of "char" and "line" unit, they are same as FieldUnit::MM + case FieldUnit::CHAR: + case FieldUnit::LINE: + case FieldUnit::MM: + rField.set_increments(50, 500, eUnit); + break; + case FieldUnit::INCH: + rField.set_increments(2, 20, eUnit); + break; + default: + rField.set_increments(10, 100, eUnit); + break; + } + + if (!bAll) + { + nMin = rField.normalize(nMin); + nMax = rField.normalize(nMax); + rField.set_range(nMin, nMax, FieldUnit::TWIP); + } + + rField.set_value(rField.normalize(nValue), FieldUnit::TWIP); +} + +void SetMetricValue(weld::MetricSpinButton& rField, sal_Int64 nCoreValue, MapUnit eUnit) +{ + sal_Int64 nVal = OutputDevice::LogicToLogic(nCoreValue, eUnit, MapUnit::Map100thMM); + nVal = rField.normalize(nVal); + rField.set_value(nVal, FieldUnit::MM_100TH); +} + +sal_Int64 GetCoreValue(const weld::MetricSpinButton& rField, MapUnit eUnit) +{ + sal_Int64 nVal = rField.get_value(FieldUnit::MM_100TH); + // avoid rounding issues + const sal_Int64 nSizeMask = 0xffffffffff000000LL; + bool bRoundBefore = true; + if( nVal >= 0 ) + { + if( (nVal & nSizeMask) == 0 ) + bRoundBefore = false; + } + else + { + if( ((-nVal) & nSizeMask ) == 0 ) + bRoundBefore = false; + } + if( bRoundBefore ) + nVal = rField.denormalize( nVal ); + sal_Int64 nUnitVal = OutputDevice::LogicToLogic(nVal, MapUnit::Map100thMM, eUnit); + if (!bRoundBefore) + nUnitVal = rField.denormalize(nUnitVal); + return nUnitVal; +} + +tools::Long CalcToUnit( float nIn, MapUnit eUnit ) +{ + // nIn is in Points + + DBG_ASSERT( eUnit == MapUnit::MapTwip || + eUnit == MapUnit::Map100thMM || + eUnit == MapUnit::Map10thMM || + eUnit == MapUnit::MapMM || + eUnit == MapUnit::MapCM, "this unit is not implemented" ); + + if (const auto eTo = MapToO3tlLength(eUnit); eTo != o3tl::Length::invalid) + return o3tl::convert(nIn, o3tl::Length::pt, eTo); + + return 0; +} + + +tools::Long ItemToControl( tools::Long nIn, MapUnit eItem, FieldUnit eCtrl ) +{ + const auto eFrom = MapToO3tlLength(eItem), eTo = FieldToO3tlLength(eCtrl); + if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid) + return o3tl::convert(nIn, eFrom, eTo); + return 0; +} + + +tools::Long ControlToItem( tools::Long nIn, FieldUnit eCtrl, MapUnit eItem ) +{ + return ItemToControl( nIn, eItem, eCtrl ); +} + + +FieldUnit MapToFieldUnit( const MapUnit eUnit ) +{ + switch ( eUnit ) + { + case MapUnit::Map100thMM: + case MapUnit::Map10thMM: + case MapUnit::MapMM: + return FieldUnit::MM; + + case MapUnit::MapCM: + return FieldUnit::CM; + + case MapUnit::Map1000thInch: + case MapUnit::Map100thInch: + case MapUnit::Map10thInch: + case MapUnit::MapInch: + return FieldUnit::INCH; + + case MapUnit::MapPoint: + return FieldUnit::POINT; + + case MapUnit::MapTwip: + return FieldUnit::TWIP; + default: ;//prevent warning + } + return FieldUnit::NONE; +} + + +tools::Long CalcToPoint( tools::Long nIn, MapUnit eUnit, sal_uInt16 nFactor ) +{ + DBG_ASSERT( eUnit == MapUnit::MapTwip || + eUnit == MapUnit::Map100thMM || + eUnit == MapUnit::Map10thMM || + eUnit == MapUnit::MapMM || + eUnit == MapUnit::MapCM, "this unit is not implemented" ); + + if (const auto eFrom = MapToO3tlLength(eUnit); eFrom != o3tl::Length::invalid) + return o3tl::convert(nIn * static_cast<sal_Int64>(nFactor), eFrom, o3tl::Length::pt); + return 0; +} + + +tools::Long TransformMetric( tools::Long nVal, FieldUnit aOld, FieldUnit aNew ) +{ + const auto eFrom = FieldToO3tlLength(aOld), eTo = FieldToO3tlLength(aNew); + if (eFrom == o3tl::Length::invalid || eTo == o3tl::Length::invalid) + return nVal; + return o3tl::convert(nVal, eFrom, eTo, o3tl::temporary(bool())); // Just 0 when overflown +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/svhtml/HtmlWriter.cxx b/svtools/source/svhtml/HtmlWriter.cxx new file mode 100644 index 000000000..b813c7ee5 --- /dev/null +++ b/svtools/source/svhtml/HtmlWriter.cxx @@ -0,0 +1,176 @@ +/* -*- 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 <svtools/HtmlWriter.hxx> +#include <tools/stream.hxx> +#include <sal/log.hxx> +#include <svtools/htmlout.hxx> + +HtmlWriter::HtmlWriter(SvStream& rStream, std::string_view rNamespace) : + mrStream(rStream), + mbElementOpen(false), + mbCharactersWritten(false), + mbPrettyPrint(true) +{ + if (!rNamespace.empty()) + { + // Convert namespace alias to a prefix. + maNamespace = OString::Concat(rNamespace) + ":"; + } +} + +HtmlWriter::~HtmlWriter() +{} + +void HtmlWriter::prettyPrint(bool b) +{ + mbPrettyPrint = b; +} + +void HtmlWriter::start(const OString& aElement) +{ + if (mbElementOpen) + { + mrStream.WriteChar('>'); + if (mbPrettyPrint) + mrStream.WriteChar('\n'); + } + maElementStack.push_back(aElement); + + if (mbPrettyPrint) + { + for(size_t i = 0; i < maElementStack.size() - 1; i++) + { + mrStream.WriteCharPtr(" "); + } + } + + mrStream.WriteChar('<'); + mrStream.WriteOString(OStringConcatenation(maNamespace + aElement)); + mbElementOpen = true; +} + +void HtmlWriter::single(const OString &aContent) +{ + start(aContent); + end(); +} + +void HtmlWriter::endAttribute() +{ + if (mbElementOpen) + { + mrStream.WriteCharPtr("/>"); + if (mbPrettyPrint) + mrStream.WriteCharPtr("\n"); + mbElementOpen = false; + } +} + +void HtmlWriter::flushStack() +{ + while (!maElementStack.empty()) + { + end(); + } +} + +bool HtmlWriter::end(const OString& aElement) +{ + bool bExpected = maElementStack.back() == aElement; + SAL_WARN_IF(!bExpected, "svtools", "HtmlWriter: end element mismatch - '" << aElement << "' expected '" << maElementStack.back() << "'"); + end(); + return bExpected; +} + +void HtmlWriter::end() +{ + if (mbElementOpen && !mbCharactersWritten) + { + mrStream.WriteCharPtr("/>"); + if (mbPrettyPrint) + mrStream.WriteCharPtr("\n"); + } + else + { + if (mbPrettyPrint) + { + for(size_t i = 0; i < maElementStack.size() - 1; i++) + { + mrStream.WriteCharPtr(" "); + } + } + mrStream.WriteCharPtr("</"); + mrStream.WriteOString(OStringConcatenation(maNamespace + maElementStack.back())); + mrStream.WriteCharPtr(">"); + if (mbPrettyPrint) + mrStream.WriteCharPtr("\n"); + } + maElementStack.pop_back(); + mbElementOpen = false; + mbCharactersWritten = false; +} + +void HtmlWriter::writeAttribute(SvStream& rStream, std::string_view aAttribute, sal_Int32 aValue) +{ + writeAttribute(rStream, aAttribute, OString::number(aValue)); +} + +void HtmlWriter::writeAttribute(SvStream& rStream, std::string_view aAttribute, std::string_view aValue) +{ + rStream.WriteOString(aAttribute); + rStream.WriteChar('='); + rStream.WriteChar('"'); + HTMLOutFuncs::Out_String(rStream, OStringToOUString(aValue, RTL_TEXTENCODING_UTF8)); + rStream.WriteChar('"'); +} + +void HtmlWriter::attribute(std::string_view aAttribute, std::string_view aValue) +{ + if (mbElementOpen && !aAttribute.empty() && !aValue.empty()) + { + mrStream.WriteChar(' '); + writeAttribute(mrStream, aAttribute, aValue); + } +} + +void HtmlWriter::attribute(std::string_view aAttribute, const sal_Int32 aValue) +{ + attribute(aAttribute, OString::number(aValue)); +} + +void HtmlWriter::attribute(std::string_view aAttribute, const char* pValue) +{ + attribute(aAttribute, std::string_view(pValue)); +} + +void HtmlWriter::attribute(std::string_view aAttribute, std::u16string_view aValue) +{ + attribute(aAttribute, OUStringToOString(aValue, RTL_TEXTENCODING_UTF8)); +} + +void HtmlWriter::attribute(std::string_view aAttribute) +{ + if (mbElementOpen && !aAttribute.empty()) + { + mrStream.WriteChar(' '); + mrStream.WriteOString(aAttribute); + } +} + +void HtmlWriter::characters(std::string_view rChars) +{ + if (!mbCharactersWritten) + mrStream.WriteCharPtr(">"); + mrStream.WriteOString(rChars); + mbCharactersWritten = true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/svhtml/htmlkywd.cxx b/svtools/source/svhtml/htmlkywd.cxx new file mode 100644 index 000000000..584322fac --- /dev/null +++ b/svtools/source/svhtml/htmlkywd.cxx @@ -0,0 +1,816 @@ +/* -*- 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 <algorithm> +#include <string_view> + +#include <o3tl/string_view.hxx> +#include <sal/types.h> +#include <rtl/ustring.hxx> +#include <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> + +// If this is odd, then getOnToken() breaks. +static_assert(static_cast<sal_Int16>(HtmlTokenId::ABBREVIATION_ON) % 2 == 0); + +namespace { + +template<typename T> +struct TokenEntry +{ + std::u16string_view sToken; + T nToken; +}; + +} + +template<typename T> +static bool sortCompare(const TokenEntry<T> & lhs, const TokenEntry<T> & rhs) +{ + return lhs.sToken < rhs.sToken; +} +template<typename T> +static bool findCompare(const TokenEntry<T> & lhs, std::u16string_view rhs) +{ + return lhs.sToken < rhs; +} +template<typename T, size_t LEN> +static T search(TokenEntry<T> const (&dataTable)[LEN], std::u16string_view key, T notFoundValue) +{ + auto findIt = std::lower_bound( std::begin(dataTable), std::end(dataTable), + key, findCompare<T> ); + if (findIt != std::end(dataTable) && key == findIt->sToken) + return findIt->nToken; + return notFoundValue; +} + +using HTML_TokenEntry = TokenEntry<HtmlTokenId>; + +// this array is sorted by the name (even if it doesn't look like it from the constant names) +HTML_TokenEntry const aHTMLTokenTab[] = { + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_comment), HtmlTokenId::COMMENT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_doctype), HtmlTokenId::DOCTYPE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_cdata), HtmlTokenId::CDATA}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_anchor), HtmlTokenId::ANCHOR_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_abbreviation), HtmlTokenId::ABBREVIATION_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_acronym), HtmlTokenId::ACRONYM_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_address), HtmlTokenId::ADDRESS_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_applet), HtmlTokenId::APPLET_ON}, // HotJava + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_area), HtmlTokenId::AREA}, // Netscape 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_author), HtmlTokenId::AUTHOR_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_bold), HtmlTokenId::BOLD_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_banner), HtmlTokenId::BANNER_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_base), HtmlTokenId::BASE}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_basefont), HtmlTokenId::BASEFONT_ON}, // Netscape + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_bigprint), HtmlTokenId::BIGPRINT_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_blink), HtmlTokenId::BLINK_ON}, // Netscape + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_blockquote), HtmlTokenId::BLOCKQUOTE_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_body), HtmlTokenId::BODY_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_blockquote30), HtmlTokenId::BLOCKQUOTE30_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_linebreak), HtmlTokenId::LINEBREAK}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_caption), HtmlTokenId::CAPTION_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_center), HtmlTokenId::CENTER_ON}, // Netscape + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_citation), HtmlTokenId::CITATION_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_code), HtmlTokenId::CODE_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_col), HtmlTokenId::COL_ON}, // HTML 3 Table Model Draft + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_colgroup), HtmlTokenId::COLGROUP_ON}, // HTML 3 Table Model Draft + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_comment2), HtmlTokenId::COMMENT2_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_credit), HtmlTokenId::CREDIT_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_dd), HtmlTokenId::DD_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_deletedtext), HtmlTokenId::DELETEDTEXT_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_definstance), HtmlTokenId::DEFINSTANCE_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_dirlist), HtmlTokenId::DIRLIST_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_division), HtmlTokenId::DIVISION_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_deflist), HtmlTokenId::DEFLIST_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_dt), HtmlTokenId::DT_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_emphasis), HtmlTokenId::EMPHASIS_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_embed), HtmlTokenId::EMBED}, // Netscape 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_figure), HtmlTokenId::FIGURE_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_footnote), HtmlTokenId::FOOTNOTE_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_font), HtmlTokenId::FONT_ON}, // Netscape + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_form), HtmlTokenId::FORM_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_frame), HtmlTokenId::FRAME_ON}, // Netscape 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_frameset), HtmlTokenId::FRAMESET_ON}, // Netscape 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_head1), HtmlTokenId::HEAD1_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_head2), HtmlTokenId::HEAD2_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_head3), HtmlTokenId::HEAD3_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_head4), HtmlTokenId::HEAD4_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_head5), HtmlTokenId::HEAD5_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_head6), HtmlTokenId::HEAD6_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_head), HtmlTokenId::HEAD_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_horzrule), HtmlTokenId::HORZRULE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_html), HtmlTokenId::HTML_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_italic), HtmlTokenId::ITALIC_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_iframe), HtmlTokenId::IFRAME_ON}, // IE 3.0b2 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_image), HtmlTokenId::IMAGE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_input), HtmlTokenId::INPUT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_insertedtext), HtmlTokenId::INSERTEDTEXT_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_keyboard), HtmlTokenId::KEYBOARD_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_language), HtmlTokenId::LANGUAGE_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_listheader), HtmlTokenId::LISTHEADER_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_li), HtmlTokenId::LI_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_link), HtmlTokenId::LINK}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_listing), HtmlTokenId::LISTING_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_map), HtmlTokenId::MAP_ON}, // Netscape 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_marquee), HtmlTokenId::MARQUEE_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_menulist), HtmlTokenId::MENULIST_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_meta), HtmlTokenId::META}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_multicol), HtmlTokenId::MULTICOL_ON}, // Netscape 3.0b5 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_nobr), HtmlTokenId::NOBR_ON}, // Netscape + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_noembed), HtmlTokenId::NOEMBED_ON}, // Netscape 2.0 ??? + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_noframe), HtmlTokenId::NOFRAMES_ON}, // Netscape 2.0 ??? + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_noframes), HtmlTokenId::NOFRAMES_ON}, // Netscape 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_noscript), HtmlTokenId::NOSCRIPT_ON}, // Netscape 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_note), HtmlTokenId::NOTE_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_object), HtmlTokenId::OBJECT_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_orderlist), HtmlTokenId::ORDERLIST_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_option), HtmlTokenId::OPTION}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_parabreak), HtmlTokenId::PARABREAK_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_param), HtmlTokenId::PARAM}, // HotJava + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_person), HtmlTokenId::PERSON_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_plaintext2), HtmlTokenId::PLAINTEXT2_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_preformtxt), HtmlTokenId::PREFORMTXT_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_shortquote), HtmlTokenId::SHORTQUOTE_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_strikethrough), HtmlTokenId::STRIKETHROUGH_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_sample), HtmlTokenId::SAMPLE_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_script), HtmlTokenId::SCRIPT_ON}, // HTML 3.2 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_sdfield), HtmlTokenId::SDFIELD_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_select), HtmlTokenId::SELECT_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_smallprint), HtmlTokenId::SMALLPRINT_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_spacer), HtmlTokenId::SPACER}, // Netscape 3.0b5 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_span), HtmlTokenId::SPAN_ON}, // Style Sheets + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_strike), HtmlTokenId::STRIKE_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_strong), HtmlTokenId::STRONG_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_style), HtmlTokenId::STYLE_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_subscript), HtmlTokenId::SUBSCRIPT_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_superscript), HtmlTokenId::SUPERSCRIPT_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_plaintext), HtmlTokenId::PLAINTEXT_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_table), HtmlTokenId::TABLE_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_tbody), HtmlTokenId::TBODY_ON}, // HTML 3 Table Model Draft + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_tabledata), HtmlTokenId::TABLEDATA_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_textarea), HtmlTokenId::TEXTAREA_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_tfoot), HtmlTokenId::TFOOT_ON}, // HTML 3 Table Model Draft + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_tableheader), HtmlTokenId::TABLEHEADER_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_thead), HtmlTokenId::THEAD_ON}, // HTML 3 Table Model Draft + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_title), HtmlTokenId::TITLE_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_tablerow), HtmlTokenId::TABLEROW_ON}, // HTML 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_teletype), HtmlTokenId::TELETYPE_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_underline), HtmlTokenId::UNDERLINE_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_unorderlist), HtmlTokenId::UNORDERLIST_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_variable), HtmlTokenId::VARIABLE_ON}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_xmp), HtmlTokenId::XMP_ON}, +}; + + +HtmlTokenId GetHTMLToken( std::u16string_view rName ) +{ + static bool bSortKeyWords = false; + if( !bSortKeyWords ) + { + assert( std::is_sorted( std::begin(aHTMLTokenTab), std::end(aHTMLTokenTab), sortCompare<HtmlTokenId> ) ); + bSortKeyWords = true; + } + + if( o3tl::starts_with( rName, u"" OOO_STRING_SVTOOLS_HTML_comment )) + return HtmlTokenId::COMMENT; + + return search( aHTMLTokenTab, rName, HtmlTokenId::NONE); +} + +using HTML_CharEntry = TokenEntry<sal_Unicode>; + +// Flag: RTF token table has already been sorted +static bool bSortCharKeyWords = false; + +static HTML_CharEntry aHTMLCharNameTab[] = { + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_lt), 60}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_gt), 62}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_amp), 38}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_quot), 34}, + + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Agrave), 192}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Aacute), 193}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Acirc), 194}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Atilde), 195}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Auml), 196}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Aring), 197}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_AElig), 198}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Ccedil), 199}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Egrave), 200}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Eacute), 201}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Ecirc), 202}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Euml), 203}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Igrave), 204}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Iacute), 205}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Icirc), 206}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Iuml), 207}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_ETH), 208}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Ntilde), 209}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Ograve), 210}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Oacute), 211}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Ocirc), 212}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Otilde), 213}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Ouml), 214}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Oslash), 216}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Ugrave), 217}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Uacute), 218}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Ucirc), 219}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Uuml), 220}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_Yacute), 221}, + + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_THORN), 222}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_C_szlig), 223}, + + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_agrave), 224}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_aacute), 225}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_acirc), 226}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_atilde), 227}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_auml), 228}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_aring), 229}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_aelig), 230}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ccedil), 231}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_egrave), 232}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_eacute), 233}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ecirc), 234}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_euml), 235}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_igrave), 236}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_iacute), 237}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_icirc), 238}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_iuml), 239}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_eth), 240}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ntilde), 241}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ograve), 242}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_oacute), 243}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ocirc), 244}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_otilde), 245}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ouml), 246}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_oslash), 248}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ugrave), 249}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_uacute), 250}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ucirc), 251}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_uuml), 252}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_yacute), 253}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_thorn), 254}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_yuml), 255}, + +// special characters + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_acute), 180}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_brvbar), 166}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_cedil), 184}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_cent), 162}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_copy), 169}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_curren), 164}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_deg), 176}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_divide), 247}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_frac12), 189}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_frac14), 188}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_frac34), 190}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_iexcl), 161}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_iquest), 191}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_laquo), 171}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_macr), 175}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_micro), 181}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_middot), 183}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_not), 172}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ordf), 170}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ordm), 186}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_para), 182}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_plusmn), 177}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_pound), 163}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_raquo), 187}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_reg), 174}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_sect), 167}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_sup1), 185}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_sup2), 178}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_sup3), 179}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_times), 215}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_uml), 168}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_yen), 165}, + +// special characters), which will be converted to tokens !!! + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_nbsp), 1}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_shy), 2}, + + +// HTML4 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_OElig), 338}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_oelig), 339}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Scaron), 352}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_scaron), 353}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Yuml), 376}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_fnof), 402}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_circ), 710}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_tilde), 732}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Alpha), 913}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Beta), 914}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Gamma), 915}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Delta), 916}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Epsilon), 917}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Zeta), 918}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Eta), 919}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Theta), 920}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Iota), 921}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Kappa), 922}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Lambda), 923}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Mu), 924}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Nu), 925}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Xi), 926}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Omicron), 927}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Pi), 928}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Rho), 929}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Sigma), 931}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Tau), 932}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Upsilon), 933}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Phi), 934}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Chi), 935}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Psi), 936}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Omega), 937}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_alpha), 945}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_beta), 946}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_gamma), 947}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_delta), 948}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_epsilon), 949}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_zeta), 950}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_eta), 951}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_theta), 952}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_iota), 953}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_kappa), 954}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_lambda), 955}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_mu), 956}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_nu), 957}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_xi), 958}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_omicron), 959}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_pi), 960}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_rho), 961}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_sigmaf), 962}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_sigma), 963}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_tau), 964}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_upsilon), 965}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_phi), 966}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_chi), 967}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_psi), 968}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_omega), 969}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_thetasym), 977}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_upsih), 978}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_piv), 982}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ensp), 8194}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_emsp), 8195}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_thinsp), 8201}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_zwnj), 8204}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_zwj), 8205}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_lrm), 8206}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_rlm), 8207}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ndash), 8211}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_mdash), 8212}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_lsquo), 8216}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_rsquo), 8217}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_sbquo), 8218}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ldquo), 8220}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_rdquo), 8221}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_bdquo), 8222}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_dagger), 8224}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Dagger), 8225}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_bull), 8226}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_hellip), 8230}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_permil), 8240}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_prime), 8242}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_Prime), 8243}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_lsaquo), 8249}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_rsaquo), 8250}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_oline), 8254}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_frasl), 8260}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_euro), 8364}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_image), 8465}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_weierp), 8472}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_real), 8476}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_trade), 8482}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_alefsym), 8501}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_larr), 8592}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_uarr), 8593}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_rarr), 8594}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_darr), 8595}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_harr), 8596}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_crarr), 8629}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_lArr), 8656}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_uArr), 8657}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_rArr), 8658}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_dArr), 8659}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_hArr), 8660}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_forall), 8704}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_part), 8706}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_exist), 8707}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_empty), 8709}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_nabla), 8711}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_isin), 8712}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_notin), 8713}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ni), 8715}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_prod), 8719}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_sum), 8721}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_minus), 8722}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_lowast), 8727}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_radic), 8730}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_prop), 8733}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_infin), 8734}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ang), 8736}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_and), 8743}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_or), 8744}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_cap), 8745}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_cup), 8746}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_int), 8747}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_there4), 8756}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_sim), 8764}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_cong), 8773}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_asymp), 8776}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ne), 8800}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_equiv), 8801}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_le), 8804}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_ge), 8805}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_sub), 8834}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_sup), 8835}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_nsub), 8836}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_sube), 8838}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_supe), 8839}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_oplus), 8853}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_otimes), 8855}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_perp), 8869}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_sdot), 8901}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_lceil), 8968}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_rceil), 8969}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_lfloor), 8970}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_rfloor), 8971}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_lang), 9001}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_rang), 9002}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_loz), 9674}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_spades), 9824}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_clubs), 9827}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_hearts), 9829}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_S_diams), 9830} +}; + +sal_Unicode GetHTMLCharName( std::u16string_view rName ) +{ + if( !bSortCharKeyWords ) + { + std::sort( std::begin(aHTMLCharNameTab), std::end(aHTMLCharNameTab), + sortCompare<sal_Unicode> ); + bSortCharKeyWords = true; + } + + return search<sal_Unicode>( aHTMLCharNameTab, rName, 0); +} + +// Flag: Options table has already been sorted +static bool bSortOptionKeyWords = false; + +using HTML_OptionEntry = TokenEntry<HtmlOptionId>; + +static HTML_OptionEntry aHTMLOptionTab[] = { + +// Attributes without value + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_checked), HtmlOptionId::CHECKED}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_compact), HtmlOptionId::COMPACT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_declare), HtmlOptionId::DECLARE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_disabled), HtmlOptionId::DISABLED}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_ismap), HtmlOptionId::ISMAP}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_mayscript), HtmlOptionId::MAYSCRIPT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_multiple), HtmlOptionId::MULTIPLE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_nohref), HtmlOptionId::NOHREF}, // Netscape 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_noresize), HtmlOptionId::NORESIZE}, // Netscape 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_noshade), HtmlOptionId::NOSHADE}, // Netscape 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_nowrap), HtmlOptionId::NOWRAP}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_sdfixed), HtmlOptionId::SDFIXED}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_selected), HtmlOptionId::SELECTED}, + +// Attributes with a string value + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_accept), HtmlOptionId::ACCEPT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_accesskey), HtmlOptionId::ACCESSKEY}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_alt), HtmlOptionId::ALT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_axis), HtmlOptionId::AXIS}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_char), HtmlOptionId::CHAR}, // HTML 3 Table Model Draft + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_charset), HtmlOptionId::CHARSET}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_class), HtmlOptionId::CLASS}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_code), HtmlOptionId::CODE}, // HotJava + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_codetype), HtmlOptionId::CODETYPE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_content), HtmlOptionId::CONTENT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_coords), HtmlOptionId::COORDS}, // Netscape 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_enctype), HtmlOptionId::ENCTYPE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_face), HtmlOptionId::FACE}, // IExplorer 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_frameborder), HtmlOptionId::FRAMEBORDER}, // IExplorer 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_httpequiv), HtmlOptionId::HTTPEQUIV}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_language), HtmlOptionId::LANGUAGE}, // JavaScript + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_name), HtmlOptionId::NAME}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_prompt), HtmlOptionId::PROMPT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_shape), HtmlOptionId::SHAPE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_standby), HtmlOptionId::STANDBY}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_style), HtmlOptionId::STYLE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_title), HtmlOptionId::TITLE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_value), HtmlOptionId::VALUE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDval), HtmlOptionId::SDVAL}, // StarDiv NumberValue + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDnum), HtmlOptionId::SDNUM}, // StarDiv NumberFormat + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_sdlibrary), HtmlOptionId::SDLIBRARY}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_sdmodule), HtmlOptionId::SDMODULE}, + +// Attributes with a SGML identifier value + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_id), HtmlOptionId::ID}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_target), HtmlOptionId::TARGET}, // Netscape 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_to), HtmlOptionId::TO}, + +// Attributes with a URI value + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_action), HtmlOptionId::ACTION}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_archive), HtmlOptionId::ARCHIVE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_background), HtmlOptionId::BACKGROUND}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_classid), HtmlOptionId::CLASSID}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_codebase), HtmlOptionId::CODEBASE}, // HotJava + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_data), HtmlOptionId::DATA}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_href), HtmlOptionId::HREF}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_script), HtmlOptionId::SCRIPT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_src), HtmlOptionId::SRC}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_usemap), HtmlOptionId::USEMAP}, // Netscape 2.0 + +// Attributes with a color value (all Netscape versions) + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_alink), HtmlOptionId::ALINK}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_bgcolor), HtmlOptionId::BGCOLOR}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_bordercolor), HtmlOptionId::BORDERCOLOR}, // IExplorer 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_bordercolorlight), HtmlOptionId::BORDERCOLORLIGHT}, // IExplorer 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_bordercolordark), HtmlOptionId::BORDERCOLORDARK}, // IExplorer 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_color), HtmlOptionId::COLOR}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_link), HtmlOptionId::LINK}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_text), HtmlOptionId::TEXT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_vlink), HtmlOptionId::VLINK}, + +// Attributes with a numerical value + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_border), HtmlOptionId::BORDER}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_cellspacing),HtmlOptionId::CELLSPACING}, // HTML 3 Table Model Draft + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_cellpadding),HtmlOptionId::CELLPADDING}, // HTML 3 Table Model Draft + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_charoff), HtmlOptionId::CHAROFF}, // HTML 3 Table Model Draft + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_colspan), HtmlOptionId::COLSPAN}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_framespacing), HtmlOptionId::FRAMESPACING}, // IExplorer 3.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_gutter), HtmlOptionId::GUTTER}, // Netscape 3.0b5 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_height), HtmlOptionId::HEIGHT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_hspace), HtmlOptionId::HSPACE}, // Netscape + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_left), HtmlOptionId::LEFT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_loop), HtmlOptionId::LOOP}, // IExplorer 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_marginheight),HtmlOptionId::MARGINHEIGHT}, // Netscape 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_marginwidth),HtmlOptionId::MARGINWIDTH}, // Netscape 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_maxlength), HtmlOptionId::MAXLENGTH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_rowspan), HtmlOptionId::ROWSPAN}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_scrollamount), HtmlOptionId::SCROLLAMOUNT}, // IExplorer 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_scrolldelay), HtmlOptionId::SCROLLDELAY}, // IExplorer 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_span), HtmlOptionId::SPAN}, // HTML 3 Table Model Draft + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_tabindex), HtmlOptionId::TABINDEX}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_vspace), HtmlOptionId::VSPACE}, // Netscape + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_width), HtmlOptionId::WIDTH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_zindex), HtmlOptionId::ZINDEX}, + +// Attributes with enum values + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_behavior), HtmlOptionId::BEHAVIOR}, // IExplorer 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_clear), HtmlOptionId::CLEAR}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_dir), HtmlOptionId::DIR}, // HTML 3 Table Model Draft + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_direction), HtmlOptionId::DIRECTION}, // IExplorer 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_format), HtmlOptionId::FORMAT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_frame), HtmlOptionId::FRAME}, // HTML 3 Table Model Draft + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_lang), HtmlOptionId::LANG}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_method), HtmlOptionId::METHOD}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_rel), HtmlOptionId::REL}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_rev), HtmlOptionId::REV}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_rules), HtmlOptionId::RULES}, // HTML 3 Table Model Draft + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_scrolling), HtmlOptionId::SCROLLING}, // Netscape 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_sdreadonly), HtmlOptionId::SDREADONLY}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_subtype), HtmlOptionId::SUBTYPE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_type), HtmlOptionId::TYPE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_valign), HtmlOptionId::VALIGN}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_valuetype), HtmlOptionId::VALUETYPE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_wrap), HtmlOptionId::WRAP}, + +// Attributes with script code value + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_onblur), HtmlOptionId::ONBLUR}, // JavaScript + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_onchange), HtmlOptionId::ONCHANGE}, // JavaScript + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_onclick), HtmlOptionId::ONCLICK}, // JavaScript + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_onfocus), HtmlOptionId::ONFOCUS}, // JavaScript + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_onload), HtmlOptionId::ONLOAD}, // JavaScript + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_onmouseover), HtmlOptionId::ONMOUSEOVER}, // JavaScript + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_onreset), HtmlOptionId::ONRESET}, // JavaScript + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_onselect), HtmlOptionId::ONSELECT}, // JavaScript + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_onsubmit), HtmlOptionId::ONSUBMIT}, // JavaScript + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_onunload), HtmlOptionId::ONUNLOAD}, // JavaScript + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_onabort), HtmlOptionId::ONABORT}, // JavaScript + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_onerror), HtmlOptionId::ONERROR}, // JavaScript + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_onmouseout), HtmlOptionId::ONMOUSEOUT}, // JavaScript + + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDonblur), HtmlOptionId::SDONBLUR}, // StarBasic + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDonchange), HtmlOptionId::SDONCHANGE}, // StarBasic + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDonclick), HtmlOptionId::SDONCLICK}, // StarBasic + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDonfocus), HtmlOptionId::SDONFOCUS}, // StarBasic + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDonload), HtmlOptionId::SDONLOAD}, // StarBasic + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDonmouseover), HtmlOptionId::SDONMOUSEOVER}, // StarBasic + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDonreset), HtmlOptionId::SDONRESET}, // StarBasic + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDonselect), HtmlOptionId::SDONSELECT}, // StarBasic + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDonsubmit), HtmlOptionId::SDONSUBMIT}, // StarBasic + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDonunload), HtmlOptionId::SDONUNLOAD}, // StarBasic + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDonabort), HtmlOptionId::SDONABORT}, // StarBasic + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDonerror), HtmlOptionId::SDONERROR}, // StarBasic + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_SDonmouseout), HtmlOptionId::SDONMOUSEOUT}, // StarBasic + +// Attributes with context sensitive values + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_align), HtmlOptionId::ALIGN}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_cols), HtmlOptionId::COLS}, // Netscape 2.0 vs HTML 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_rows), HtmlOptionId::ROWS}, // Netscape 2.0 vs HTML 2.0 + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_size), HtmlOptionId::SIZE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_HTML_O_start), HtmlOptionId::START}, // Netscape 2.0 vs IExplorer 2.0 +}; + +HtmlOptionId GetHTMLOption( std::u16string_view rName ) +{ + if( !bSortOptionKeyWords ) + { + std::sort( std::begin(aHTMLOptionTab), std::end(aHTMLOptionTab), sortCompare<HtmlOptionId> ); + bSortOptionKeyWords = true; + } + + return search( aHTMLOptionTab, rName, HtmlOptionId::UNKNOWN); +} + + +using HTML_ColorEntry = TokenEntry<sal_uInt32>; + +// Flag: color table has already been sorted +static bool bSortColorKeyWords = false; + +// Color names are not exported (source: +// "http://www.uio.no/~mnbjerke/colors_w.html") +// "http://www.infi.net/wwwimages/colorindex.html" seem to be buggy. +HTML_ColorEntry const aHTMLColorNameTab[] = { + { std::u16string_view(u"aliceblue"), 0x00f0f8ffUL }, + { std::u16string_view(u"antiquewhite"), 0x00faebd7UL }, + { std::u16string_view(u"aqua"), 0x0000ffffUL }, + { std::u16string_view(u"aquamarine"), 0x007fffd4UL }, + { std::u16string_view(u"azure"), 0x00f0ffffUL }, + { std::u16string_view(u"beige"), 0x00f5f5dcUL }, + { std::u16string_view(u"bisque"), 0x00ffe4c4UL }, + { std::u16string_view(u"black"), 0x00000000UL }, + { std::u16string_view(u"blanchedalmond"), 0x00ffebcdUL }, + { std::u16string_view(u"blue"), 0x000000ffUL }, + { std::u16string_view(u"blueviolet"), 0x008a2be2UL }, + { std::u16string_view(u"brown"), 0x00a52a2aUL }, + { std::u16string_view(u"burlywood"), 0x00deb887UL }, + { std::u16string_view(u"cadetblue"), 0x005f9ea0UL }, + { std::u16string_view(u"chartreuse"), 0x007fff00UL }, + { std::u16string_view(u"chocolate"), 0x00d2691eUL }, + { std::u16string_view(u"coral"), 0x00ff7f50UL }, + { std::u16string_view(u"cornflowerblue"), 0x006495edUL }, + { std::u16string_view(u"cornsilk"), 0x00fff8dcUL }, + { std::u16string_view(u"crimson"), 0x00dc143cUL }, + { std::u16string_view(u"cyan"), 0x0000ffffUL }, + { std::u16string_view(u"darkblue"), 0x0000008bUL }, + { std::u16string_view(u"darkcyan"), 0x00008b8bUL }, + { std::u16string_view(u"darkgoldenrod"), 0x00b8860bUL }, + { std::u16string_view(u"darkgray"), 0x00a9a9a9UL }, + { std::u16string_view(u"darkgreen"), 0x00006400UL }, + { std::u16string_view(u"darkkhaki"), 0x00bdb76bUL }, + { std::u16string_view(u"darkmagenta"), 0x008b008bUL }, + { std::u16string_view(u"darkolivegreen"), 0x00556b2fUL }, + { std::u16string_view(u"darkorange"), 0x00ff8c00UL }, + { std::u16string_view(u"darkorchid"), 0x009932ccUL }, + { std::u16string_view(u"darkred"), 0x008b0000UL }, + { std::u16string_view(u"darksalmon"), 0x00e9967aUL }, + { std::u16string_view(u"darkseagreen"), 0x008fbc8fUL }, + { std::u16string_view(u"darkslateblue"), 0x00483d8bUL }, + { std::u16string_view(u"darkslategray"), 0x002f4f4fUL }, + { std::u16string_view(u"darkturquoise"), 0x0000ced1UL }, + { std::u16string_view(u"darkviolet"), 0x009400d3UL }, + { std::u16string_view(u"deeppink"), 0x00ff1493UL }, + { std::u16string_view(u"deepskyblue"), 0x0000bfffUL }, + { std::u16string_view(u"dimgray"), 0x00696969UL }, + { std::u16string_view(u"dodgerblue"), 0x001e90ffUL }, + { std::u16string_view(u"firebrick"), 0x00b22222UL }, + { std::u16string_view(u"floralwhite"), 0x00fffaf0UL }, + { std::u16string_view(u"forestgreen"), 0x00228b22UL }, + { std::u16string_view(u"fuchsia"), 0x00ff00ffUL }, + { std::u16string_view(u"gainsboro"), 0x00dcdcdcUL }, + { std::u16string_view(u"ghostwhite"), 0x00f8f8ffUL }, + { std::u16string_view(u"gold"), 0x00ffd700UL }, + { std::u16string_view(u"goldenrod"), 0x00daa520UL }, + { std::u16string_view(u"gray"), 0x00808080UL }, + { std::u16string_view(u"green"), 0x00008000UL }, + { std::u16string_view(u"greenyellow"), 0x00adff2fUL }, + { std::u16string_view(u"honeydew"), 0x00f0fff0UL }, + { std::u16string_view(u"hotpink"), 0x00ff69b4UL }, + { std::u16string_view(u"indianred"), 0x00cd5c5cUL }, + { std::u16string_view(u"indigo"), 0x004b0082UL }, + { std::u16string_view(u"ivory"), 0x00fffff0UL }, + { std::u16string_view(u"khaki"), 0x00f0e68cUL }, + { std::u16string_view(u"lavender"), 0x00e6e6faUL }, + { std::u16string_view(u"lavenderblush"), 0x00fff0f5UL }, + { std::u16string_view(u"lawngreen"), 0x007cfc00UL }, + { std::u16string_view(u"lemonchiffon"), 0x00fffacdUL }, + { std::u16string_view(u"lightblue"), 0x00add8e6UL }, + { std::u16string_view(u"lightcoral"), 0x00f08080UL }, + { std::u16string_view(u"lightcyan"), 0x00e0ffffUL }, + { std::u16string_view(u"lightgoldenrodyellow"), 0x00fafad2UL }, + { std::u16string_view(u"lightgreen"), 0x0090ee90UL }, + { std::u16string_view(u"lightgrey"), 0x00d3d3d3UL }, + { std::u16string_view(u"lightpink"), 0x00ffb6c1UL }, + { std::u16string_view(u"lightsalmon"), 0x00ffa07aUL }, + { std::u16string_view(u"lightseagreen"), 0x0020b2aaUL }, + { std::u16string_view(u"lightskyblue"), 0x0087cefaUL }, + { std::u16string_view(u"lightslategray"), 0x00778899UL }, + { std::u16string_view(u"lightsteelblue"), 0x00b0c4deUL }, + { std::u16string_view(u"lightyellow"), 0x00ffffe0UL }, + { std::u16string_view(u"lime"), 0x0000ff00UL }, + { std::u16string_view(u"limegreen"), 0x0032cd32UL }, + { std::u16string_view(u"linen"), 0x00faf0e6UL }, + { std::u16string_view(u"magenta"), 0x00ff00ffUL }, + { std::u16string_view(u"maroon"), 0x00800000UL }, + { std::u16string_view(u"mediumaquamarine"), 0x0066cdaaUL }, + { std::u16string_view(u"mediumblue"), 0x000000cdUL }, + { std::u16string_view(u"mediumorchid"), 0x00ba55d3UL }, + { std::u16string_view(u"mediumpurple"), 0x009370dbUL }, + { std::u16string_view(u"mediumseagreen"), 0x003cb371UL }, + { std::u16string_view(u"mediumslateblue"), 0x007b68eeUL }, + { std::u16string_view(u"mediumspringgreen"), 0x0000fa9aUL }, + { std::u16string_view(u"mediumturquoise"), 0x0048d1ccUL }, + { std::u16string_view(u"mediumvioletred"), 0x00c71585UL }, + { std::u16string_view(u"midnightblue"), 0x00191970UL }, + { std::u16string_view(u"mintcream"), 0x00f5fffaUL }, + { std::u16string_view(u"mistyrose"), 0x00ffe4e1UL }, + { std::u16string_view(u"moccasin"), 0x00ffe4b5UL }, + { std::u16string_view(u"navajowhite"), 0x00ffdeadUL }, + { std::u16string_view(u"navy"), 0x00000080UL }, + { std::u16string_view(u"oldlace"), 0x00fdf5e6UL }, + { std::u16string_view(u"olive"), 0x00808000UL }, + { std::u16string_view(u"olivedrab"), 0x006b8e23UL }, + { std::u16string_view(u"orange"), 0x00ffa500UL }, + { std::u16string_view(u"orangered"), 0x00ff4500UL }, + { std::u16string_view(u"orchid"), 0x00da70d6UL }, + { std::u16string_view(u"palegoldenrod"), 0x00eee8aaUL }, + { std::u16string_view(u"palegreen"), 0x0098fb98UL }, + { std::u16string_view(u"paleturquoise"), 0x00afeeeeUL }, + { std::u16string_view(u"palevioletred"), 0x00db7093UL }, + { std::u16string_view(u"papayawhip"), 0x00ffefd5UL }, + { std::u16string_view(u"peachpuff"), 0x00ffdab9UL }, + { std::u16string_view(u"peru"), 0x00cd853fUL }, + { std::u16string_view(u"pink"), 0x00ffc0cbUL }, + { std::u16string_view(u"plum"), 0x00dda0ddUL }, + { std::u16string_view(u"powderblue"), 0x00b0e0e6UL }, + { std::u16string_view(u"purple"), 0x00800080UL }, + { std::u16string_view(u"red"), 0x00ff0000UL }, + { std::u16string_view(u"rosybrown"), 0x00bc8f8fUL }, + { std::u16string_view(u"royalblue"), 0x004169e1UL }, + { std::u16string_view(u"saddlebrown"), 0x008b4513UL }, + { std::u16string_view(u"salmon"), 0x00fa8072UL }, + { std::u16string_view(u"sandybrown"), 0x00f4a460UL }, + { std::u16string_view(u"seagreen"), 0x002e8b57UL }, + { std::u16string_view(u"seashell"), 0x00fff5eeUL }, + { std::u16string_view(u"sienna"), 0x00a0522dUL }, + { std::u16string_view(u"silver"), 0x00c0c0c0UL }, + { std::u16string_view(u"skyblue"), 0x0087ceebUL }, + { std::u16string_view(u"slateblue"), 0x006a5acdUL }, + { std::u16string_view(u"slategray"), 0x00708090UL }, + { std::u16string_view(u"snow"), 0x00fffafaUL }, + { std::u16string_view(u"springgreen"), 0x0000ff7fUL }, + { std::u16string_view(u"steelblue"), 0x004682b4UL }, + { std::u16string_view(u"tan"), 0x00d2b48cUL }, + { std::u16string_view(u"teal"), 0x00008080UL }, + { std::u16string_view(u"thistle"), 0x00d8bfd8UL }, + { std::u16string_view(u"tomato"), 0x00ff6347UL }, + { std::u16string_view(u"turquoise"), 0x0040e0d0UL }, + { std::u16string_view(u"violet"), 0x00ee82eeUL }, + { std::u16string_view(u"wheat"), 0x00f5deb3UL }, + { std::u16string_view(u"white"), 0x00ffffffUL }, + { std::u16string_view(u"whitesmoke"), 0x00f5f5f5UL }, + { std::u16string_view(u"yellow"), 0x00ffff00UL }, + { std::u16string_view(u"yellowgreen"), 0x009acd32UL } +}; + +sal_uInt32 GetHTMLColor( const OUString& rName ) +{ + if( !bSortColorKeyWords ) + { + assert( std::is_sorted( std::begin(aHTMLColorNameTab), std::end(aHTMLColorNameTab), + sortCompare<sal_uInt32> ) ); + bSortColorKeyWords = true; + } + + OUString aLowerCase(rName.toAsciiLowerCase()); + + return search<sal_uInt32>( aHTMLColorNameTab, aLowerCase, SAL_MAX_UINT32); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/svhtml/htmlout.cxx b/svtools/source/svhtml/htmlout.cxx new file mode 100644 index 000000000..b42e425df --- /dev/null +++ b/svtools/source/svhtml/htmlout.cxx @@ -0,0 +1,1007 @@ +/* -*- 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 <svl/numformat.hxx> +#include <svl/zformat.hxx> +#include <svl/macitem.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> + +#include <svtools/HtmlWriter.hxx> +#include <svtools/htmlout.hxx> +#include <svtools/htmlkywd.hxx> +#include <vcl/imap.hxx> +#include <vcl/imaprect.hxx> +#include <vcl/imapcirc.hxx> +#include <vcl/imappoly.hxx> +#include <svl/urihelper.hxx> +#include <rtl/character.hxx> +#include <tools/debug.hxx> + +#include <sstream> + +#define TXTCONV_BUFFER_SIZE 20 + +static sal_Size convertUnicodeToText(const sal_Unicode* pSrcBuf, sal_Size nSrcChars, char* pDestBuf, + sal_Size nDestBytes, sal_uInt32 nFlags, sal_uInt32* pInfo, + sal_Size* pSrcCvtChars) +{ + static rtl_UnicodeToTextConverter hConverter + = rtl_createUnicodeToTextConverter(RTL_TEXTENCODING_UTF8); + static rtl_UnicodeToTextContext hContext = hConverter + ? rtl_createUnicodeToTextContext(hConverter) + : reinterpret_cast<rtl_TextToUnicodeContext>(1); + + return rtl_convertUnicodeToText(hConverter, hContext, pSrcBuf, nSrcChars, pDestBuf, nDestBytes, + nFlags, pInfo, pSrcCvtChars); +} + +static const char *lcl_svhtml_GetEntityForChar( sal_uInt32 c, + rtl_TextEncoding eDestEnc ) +{ + const char* pStr = nullptr; + + // Note: We currently handle special cases for ISO-8859-2 here simply because + // the code was already submitted. But we should also handle other code pages + // as well as the code becomes available. + + if( eDestEnc == RTL_TEXTENCODING_ISO_8859_2 || eDestEnc == RTL_TEXTENCODING_MS_1250 ) + { + // Don't handle the following characters for Easter European (ISO-8859-2). + switch ( c ) + { + case 164: // curren + case 184: // ccedil + case 193: // Aacute + case 194: // Acirc + case 196: // Auml + case 199: // Ccedil + case 201: // Eacute + case 203: // Euml + case 205: // Iacute + case 206: // Icirc + case 211: // Oacute + case 212: // Ocirc + case 214: // Ouml + case 215: // times + case 218: // Uacute + case 220: // Uuml + case 221: // Yacute + case 225: // aacute + case 226: // acirc + case 228: // auml + case 233: // eacute + case 235: // euml + case 237: // iacute + case 238: // icirc + case 243: // oacute + case 244: // ocirc + case 246: // ouml + case 247: // divide + case 250: // uacute + case 252: // uuml + case 253: // yacute + case 352: // Scaron + case 353: // scaron + return pStr; + } + } + + // TODO: handle more special cases for other code pages. + + switch( c ) + { +// case '\x0a': return HTMLOutFuncs::Out_Tag( rStream, OOO_STRING_SVTOOLS_HTML_linebreak ); + + case '<': pStr = OOO_STRING_SVTOOLS_HTML_C_lt; break; + case '>': pStr = OOO_STRING_SVTOOLS_HTML_C_gt; break; + case '&': pStr = OOO_STRING_SVTOOLS_HTML_C_amp; break; + case '"': pStr = OOO_STRING_SVTOOLS_HTML_C_quot; break; + + case 161: pStr = OOO_STRING_SVTOOLS_HTML_S_iexcl; break; + case 162: pStr = OOO_STRING_SVTOOLS_HTML_S_cent; break; + case 163: pStr = OOO_STRING_SVTOOLS_HTML_S_pound; break; + case 164: pStr = OOO_STRING_SVTOOLS_HTML_S_curren; break; + case 165: pStr = OOO_STRING_SVTOOLS_HTML_S_yen; break; + case 166: pStr = OOO_STRING_SVTOOLS_HTML_S_brvbar; break; + case 167: pStr = OOO_STRING_SVTOOLS_HTML_S_sect; break; + case 168: pStr = OOO_STRING_SVTOOLS_HTML_S_uml; break; + case 169: pStr = OOO_STRING_SVTOOLS_HTML_S_copy; break; + case 170: pStr = OOO_STRING_SVTOOLS_HTML_S_ordf; break; + case 171: pStr = OOO_STRING_SVTOOLS_HTML_S_laquo; break; + case 172: pStr = OOO_STRING_SVTOOLS_HTML_S_not; break; + case 174: pStr = OOO_STRING_SVTOOLS_HTML_S_reg; break; + case 175: pStr = OOO_STRING_SVTOOLS_HTML_S_macr; break; + case 176: pStr = OOO_STRING_SVTOOLS_HTML_S_deg; break; + case 177: pStr = OOO_STRING_SVTOOLS_HTML_S_plusmn; break; + case 178: pStr = OOO_STRING_SVTOOLS_HTML_S_sup2; break; + case 179: pStr = OOO_STRING_SVTOOLS_HTML_S_sup3; break; + case 180: pStr = OOO_STRING_SVTOOLS_HTML_S_acute; break; + case 181: pStr = OOO_STRING_SVTOOLS_HTML_S_micro; break; + case 182: pStr = OOO_STRING_SVTOOLS_HTML_S_para; break; + case 183: pStr = OOO_STRING_SVTOOLS_HTML_S_middot; break; + case 184: pStr = OOO_STRING_SVTOOLS_HTML_S_cedil; break; + case 185: pStr = OOO_STRING_SVTOOLS_HTML_S_sup1; break; + case 186: pStr = OOO_STRING_SVTOOLS_HTML_S_ordm; break; + case 187: pStr = OOO_STRING_SVTOOLS_HTML_S_raquo; break; + case 188: pStr = OOO_STRING_SVTOOLS_HTML_S_frac14; break; + case 189: pStr = OOO_STRING_SVTOOLS_HTML_S_frac12; break; + case 190: pStr = OOO_STRING_SVTOOLS_HTML_S_frac34; break; + case 191: pStr = OOO_STRING_SVTOOLS_HTML_S_iquest; break; + + case 192: pStr = OOO_STRING_SVTOOLS_HTML_C_Agrave; break; + case 193: pStr = OOO_STRING_SVTOOLS_HTML_C_Aacute; break; + case 194: pStr = OOO_STRING_SVTOOLS_HTML_C_Acirc; break; + case 195: pStr = OOO_STRING_SVTOOLS_HTML_C_Atilde; break; + case 196: pStr = OOO_STRING_SVTOOLS_HTML_C_Auml; break; + case 197: pStr = OOO_STRING_SVTOOLS_HTML_C_Aring; break; + case 198: pStr = OOO_STRING_SVTOOLS_HTML_C_AElig; break; + case 199: pStr = OOO_STRING_SVTOOLS_HTML_C_Ccedil; break; + case 200: pStr = OOO_STRING_SVTOOLS_HTML_C_Egrave; break; + case 201: pStr = OOO_STRING_SVTOOLS_HTML_C_Eacute; break; + case 202: pStr = OOO_STRING_SVTOOLS_HTML_C_Ecirc; break; + case 203: pStr = OOO_STRING_SVTOOLS_HTML_C_Euml; break; + case 204: pStr = OOO_STRING_SVTOOLS_HTML_C_Igrave; break; + case 205: pStr = OOO_STRING_SVTOOLS_HTML_C_Iacute; break; + case 206: pStr = OOO_STRING_SVTOOLS_HTML_C_Icirc; break; + case 207: pStr = OOO_STRING_SVTOOLS_HTML_C_Iuml; break; + case 208: pStr = OOO_STRING_SVTOOLS_HTML_C_ETH; break; + case 209: pStr = OOO_STRING_SVTOOLS_HTML_C_Ntilde; break; + case 210: pStr = OOO_STRING_SVTOOLS_HTML_C_Ograve; break; + case 211: pStr = OOO_STRING_SVTOOLS_HTML_C_Oacute; break; + case 212: pStr = OOO_STRING_SVTOOLS_HTML_C_Ocirc; break; + case 213: pStr = OOO_STRING_SVTOOLS_HTML_C_Otilde; break; + case 214: pStr = OOO_STRING_SVTOOLS_HTML_C_Ouml; break; + case 215: pStr = OOO_STRING_SVTOOLS_HTML_S_times; break; + case 216: pStr = OOO_STRING_SVTOOLS_HTML_C_Oslash; break; + case 217: pStr = OOO_STRING_SVTOOLS_HTML_C_Ugrave; break; + case 218: pStr = OOO_STRING_SVTOOLS_HTML_C_Uacute; break; + case 219: pStr = OOO_STRING_SVTOOLS_HTML_C_Ucirc; break; + case 220: pStr = OOO_STRING_SVTOOLS_HTML_C_Uuml; break; + case 221: pStr = OOO_STRING_SVTOOLS_HTML_C_Yacute; break; + + case 222: pStr = OOO_STRING_SVTOOLS_HTML_C_THORN; break; + case 223: pStr = OOO_STRING_SVTOOLS_HTML_C_szlig; break; + + case 224: pStr = OOO_STRING_SVTOOLS_HTML_S_agrave; break; + case 225: pStr = OOO_STRING_SVTOOLS_HTML_S_aacute; break; + case 226: pStr = OOO_STRING_SVTOOLS_HTML_S_acirc; break; + case 227: pStr = OOO_STRING_SVTOOLS_HTML_S_atilde; break; + case 228: pStr = OOO_STRING_SVTOOLS_HTML_S_auml; break; + case 229: pStr = OOO_STRING_SVTOOLS_HTML_S_aring; break; + case 230: pStr = OOO_STRING_SVTOOLS_HTML_S_aelig; break; + case 231: pStr = OOO_STRING_SVTOOLS_HTML_S_ccedil; break; + case 232: pStr = OOO_STRING_SVTOOLS_HTML_S_egrave; break; + case 233: pStr = OOO_STRING_SVTOOLS_HTML_S_eacute; break; + case 234: pStr = OOO_STRING_SVTOOLS_HTML_S_ecirc; break; + case 235: pStr = OOO_STRING_SVTOOLS_HTML_S_euml; break; + case 236: pStr = OOO_STRING_SVTOOLS_HTML_S_igrave; break; + case 237: pStr = OOO_STRING_SVTOOLS_HTML_S_iacute; break; + case 238: pStr = OOO_STRING_SVTOOLS_HTML_S_icirc; break; + case 239: pStr = OOO_STRING_SVTOOLS_HTML_S_iuml; break; + case 240: pStr = OOO_STRING_SVTOOLS_HTML_S_eth; break; + case 241: pStr = OOO_STRING_SVTOOLS_HTML_S_ntilde; break; + case 242: pStr = OOO_STRING_SVTOOLS_HTML_S_ograve; break; + case 243: pStr = OOO_STRING_SVTOOLS_HTML_S_oacute; break; + case 244: pStr = OOO_STRING_SVTOOLS_HTML_S_ocirc; break; + case 245: pStr = OOO_STRING_SVTOOLS_HTML_S_otilde; break; + case 246: pStr = OOO_STRING_SVTOOLS_HTML_S_ouml; break; + case 247: pStr = OOO_STRING_SVTOOLS_HTML_S_divide; break; + case 248: pStr = OOO_STRING_SVTOOLS_HTML_S_oslash; break; + case 249: pStr = OOO_STRING_SVTOOLS_HTML_S_ugrave; break; + case 250: pStr = OOO_STRING_SVTOOLS_HTML_S_uacute; break; + case 251: pStr = OOO_STRING_SVTOOLS_HTML_S_ucirc; break; + case 252: pStr = OOO_STRING_SVTOOLS_HTML_S_uuml; break; + case 253: pStr = OOO_STRING_SVTOOLS_HTML_S_yacute; break; + case 254: pStr = OOO_STRING_SVTOOLS_HTML_S_thorn; break; + case 255: pStr = OOO_STRING_SVTOOLS_HTML_S_yuml; break; + + case 338: pStr = OOO_STRING_SVTOOLS_HTML_S_OElig; break; + case 339: pStr = OOO_STRING_SVTOOLS_HTML_S_oelig; break; + case 352: pStr = OOO_STRING_SVTOOLS_HTML_S_Scaron; break; + case 353: pStr = OOO_STRING_SVTOOLS_HTML_S_scaron; break; + case 376: pStr = OOO_STRING_SVTOOLS_HTML_S_Yuml; break; + case 402: pStr = OOO_STRING_SVTOOLS_HTML_S_fnof; break; + case 710: pStr = OOO_STRING_SVTOOLS_HTML_S_circ; break; + case 732: pStr = OOO_STRING_SVTOOLS_HTML_S_tilde; break; + + // Greek chars are handled later, + // since they should *not* be transformed to entities + // when generating Greek text (== using Greek encoding) + + case 8194: pStr = OOO_STRING_SVTOOLS_HTML_S_ensp; break; + case 8195: pStr = OOO_STRING_SVTOOLS_HTML_S_emsp; break; + case 8201: pStr = OOO_STRING_SVTOOLS_HTML_S_thinsp; break; + case 8204: pStr = OOO_STRING_SVTOOLS_HTML_S_zwnj; break; + case 8205: pStr = OOO_STRING_SVTOOLS_HTML_S_zwj; break; + case 8206: pStr = OOO_STRING_SVTOOLS_HTML_S_lrm; break; + case 8207: pStr = OOO_STRING_SVTOOLS_HTML_S_rlm; break; + case 8211: pStr = OOO_STRING_SVTOOLS_HTML_S_ndash; break; + case 8212: pStr = OOO_STRING_SVTOOLS_HTML_S_mdash; break; + case 8216: pStr = OOO_STRING_SVTOOLS_HTML_S_lsquo; break; + case 8217: pStr = OOO_STRING_SVTOOLS_HTML_S_rsquo; break; + case 8218: pStr = OOO_STRING_SVTOOLS_HTML_S_sbquo; break; + case 8220: pStr = OOO_STRING_SVTOOLS_HTML_S_ldquo; break; + case 8221: pStr = OOO_STRING_SVTOOLS_HTML_S_rdquo; break; + case 8222: pStr = OOO_STRING_SVTOOLS_HTML_S_bdquo; break; + case 8224: pStr = OOO_STRING_SVTOOLS_HTML_S_dagger; break; + case 8225: pStr = OOO_STRING_SVTOOLS_HTML_S_Dagger; break; + case 8226: pStr = OOO_STRING_SVTOOLS_HTML_S_bull; break; + case 8230: pStr = OOO_STRING_SVTOOLS_HTML_S_hellip; break; + case 8240: pStr = OOO_STRING_SVTOOLS_HTML_S_permil; break; + case 8242: pStr = OOO_STRING_SVTOOLS_HTML_S_prime; break; + case 8243: pStr = OOO_STRING_SVTOOLS_HTML_S_Prime; break; + case 8249: pStr = OOO_STRING_SVTOOLS_HTML_S_lsaquo; break; + case 8250: pStr = OOO_STRING_SVTOOLS_HTML_S_rsaquo; break; + case 8254: pStr = OOO_STRING_SVTOOLS_HTML_S_oline; break; + case 8260: pStr = OOO_STRING_SVTOOLS_HTML_S_frasl; break; + case 8364: pStr = OOO_STRING_SVTOOLS_HTML_S_euro; break; + case 8465: pStr = OOO_STRING_SVTOOLS_HTML_S_image; break; + case 8472: pStr = OOO_STRING_SVTOOLS_HTML_S_weierp; break; + case 8476: pStr = OOO_STRING_SVTOOLS_HTML_S_real; break; + case 8482: pStr = OOO_STRING_SVTOOLS_HTML_S_trade; break; + case 8501: pStr = OOO_STRING_SVTOOLS_HTML_S_alefsym; break; + case 8592: pStr = OOO_STRING_SVTOOLS_HTML_S_larr; break; + case 8593: pStr = OOO_STRING_SVTOOLS_HTML_S_uarr; break; + case 8594: pStr = OOO_STRING_SVTOOLS_HTML_S_rarr; break; + case 8595: pStr = OOO_STRING_SVTOOLS_HTML_S_darr; break; + case 8596: pStr = OOO_STRING_SVTOOLS_HTML_S_harr; break; + case 8629: pStr = OOO_STRING_SVTOOLS_HTML_S_crarr; break; + case 8656: pStr = OOO_STRING_SVTOOLS_HTML_S_lArr; break; + case 8657: pStr = OOO_STRING_SVTOOLS_HTML_S_uArr; break; + case 8658: pStr = OOO_STRING_SVTOOLS_HTML_S_rArr; break; + case 8659: pStr = OOO_STRING_SVTOOLS_HTML_S_dArr; break; + case 8660: pStr = OOO_STRING_SVTOOLS_HTML_S_hArr; break; + case 8704: pStr = OOO_STRING_SVTOOLS_HTML_S_forall; break; + case 8706: pStr = OOO_STRING_SVTOOLS_HTML_S_part; break; + case 8707: pStr = OOO_STRING_SVTOOLS_HTML_S_exist; break; + case 8709: pStr = OOO_STRING_SVTOOLS_HTML_S_empty; break; + case 8711: pStr = OOO_STRING_SVTOOLS_HTML_S_nabla; break; + case 8712: pStr = OOO_STRING_SVTOOLS_HTML_S_isin; break; + case 8713: pStr = OOO_STRING_SVTOOLS_HTML_S_notin; break; + case 8715: pStr = OOO_STRING_SVTOOLS_HTML_S_ni; break; + case 8719: pStr = OOO_STRING_SVTOOLS_HTML_S_prod; break; + case 8721: pStr = OOO_STRING_SVTOOLS_HTML_S_sum; break; + case 8722: pStr = OOO_STRING_SVTOOLS_HTML_S_minus; break; + case 8727: pStr = OOO_STRING_SVTOOLS_HTML_S_lowast; break; + case 8730: pStr = OOO_STRING_SVTOOLS_HTML_S_radic; break; + case 8733: pStr = OOO_STRING_SVTOOLS_HTML_S_prop; break; + case 8734: pStr = OOO_STRING_SVTOOLS_HTML_S_infin; break; + case 8736: pStr = OOO_STRING_SVTOOLS_HTML_S_ang; break; + case 8743: pStr = OOO_STRING_SVTOOLS_HTML_S_and; break; + case 8744: pStr = OOO_STRING_SVTOOLS_HTML_S_or; break; + case 8745: pStr = OOO_STRING_SVTOOLS_HTML_S_cap; break; + case 8746: pStr = OOO_STRING_SVTOOLS_HTML_S_cup; break; + case 8747: pStr = OOO_STRING_SVTOOLS_HTML_S_int; break; + case 8756: pStr = OOO_STRING_SVTOOLS_HTML_S_there4; break; + case 8764: pStr = OOO_STRING_SVTOOLS_HTML_S_sim; break; + case 8773: pStr = OOO_STRING_SVTOOLS_HTML_S_cong; break; + case 8776: pStr = OOO_STRING_SVTOOLS_HTML_S_asymp; break; + case 8800: pStr = OOO_STRING_SVTOOLS_HTML_S_ne; break; + case 8801: pStr = OOO_STRING_SVTOOLS_HTML_S_equiv; break; + case 8804: pStr = OOO_STRING_SVTOOLS_HTML_S_le; break; + case 8805: pStr = OOO_STRING_SVTOOLS_HTML_S_ge; break; + case 8834: pStr = OOO_STRING_SVTOOLS_HTML_S_sub; break; + case 8835: pStr = OOO_STRING_SVTOOLS_HTML_S_sup; break; + case 8836: pStr = OOO_STRING_SVTOOLS_HTML_S_nsub; break; + case 8838: pStr = OOO_STRING_SVTOOLS_HTML_S_sube; break; + case 8839: pStr = OOO_STRING_SVTOOLS_HTML_S_supe; break; + case 8853: pStr = OOO_STRING_SVTOOLS_HTML_S_oplus; break; + case 8855: pStr = OOO_STRING_SVTOOLS_HTML_S_otimes; break; + case 8869: pStr = OOO_STRING_SVTOOLS_HTML_S_perp; break; + case 8901: pStr = OOO_STRING_SVTOOLS_HTML_S_sdot; break; + case 8968: pStr = OOO_STRING_SVTOOLS_HTML_S_lceil; break; + case 8969: pStr = OOO_STRING_SVTOOLS_HTML_S_rceil; break; + case 8970: pStr = OOO_STRING_SVTOOLS_HTML_S_lfloor; break; + case 8971: pStr = OOO_STRING_SVTOOLS_HTML_S_rfloor; break; + case 9001: pStr = OOO_STRING_SVTOOLS_HTML_S_lang; break; + case 9002: pStr = OOO_STRING_SVTOOLS_HTML_S_rang; break; + case 9674: pStr = OOO_STRING_SVTOOLS_HTML_S_loz; break; + case 9824: pStr = OOO_STRING_SVTOOLS_HTML_S_spades; break; + case 9827: pStr = OOO_STRING_SVTOOLS_HTML_S_clubs; break; + case 9829: pStr = OOO_STRING_SVTOOLS_HTML_S_hearts; break; + case 9830: pStr = OOO_STRING_SVTOOLS_HTML_S_diams; break; + } + + // Greek chars: if we do not produce a Greek encoding, + // transform them into entities + if( !pStr && + ( eDestEnc != RTL_TEXTENCODING_ISO_8859_7 ) && + ( eDestEnc != RTL_TEXTENCODING_MS_1253 ) ) + { + switch( c ) + { + case 913: pStr = OOO_STRING_SVTOOLS_HTML_S_Alpha; break; + case 914: pStr = OOO_STRING_SVTOOLS_HTML_S_Beta; break; + case 915: pStr = OOO_STRING_SVTOOLS_HTML_S_Gamma; break; + case 916: pStr = OOO_STRING_SVTOOLS_HTML_S_Delta; break; + case 917: pStr = OOO_STRING_SVTOOLS_HTML_S_Epsilon; break; + case 918: pStr = OOO_STRING_SVTOOLS_HTML_S_Zeta; break; + case 919: pStr = OOO_STRING_SVTOOLS_HTML_S_Eta; break; + case 920: pStr = OOO_STRING_SVTOOLS_HTML_S_Theta; break; + case 921: pStr = OOO_STRING_SVTOOLS_HTML_S_Iota; break; + case 922: pStr = OOO_STRING_SVTOOLS_HTML_S_Kappa; break; + case 923: pStr = OOO_STRING_SVTOOLS_HTML_S_Lambda; break; + case 924: pStr = OOO_STRING_SVTOOLS_HTML_S_Mu; break; + case 925: pStr = OOO_STRING_SVTOOLS_HTML_S_Nu; break; + case 926: pStr = OOO_STRING_SVTOOLS_HTML_S_Xi; break; + case 927: pStr = OOO_STRING_SVTOOLS_HTML_S_Omicron; break; + case 928: pStr = OOO_STRING_SVTOOLS_HTML_S_Pi; break; + case 929: pStr = OOO_STRING_SVTOOLS_HTML_S_Rho; break; + case 931: pStr = OOO_STRING_SVTOOLS_HTML_S_Sigma; break; + case 932: pStr = OOO_STRING_SVTOOLS_HTML_S_Tau; break; + case 933: pStr = OOO_STRING_SVTOOLS_HTML_S_Upsilon; break; + case 934: pStr = OOO_STRING_SVTOOLS_HTML_S_Phi; break; + case 935: pStr = OOO_STRING_SVTOOLS_HTML_S_Chi; break; + case 936: pStr = OOO_STRING_SVTOOLS_HTML_S_Psi; break; + case 937: pStr = OOO_STRING_SVTOOLS_HTML_S_Omega; break; + case 945: pStr = OOO_STRING_SVTOOLS_HTML_S_alpha; break; + case 946: pStr = OOO_STRING_SVTOOLS_HTML_S_beta; break; + case 947: pStr = OOO_STRING_SVTOOLS_HTML_S_gamma; break; + case 948: pStr = OOO_STRING_SVTOOLS_HTML_S_delta; break; + case 949: pStr = OOO_STRING_SVTOOLS_HTML_S_epsilon; break; + case 950: pStr = OOO_STRING_SVTOOLS_HTML_S_zeta; break; + case 951: pStr = OOO_STRING_SVTOOLS_HTML_S_eta; break; + case 952: pStr = OOO_STRING_SVTOOLS_HTML_S_theta; break; + case 953: pStr = OOO_STRING_SVTOOLS_HTML_S_iota; break; + case 954: pStr = OOO_STRING_SVTOOLS_HTML_S_kappa; break; + case 955: pStr = OOO_STRING_SVTOOLS_HTML_S_lambda; break; + case 956: pStr = OOO_STRING_SVTOOLS_HTML_S_mu; break; + case 957: pStr = OOO_STRING_SVTOOLS_HTML_S_nu; break; + case 958: pStr = OOO_STRING_SVTOOLS_HTML_S_xi; break; + case 959: pStr = OOO_STRING_SVTOOLS_HTML_S_omicron; break; + case 960: pStr = OOO_STRING_SVTOOLS_HTML_S_pi; break; + case 961: pStr = OOO_STRING_SVTOOLS_HTML_S_rho; break; + case 962: pStr = OOO_STRING_SVTOOLS_HTML_S_sigmaf; break; + case 963: pStr = OOO_STRING_SVTOOLS_HTML_S_sigma; break; + case 964: pStr = OOO_STRING_SVTOOLS_HTML_S_tau; break; + case 965: pStr = OOO_STRING_SVTOOLS_HTML_S_upsilon; break; + case 966: pStr = OOO_STRING_SVTOOLS_HTML_S_phi; break; + case 967: pStr = OOO_STRING_SVTOOLS_HTML_S_chi; break; + case 968: pStr = OOO_STRING_SVTOOLS_HTML_S_psi; break; + case 969: pStr = OOO_STRING_SVTOOLS_HTML_S_omega; break; + case 977: pStr = OOO_STRING_SVTOOLS_HTML_S_thetasym;break; + case 978: pStr = OOO_STRING_SVTOOLS_HTML_S_upsih; break; + case 982: pStr = OOO_STRING_SVTOOLS_HTML_S_piv; break; + } + } + + return pStr; +} + +static sal_Size lcl_FlushContext(char* pBuffer, sal_uInt32 nFlags) +{ + sal_uInt32 nInfo = 0; + sal_Size nSrcChars; + sal_Size nLen = convertUnicodeToText(nullptr, 0, + pBuffer, TXTCONV_BUFFER_SIZE, nFlags|RTL_UNICODETOTEXT_FLAGS_FLUSH, + &nInfo, &nSrcChars); + DBG_ASSERT((nInfo & (RTL_UNICODETOTEXT_INFO_ERROR|RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL)) == 0, "HTMLOut: error while flushing"); + return nLen; +} + +static OString lcl_ConvertCharToHTML( sal_uInt32 c, + OUString *pNonConvertableChars ) +{ + assert(rtl::isUnicodeCodePoint(c)); + + OStringBuffer aDest; + const char *pStr = nullptr; + switch( c ) + { + case 0xA0: // is a hard blank + pStr = OOO_STRING_SVTOOLS_HTML_S_nbsp; + break; + case 0x2011: // is a hard hyphen + pStr = "#8209"; + break; + case 0xAD: // is a soft hyphen + pStr = OOO_STRING_SVTOOLS_HTML_S_shy; + break; + default: + // There may be an entity for the character. + // The new HTML4 entities above 255 are not used for UTF-8, + // because Netscape 4 does support UTF-8 but does not support + // these entities. + if( c < 128 ) + pStr = lcl_svhtml_GetEntityForChar( c, RTL_TEXTENCODING_UTF8 ); + break; + } + + char cBuffer[TXTCONV_BUFFER_SIZE]; + const sal_uInt32 nFlags = RTL_UNICODETOTEXT_FLAGS_NONSPACING_IGNORE| + RTL_UNICODETOTEXT_FLAGS_CONTROL_IGNORE| + RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR| + RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR; + if( pStr ) + { + sal_Size nLen = lcl_FlushContext(cBuffer, nFlags); + char *pBuffer = cBuffer; + while( nLen-- ) + aDest.append(*pBuffer++); + aDest.append('&').append(pStr).append(';'); + } + else + { + sal_uInt32 nInfo = 0; + sal_Size nSrcChars; + + sal_Unicode utf16[2]; + auto n = rtl::splitSurrogates(c, utf16); + sal_Size nLen = convertUnicodeToText(utf16, n, + cBuffer, TXTCONV_BUFFER_SIZE, + nFlags, &nInfo, &nSrcChars); + if( nLen > 0 && (nInfo & (RTL_UNICODETOTEXT_INFO_ERROR|RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL)) == 0 ) + { + char *pBuffer = cBuffer; + while( nLen-- ) + aDest.append(*pBuffer++); + } + else + { + // If the character could not be converted to the destination + // character set, the UNICODE character is exported as character + // entity. + // coverity[callee_ptr_arith] - its ok + nLen = lcl_FlushContext(cBuffer, nFlags); + char *pBuffer = cBuffer; + while( nLen-- ) + aDest.append(*pBuffer++); + + aDest.append('&').append('#').append(static_cast<sal_Int32>(c)) + // Unicode code points guaranteed to fit into sal_Int32 + .append(';'); + if( pNonConvertableChars ) + { + OUString cs(&c, 1); + if( -1 == pNonConvertableChars->indexOf( cs ) ) + (*pNonConvertableChars) += cs; + } + } + } + return aDest.makeStringAndClear(); +} + +static OString lcl_FlushToAscii() +{ + OStringBuffer aDest; + + char cBuffer[TXTCONV_BUFFER_SIZE]; + const sal_uInt32 nFlags = RTL_UNICODETOTEXT_FLAGS_NONSPACING_IGNORE| + RTL_UNICODETOTEXT_FLAGS_CONTROL_IGNORE| + RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR| + RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR; + sal_Size nLen = lcl_FlushContext(cBuffer, nFlags); + char *pBuffer = cBuffer; + while( nLen-- ) + aDest.append(*pBuffer++); + return aDest.makeStringAndClear(); +} + +OString HTMLOutFuncs::ConvertStringToHTML( const OUString& rSrc, + OUString *pNonConvertableChars ) +{ + OStringBuffer aDest; + for( sal_Int32 i=0, nLen = rSrc.getLength(); i < nLen; ) + aDest.append(lcl_ConvertCharToHTML( + rSrc.iterateCodePoints(&i), pNonConvertableChars)); + aDest.append(lcl_FlushToAscii()); + return aDest.makeStringAndClear(); +} + +SvStream& HTMLOutFuncs::Out_AsciiTag( SvStream& rStream, std::string_view rStr, + bool bOn ) +{ + if(bOn) + rStream.WriteCharPtr("<"); + else + rStream.WriteCharPtr("</"); + + rStream.WriteOString(rStr).WriteChar('>'); + + return rStream; +} + +SvStream& HTMLOutFuncs::Out_Char( SvStream& rStream, sal_uInt32 c, + OUString *pNonConvertableChars ) +{ + OString sOut = lcl_ConvertCharToHTML( c, pNonConvertableChars ); + rStream.WriteOString( sOut ); + return rStream; +} + +SvStream& HTMLOutFuncs::Out_String( SvStream& rStream, const OUString& rOUStr, + OUString *pNonConvertableChars ) +{ + sal_Int32 nLen = rOUStr.getLength(); + for( sal_Int32 n = 0; n < nLen; ) + HTMLOutFuncs::Out_Char( rStream, rOUStr.iterateCodePoints(&n), + pNonConvertableChars ); + HTMLOutFuncs::FlushToAscii( rStream ); + return rStream; +} + +SvStream& HTMLOutFuncs::FlushToAscii( SvStream& rStream ) +{ + OString sOut = lcl_FlushToAscii(); + + if (!sOut.isEmpty()) + rStream.WriteOString( sOut ); + + return rStream; +} + +SvStream& HTMLOutFuncs::Out_Hex( SvStream& rStream, sal_uInt32 nHex, sal_uInt8 nLen ) +{ // out into a stream + char aNToABuf[] = "0000000000000000"; + + DBG_ASSERT( nLen < sizeof(aNToABuf), "too many places" ); + if( nLen>=sizeof(aNToABuf) ) + nLen = (sizeof(aNToABuf)-1); + + // set pointer to end of buffer + char *pStr = aNToABuf + (sizeof(aNToABuf)-1); + for( sal_uInt8 n = 0; n < nLen; ++n ) + { + *(--pStr) = static_cast<char>(nHex & 0xf ) + 48; + if( *pStr > '9' ) + *pStr += 39; + nHex >>= 4; + } + return rStream.WriteCharPtr( pStr ); +} + + +SvStream& HTMLOutFuncs::Out_Color( SvStream& rStream, const Color& rColor, bool bXHTML ) +{ + rStream.WriteCharPtr( "\"" ); + if (bXHTML) + rStream.WriteCharPtr( "color: " ); + rStream.WriteCharPtr( "#" ); + if( rColor == COL_AUTO ) + { + rStream.WriteCharPtr( "000000" ); + } + else + { + Out_Hex( rStream, rColor.GetRed(), 2 ); + Out_Hex( rStream, rColor.GetGreen(), 2 ); + Out_Hex( rStream, rColor.GetBlue(), 2 ); + } + rStream.WriteChar( '\"' ); + + return rStream; +} + +SvStream& HTMLOutFuncs::Out_ImageMap( SvStream& rStream, + const OUString& rBaseURL, + const ImageMap& rIMap, + const OUString& rName, + const HTMLOutEvent *pEventTable, + bool bOutStarBasic, + const char *pDelim, + const char *pIndentArea, + const char *pIndentMap, + OUString *pNonConvertableChars ) +{ + const OUString& rOutName = !rName.isEmpty() ? rName : rIMap.GetName(); + DBG_ASSERT( !rOutName.isEmpty(), "No ImageMap-Name" ); + if( rOutName.isEmpty() ) + return rStream; + + OStringBuffer sOut; + sOut.append(OString::Concat("<") + + OOO_STRING_SVTOOLS_HTML_map + " " + OOO_STRING_SVTOOLS_HTML_O_name + "=\""); + rStream.WriteOString( sOut.makeStringAndClear() ); + Out_String( rStream, rOutName, pNonConvertableChars ); + rStream.WriteCharPtr( "\">" ); + + for( size_t i=0; i<rIMap.GetIMapObjectCount(); i++ ) + { + const IMapObject* pObj = rIMap.GetIMapObject( i ); + DBG_ASSERT( pObj, "Where is the ImageMap-Object?" ); + + if( pObj ) + { + const char *pShape = nullptr; + OString aCoords; + switch( pObj->GetType() ) + { + case IMapObjectType::Rectangle: + { + const IMapRectangleObject* pRectObj = + static_cast<const IMapRectangleObject *>(pObj); + pShape = OOO_STRING_SVTOOLS_HTML_SH_rect; + tools::Rectangle aRect( pRectObj->GetRectangle() ); + + aCoords = OStringBuffer() + .append(static_cast<sal_Int32>(aRect.Left())) + .append(',') + .append(static_cast<sal_Int32>(aRect.Top())) + .append(',') + .append(static_cast<sal_Int32>(aRect.Right())) + .append(',') + .append(static_cast<sal_Int32>(aRect.Bottom())) + .makeStringAndClear(); + } + break; + case IMapObjectType::Circle: + { + const IMapCircleObject* pCirc = + static_cast<const IMapCircleObject *>(pObj); + pShape= OOO_STRING_SVTOOLS_HTML_SH_circ; + Point aCenter( pCirc->GetCenter() ); + tools::Long nOff = pCirc->GetRadius(); + + aCoords = OStringBuffer() + .append(static_cast<sal_Int32>(aCenter.X())) + .append(',') + .append(static_cast<sal_Int32>(aCenter.Y())) + .append(',') + .append(static_cast<sal_Int32>(nOff)) + .makeStringAndClear(); + } + break; + case IMapObjectType::Polygon: + { + const IMapPolygonObject* pPolyObj = + static_cast<const IMapPolygonObject *>(pObj); + pShape= OOO_STRING_SVTOOLS_HTML_SH_poly; + tools::Polygon aPoly( pPolyObj->GetPolygon() ); + sal_uInt16 nCount = aPoly.GetSize(); + OStringBuffer aTmpBuf; + if( nCount>0 ) + { + const Point& rPoint = aPoly[0]; + aTmpBuf.append(static_cast<sal_Int32>(rPoint.X())) + .append(',') + .append(static_cast<sal_Int32>(rPoint.Y())); + } + for( sal_uInt16 j=1; j<nCount; j++ ) + { + const Point& rPoint = aPoly[j]; + aTmpBuf.append(',') + .append(static_cast<sal_Int32>(rPoint.X())) + .append(',') + .append(static_cast<sal_Int32>(rPoint.Y())); + } + aCoords = aTmpBuf.makeStringAndClear(); + } + break; + default: + DBG_ASSERT( pShape, "unknown IMapObject" ); + break; + } + + if( pShape ) + { + if( pDelim ) + rStream.WriteCharPtr( pDelim ); + if( pIndentArea ) + rStream.WriteCharPtr( pIndentArea ); + + sOut.append(OString::Concat("<") + OOO_STRING_SVTOOLS_HTML_area + " " OOO_STRING_SVTOOLS_HTML_O_shape + "=" + pShape + " " + OOO_STRING_SVTOOLS_HTML_O_coords "=\"" + + aCoords + "\" "); + rStream.WriteOString( sOut.makeStringAndClear() ); + + OUString aURL( pObj->GetURL() ); + if( !aURL.isEmpty() && pObj->IsActive() ) + { + aURL = URIHelper::simpleNormalizedMakeRelative( + rBaseURL, aURL ); + sOut.append(OOO_STRING_SVTOOLS_HTML_O_href "=\""); + rStream.WriteOString( sOut.makeStringAndClear() ); + Out_String( rStream, aURL, pNonConvertableChars ).WriteChar( '\"' ); + } + else + rStream.WriteCharPtr( OOO_STRING_SVTOOLS_HTML_O_nohref ); + + const OUString& rObjName = pObj->GetName(); + if( !rObjName.isEmpty() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_name "=\""); + rStream.WriteOString( sOut.makeStringAndClear() ); + Out_String( rStream, rObjName, pNonConvertableChars ).WriteChar( '\"' ); + } + + const OUString& rTarget = pObj->GetTarget(); + if( !rTarget.isEmpty() && pObj->IsActive() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_target "=\""); + rStream.WriteOString( sOut.makeStringAndClear() ); + Out_String( rStream, rTarget, pNonConvertableChars ).WriteChar( '\"' ); + } + + OUString rDesc( pObj->GetAltText() ); + if( rDesc.isEmpty() ) + rDesc = pObj->GetDesc(); + + if( !rDesc.isEmpty() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_alt "=\""); + rStream.WriteOString( sOut.makeStringAndClear() ); + Out_String( rStream, rDesc, pNonConvertableChars ).WriteChar( '\"' ); + } + + const SvxMacroTableDtor& rMacroTab = pObj->GetMacroTable(); + if( pEventTable && !rMacroTab.empty() ) + Out_Events( rStream, rMacroTab, pEventTable, + bOutStarBasic, pNonConvertableChars ); + + rStream.WriteChar( '>' ); + } + } + + } + + if( pDelim ) + rStream.WriteCharPtr( pDelim ); + if( pIndentMap ) + rStream.WriteCharPtr( pIndentMap ); + Out_AsciiTag( rStream, OOO_STRING_SVTOOLS_HTML_map, false ); + + return rStream; +} + +SvStream& HTMLOutFuncs::OutScript( SvStream& rStrm, + const OUString& rBaseURL, + std::u16string_view rSource, + const OUString& rLanguage, + ScriptType eScriptType, + const OUString& rSrc, + const OUString *pSBLibrary, + const OUString *pSBModule, + OUString *pNonConvertableChars ) +{ + // script is not indented! + OStringBuffer sOut; + sOut.append('<') + .append(OOO_STRING_SVTOOLS_HTML_script); + + if( !rLanguage.isEmpty() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_language "=\""); + rStrm.WriteOString( sOut.makeStringAndClear() ); + Out_String( rStrm, rLanguage, pNonConvertableChars ); + sOut.append('\"'); + } + + if( !rSrc.isEmpty() ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_src "=\""); + rStrm.WriteOString( sOut.makeStringAndClear() ); + Out_String( rStrm, URIHelper::simpleNormalizedMakeRelative(rBaseURL, rSrc), pNonConvertableChars ); + sOut.append('\"'); + } + + if( STARBASIC != eScriptType && pSBLibrary ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_sdlibrary "=\""); + rStrm.WriteOString( sOut.makeStringAndClear() ); + Out_String( rStrm, *pSBLibrary, pNonConvertableChars ); + sOut.append('\"'); + } + + if( STARBASIC != eScriptType && pSBModule ) + { + sOut.append(" " OOO_STRING_SVTOOLS_HTML_O_sdmodule "=\""); + rStrm.WriteOString( sOut.makeStringAndClear() ); + Out_String( rStrm, *pSBModule, pNonConvertableChars ); + sOut.append('\"'); + } + + sOut.append('>'); + + rStrm.WriteOString( sOut.makeStringAndClear() ); + + if( !rSource.empty() || pSBLibrary || pSBModule ) + { + rStrm.WriteCharPtr( SAL_NEWLINE_STRING ); + + if( JAVASCRIPT != eScriptType ) + { + rStrm.WriteCharPtr( "<!--" ) + .WriteCharPtr( SAL_NEWLINE_STRING ); + } + + if( STARBASIC == eScriptType ) + { + if( pSBLibrary ) + { + sOut.append("' " OOO_STRING_SVTOOLS_HTML_SB_library " " + + OUStringToOString(*pSBLibrary, RTL_TEXTENCODING_UTF8)); + rStrm.WriteOString( sOut.makeStringAndClear() ).WriteCharPtr( SAL_NEWLINE_STRING ); + } + + if( pSBModule ) + { + sOut.append("' " OOO_STRING_SVTOOLS_HTML_SB_module " " + + OUStringToOString(*pSBModule, RTL_TEXTENCODING_UTF8)); + rStrm.WriteOString( sOut.makeStringAndClear() ).WriteCharPtr( SAL_NEWLINE_STRING ); + } + } + + if( !rSource.empty() ) + { + // we write the module in ANSI-charset, but with + // the system new line. + const OString sSource(OUStringToOString(rSource, RTL_TEXTENCODING_UTF8)); + rStrm.WriteOString( sSource ).WriteCharPtr( SAL_NEWLINE_STRING ); + } + rStrm.WriteCharPtr( SAL_NEWLINE_STRING ); + + if( JAVASCRIPT != eScriptType ) + { + // MIB/MM: if it is not StarBasic, a // could be wrong. + // As the comment is removed during reading, it is not helping us... + rStrm.WriteCharPtr( STARBASIC == eScriptType ? "' -->" : "// -->" ) + .WriteCharPtr( SAL_NEWLINE_STRING ); + } + } + + HTMLOutFuncs::Out_AsciiTag( rStrm, OOO_STRING_SVTOOLS_HTML_script, false ); + + return rStrm; +} + + +SvStream& HTMLOutFuncs::Out_Events( SvStream& rStrm, + const SvxMacroTableDtor& rMacroTable, + const HTMLOutEvent *pEventTable, + bool bOutStarBasic, + OUString *pNonConvertableChars ) +{ + sal_uInt16 i=0; + while( pEventTable[i].pBasicName || pEventTable[i].pJavaName ) + { + const SvxMacro *pMacro = + rMacroTable.Get( pEventTable[i].nEvent ); + + if( pMacro && pMacro->HasMacro() && + ( JAVASCRIPT == pMacro->GetScriptType() || bOutStarBasic )) + { + const char *pStr = STARBASIC == pMacro->GetScriptType() + ? pEventTable[i].pBasicName + : pEventTable[i].pJavaName; + + if( pStr ) + { + OString sOut = OString::Concat(" ") + pStr + "=\""; + rStrm.WriteOString( sOut ); + + Out_String( rStrm, pMacro->GetMacName(), pNonConvertableChars ).WriteChar( '\"' ); + } + } + i++; + } + + return rStrm; +} + +OString HTMLOutFuncs::CreateTableDataOptionsValNum( + bool bValue, + double fVal, sal_uInt32 nFormat, SvNumberFormatter& rFormatter, + OUString* pNonConvertableChars) +{ + OStringBuffer aStrTD; + + if ( bValue ) + { + // printf / scanf is not precise enough + OUString aValStr; + rFormatter.GetInputLineString( fVal, 0, aValStr ); + OString sTmp(OUStringToOString(aValStr, RTL_TEXTENCODING_UTF8)); + aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_SDval "=\"" + + sTmp + "\""); + } + if ( bValue || nFormat ) + { + aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_SDnum "=\"" + + OString::number(static_cast<sal_uInt16>( + Application::GetSettings().GetLanguageTag().getLanguageType())) + + ";"); // Language for Format 0 + if ( nFormat ) + { + OString aNumStr; + LanguageType nLang; + const SvNumberformat* pFormatEntry = rFormatter.GetEntry( nFormat ); + if ( pFormatEntry ) + { + aNumStr = ConvertStringToHTML( pFormatEntry->GetFormatstring(), + pNonConvertableChars ); + nLang = pFormatEntry->GetLanguage(); + } + else + nLang = LANGUAGE_SYSTEM; + aStrTD.append(static_cast<sal_Int32>(static_cast<sal_uInt16>(nLang))).append(';'). + append(aNumStr); + } + aStrTD.append('\"'); + } + return aStrTD.makeStringAndClear(); +} + +bool HTMLOutFuncs::PrivateURLToInternalImg( OUString& rURL ) +{ + if( rURL.startsWith(OOO_STRING_SVTOOLS_HTML_private_image) ) + { + rURL = rURL.copy( strlen(OOO_STRING_SVTOOLS_HTML_private_image) ); + return true; + } + + return false; +} + +void HtmlWriterHelper::applyColor(HtmlWriter& rHtmlWriter, std::string_view aAttributeName, const Color& rColor) +{ + OStringBuffer sBuffer; + + if( rColor == COL_AUTO ) + { + sBuffer.append("#000000"); + } + else + { + sBuffer.append('#'); + std::ostringstream sStringStream; + sStringStream + << std::right + << std::setfill('0') + << std::setw(6) + << std::hex + << sal_uInt32(rColor.GetRGBColor()); + sBuffer.append(sStringStream.str().c_str()); + } + + rHtmlWriter.attribute(aAttributeName, sBuffer.makeStringAndClear()); +} + + +void HtmlWriterHelper::applyEvents(HtmlWriter& rHtmlWriter, const SvxMacroTableDtor& rMacroTable, const HTMLOutEvent* pEventTable, bool bOutStarBasic) +{ + sal_uInt16 i = 0; + while (pEventTable[i].pBasicName || pEventTable[i].pJavaName) + { + const SvxMacro* pMacro = rMacroTable.Get(pEventTable[i].nEvent); + + if (pMacro && pMacro->HasMacro() && (JAVASCRIPT == pMacro->GetScriptType() || bOutStarBasic)) + { + const char* pAttributeName = nullptr; + if (STARBASIC == pMacro->GetScriptType()) + pAttributeName = pEventTable[i].pBasicName; + else + pAttributeName = pEventTable[i].pJavaName; + + if (pAttributeName) + { + rHtmlWriter.attribute(pAttributeName, OUStringToOString(pMacro->GetMacName(), RTL_TEXTENCODING_UTF8)); + } + } + i++; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/svhtml/htmlsupp.cxx b/svtools/source/svhtml/htmlsupp.cxx new file mode 100644 index 000000000..a418d61eb --- /dev/null +++ b/svtools/source/svhtml/htmlsupp.cxx @@ -0,0 +1,159 @@ +/* -*- 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 <svtools/parhtml.hxx> +#include <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> +#include <tools/urlobj.hxx> + +// Table for converting option values into strings +HTMLOptionEnum<HTMLScriptLanguage> const aScriptLangOptEnums[] = +{ + { OOO_STRING_SVTOOLS_HTML_LG_starbasic, HTMLScriptLanguage::StarBasic }, + { OOO_STRING_SVTOOLS_HTML_LG_javascript, HTMLScriptLanguage::JavaScript }, + { OOO_STRING_SVTOOLS_HTML_LG_javascript11, HTMLScriptLanguage::JavaScript }, + { OOO_STRING_SVTOOLS_HTML_LG_livescript, HTMLScriptLanguage::JavaScript }, + { nullptr, HTMLScriptLanguage(0) } +}; + +void HTMLParser::ParseScriptOptions( OUString& rLangString, std::u16string_view rBaseURL, + HTMLScriptLanguage& rLang, + OUString& rSrc, + OUString& rLibrary, + OUString& rModule ) +{ + const HTMLOptions& aScriptOptions = GetOptions(); + + rLangString.clear(); + rLang = HTMLScriptLanguage::JavaScript; + rSrc.clear(); + rLibrary.clear(); + rModule.clear(); + + for( size_t i = aScriptOptions.size(); i; ) + { + const HTMLOption& aOption = aScriptOptions[--i]; + switch( aOption.GetToken() ) + { + case HtmlOptionId::LANGUAGE: + { + rLangString = aOption.GetString(); + HTMLScriptLanguage nLang; + if( aOption.GetEnum( nLang, aScriptLangOptEnums ) ) + rLang = nLang; + else + rLang = HTMLScriptLanguage::Unknown; + } + break; + + case HtmlOptionId::SRC: + rSrc = INetURLObject::GetAbsURL( rBaseURL, aOption.GetString() ); + break; + case HtmlOptionId::SDLIBRARY: + rLibrary = aOption.GetString(); + break; + + case HtmlOptionId::SDMODULE: + rModule = aOption.GetString(); + break; + default: break; + } + } +} + +void HTMLParser::RemoveSGMLComment( OUString &rString ) +{ + sal_Unicode c = 0; + sal_Int32 idx = 0; + while (idx < rString.getLength()) + { + c = rString[idx]; + if (!( c==' ' || c=='\t' || c=='\r' || c=='\n' ) ) + break; + idx++; + } + if (idx) + rString = rString.copy( idx ); + + idx = rString.getLength() - 1; + while (idx > 0) + // Can never get to 0 because that would mean the string contains only whitespace, and the first + // loop would already have removed all of those. + { + c = rString[idx]; + if (!( c==' ' || c=='\t' || c=='\r' || c=='\n' ) ) + break; + idx--; + } + if (idx != rString.getLength() - 1) + rString = rString.copy( 0, idx + 1 ); + + // remove SGML comments + if( rString.startsWith( "<!--" ) ) + { + // the whole line + sal_Int32 nPos = 4; + while( nPos < rString.getLength() ) + { + c = rString[nPos]; + if (c == '\r' || c == '\n') + break; + ++nPos; + } + if( c == '\r' && nPos+1 < rString.getLength() && + '\n' == rString[nPos+1] ) + ++nPos; + else if( c != '\n' ) + nPos = 3; + ++nPos; + rString = rString.copy( nPos ); + } + + if( !rString.endsWith("-->") ) + return; + + rString = rString.copy( 0, rString.getLength()-3 ); + // "//" or "'", maybe preceding CR/LF + rString = comphelper::string::stripEnd(rString, ' '); + sal_Int32 nDel = 0, nLen = rString.getLength(); + if( nLen >= 2 && + rString.endsWith("//") ) + { + nDel = 2; + } + else if( nLen && '\'' == rString[nLen-1] ) + { + nDel = 1; + } + if( nDel && nLen >= nDel+1 ) + { + c = rString[nLen-(nDel+1)]; + if( '\r'==c || '\n'==c ) + { + nDel++; + if( '\n'==c && nLen >= nDel+1 && + '\r'==rString[nLen-(nDel+1)] ) + nDel++; + } + } + rString = rString.copy( 0, nLen-nDel ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/svhtml/parhtml.cxx b/svtools/source/svhtml/parhtml.cxx new file mode 100644 index 000000000..70d1da950 --- /dev/null +++ b/svtools/source/svhtml/parhtml.cxx @@ -0,0 +1,2200 @@ +/* -*- 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 <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <tools/stream.hxx> +#include <tools/debug.hxx> +#include <tools/color.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/character.hxx> +#include <rtl/tencinfo.h> +#include <sal/log.hxx> +#include <tools/tenccvt.hxx> +#include <tools/datetime.hxx> +#include <unotools/datetime.hxx> +#include <svl/inettype.hxx> +#include <svl/lngmisc.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> + +#include <svtools/parhtml.hxx> +#include <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> + +#include <utility> + +using namespace ::com::sun::star; + + +const sal_Int32 MAX_LEN( 1024 ); + +const sal_Int32 MAX_ENTITY_LEN( 8 ); + + +// Tables to convert option values into strings + +// <INPUT TYPE=xxx> +HTMLOptionEnum<HTMLInputType> const aInputTypeOptEnums[] = +{ + { OOO_STRING_SVTOOLS_HTML_IT_text, HTMLInputType::Text }, + { OOO_STRING_SVTOOLS_HTML_IT_password, HTMLInputType::Password }, + { OOO_STRING_SVTOOLS_HTML_IT_checkbox, HTMLInputType::Checkbox }, + { OOO_STRING_SVTOOLS_HTML_IT_radio, HTMLInputType::Radio }, + { OOO_STRING_SVTOOLS_HTML_IT_range, HTMLInputType::Range }, + { OOO_STRING_SVTOOLS_HTML_IT_scribble, HTMLInputType::Scribble }, + { OOO_STRING_SVTOOLS_HTML_IT_file, HTMLInputType::File }, + { OOO_STRING_SVTOOLS_HTML_IT_hidden, HTMLInputType::Hidden }, + { OOO_STRING_SVTOOLS_HTML_IT_submit, HTMLInputType::Submit }, + { OOO_STRING_SVTOOLS_HTML_IT_image, HTMLInputType::Image }, + { OOO_STRING_SVTOOLS_HTML_IT_reset, HTMLInputType::Reset }, + { OOO_STRING_SVTOOLS_HTML_IT_button, HTMLInputType::Button }, + { nullptr, HTMLInputType(0) } +}; + +// <TABLE FRAME=xxx> +HTMLOptionEnum<HTMLTableFrame> const aTableFrameOptEnums[] = +{ + { OOO_STRING_SVTOOLS_HTML_TF_void, HTMLTableFrame::Void }, + { OOO_STRING_SVTOOLS_HTML_TF_above, HTMLTableFrame::Above }, + { OOO_STRING_SVTOOLS_HTML_TF_below, HTMLTableFrame::Below }, + { OOO_STRING_SVTOOLS_HTML_TF_hsides, HTMLTableFrame::HSides }, + { OOO_STRING_SVTOOLS_HTML_TF_lhs, HTMLTableFrame::LHS }, + { OOO_STRING_SVTOOLS_HTML_TF_rhs, HTMLTableFrame::RHS }, + { OOO_STRING_SVTOOLS_HTML_TF_vsides, HTMLTableFrame::VSides }, + { OOO_STRING_SVTOOLS_HTML_TF_box, HTMLTableFrame::Box }, + { OOO_STRING_SVTOOLS_HTML_TF_border, HTMLTableFrame::Box }, + { nullptr, HTMLTableFrame(0) } +}; + +// <TABLE RULES=xxx> +HTMLOptionEnum<HTMLTableRules> const aTableRulesOptEnums[] = +{ + { OOO_STRING_SVTOOLS_HTML_TR_none, HTMLTableRules::NONE }, + { OOO_STRING_SVTOOLS_HTML_TR_groups, HTMLTableRules::Groups }, + { OOO_STRING_SVTOOLS_HTML_TR_rows, HTMLTableRules::Rows }, + { OOO_STRING_SVTOOLS_HTML_TR_cols, HTMLTableRules::Cols }, + { OOO_STRING_SVTOOLS_HTML_TR_all, HTMLTableRules::All }, + { nullptr, HTMLTableRules(0) } +}; + + +HTMLOption::HTMLOption( HtmlOptionId nTok, const OUString& rToken, + const OUString& rValue ) + : aValue(rValue) + , aToken(rToken) + , nToken( nTok ) +{ + DBG_ASSERT( nToken>=HtmlOptionId::BOOL_START && nToken<HtmlOptionId::END, + "HTMLOption: unknown token" ); +} + +sal_uInt32 HTMLOption::GetNumber() const +{ + DBG_ASSERT( (nToken>=HtmlOptionId::NUMBER_START && + nToken<HtmlOptionId::NUMBER_END) || + (nToken>=HtmlOptionId::CONTEXT_START && + nToken<HtmlOptionId::CONTEXT_END) || + nToken==HtmlOptionId::VALUE, + "GetNumber: Option not numerical" ); + OUString aTmp(comphelper::string::stripStart(aValue, ' ')); + sal_Int32 nTmp = aTmp.toInt32(); + return nTmp >= 0 ? static_cast<sal_uInt32>(nTmp) : 0; +} + +sal_Int32 HTMLOption::GetSNumber() const +{ + DBG_ASSERT( (nToken>=HtmlOptionId::NUMBER_START && nToken<HtmlOptionId::NUMBER_END) || + (nToken>=HtmlOptionId::CONTEXT_START && nToken<HtmlOptionId::CONTEXT_END), + "GetSNumber: Option not numerical" ); + OUString aTmp(comphelper::string::stripStart(aValue, ' ')); + return aTmp.toInt32(); +} + +void HTMLOption::GetNumbers( std::vector<sal_uInt32> &rNumbers ) const +{ + rNumbers.clear(); + + // This is a very simplified scanner: it only searches all + // numerals in the string. + bool bInNum = false; + sal_uInt32 nNum = 0; + for( sal_Int32 i=0; i<aValue.getLength(); i++ ) + { + sal_Unicode c = aValue[ i ]; + if( c>='0' && c<='9' ) + { + nNum *= 10; + nNum += (c - '0'); + bInNum = true; + } + else if( bInNum ) + { + rNumbers.push_back( nNum ); + bInNum = false; + nNum = 0; + } + } + if( bInNum ) + { + rNumbers.push_back( nNum ); + } +} + +void HTMLOption::GetColor( Color& rColor ) const +{ + DBG_ASSERT( (nToken>=HtmlOptionId::COLOR_START && nToken<HtmlOptionId::COLOR_END) || nToken==HtmlOptionId::SIZE, + "GetColor: Option is not a color." ); + + OUString aTmp(aValue.toAsciiLowerCase()); + sal_uInt32 nColor = SAL_MAX_UINT32; + if (!aTmp.isEmpty() && aTmp[0] != '#') + nColor = GetHTMLColor(aTmp); + + if( SAL_MAX_UINT32 == nColor ) + { + nColor = 0; + sal_Int32 nPos = 0; + for (sal_uInt32 i=0; i<6; ++i) + { + // Whatever Netscape does to get color values, + // at maximum three characters < '0' are ignored. + sal_Unicode c = nPos<aTmp.getLength() ? aTmp[ nPos++ ] : '0'; + if( c < '0' ) + { + c = nPos<aTmp.getLength() ? aTmp[nPos++] : '0'; + if( c < '0' ) + c = nPos<aTmp.getLength() ? aTmp[nPos++] : '0'; + } + nColor *= 16; + if( c >= '0' && c <= '9' ) + nColor += (c - '0'); + else if( c >= 'a' && c <= 'f' ) + nColor += (c + 0xa - 'a'); + } + } + + rColor.SetRed( static_cast<sal_uInt8>((nColor & 0x00ff0000) >> 16) ); + rColor.SetGreen( static_cast<sal_uInt8>((nColor & 0x0000ff00) >> 8)); + rColor.SetBlue( static_cast<sal_uInt8>(nColor & 0x000000ff) ); +} + +HTMLInputType HTMLOption::GetInputType() const +{ + DBG_ASSERT( nToken==HtmlOptionId::TYPE, "GetInputType: Option not TYPE" ); + return GetEnum( aInputTypeOptEnums, HTMLInputType::Text ); +} + +HTMLTableFrame HTMLOption::GetTableFrame() const +{ + DBG_ASSERT( nToken==HtmlOptionId::FRAME, "GetTableFrame: Option not FRAME" ); + return GetEnum( aTableFrameOptEnums ); +} + +HTMLTableRules HTMLOption::GetTableRules() const +{ + DBG_ASSERT( nToken==HtmlOptionId::RULES, "GetTableRules: Option not RULES" ); + return GetEnum( aTableRulesOptEnums ); +} + +HTMLParser::HTMLParser( SvStream& rIn, bool bReadNewDoc ) : + SvParser<HtmlTokenId>( rIn ), + bNewDoc(bReadNewDoc), + bIsInHeader(true), + bReadListing(false), + bReadXMP(false), + bReadPRE(false), + bReadTextArea(false), + bReadScript(false), + bReadStyle(false), + bEndTokenFound(false), + bPre_IgnoreNewPara(false), + bReadNextChar(false), + bReadComment(false), + nPre_LinePos(0), + mnPendingOffToken(HtmlTokenId::NONE) +{ + //#i76649, default to UTF-8 for HTML unless we know differently + SetSrcEncoding(RTL_TEXTENCODING_UTF8); +} + +HTMLParser::~HTMLParser() +{ +} + +void HTMLParser::SetNamespace(std::u16string_view rNamespace) +{ + // Convert namespace alias to a prefix. + maNamespace = OUString::Concat(rNamespace) + ":"; +} + +namespace +{ + class RefGuard + { + private: + HTMLParser& m_rParser; + public: + RefGuard(HTMLParser& rParser) + : m_rParser(rParser) + { + m_rParser.AddFirstRef(); + } + + ~RefGuard() + { + if (m_rParser.GetStatus() != SvParserState::Pending) + m_rParser.ReleaseRef(); // Parser not needed anymore + } + }; +} + +SvParserState HTMLParser::CallParser() +{ + eState = SvParserState::Working; + nNextCh = GetNextChar(); + SaveState( HtmlTokenId::NONE ); + + nPre_LinePos = 0; + bPre_IgnoreNewPara = false; + + RefGuard aRefGuard(*this); + + Continue( HtmlTokenId::NONE ); + + return eState; +} + +void HTMLParser::Continue( HtmlTokenId nToken ) +{ + if( nToken == HtmlTokenId::NONE ) + nToken = GetNextToken(); + + while( IsParserWorking() ) + { + SaveState( nToken ); + nToken = FilterToken( nToken ); + + if( nToken != HtmlTokenId::NONE ) + NextToken( nToken ); + + if( IsParserWorking() ) + SaveState( HtmlTokenId::NONE ); // continue with new token + + nToken = GetNextToken(); + } +} + +HtmlTokenId HTMLParser::FilterToken( HtmlTokenId nToken ) +{ + switch( nToken ) + { + case HtmlTokenId(EOF): + nToken = HtmlTokenId::NONE; + break; // don't pass + + case HtmlTokenId::HEAD_OFF: + bIsInHeader = false; + break; + + case HtmlTokenId::HEAD_ON: + bIsInHeader = true; + break; + + case HtmlTokenId::BODY_ON: + bIsInHeader = false; + break; + + case HtmlTokenId::FRAMESET_ON: + bIsInHeader = false; + break; + + case HtmlTokenId::BODY_OFF: + bReadPRE = bReadListing = bReadXMP = false; + break; + + case HtmlTokenId::HTML_OFF: + nToken = HtmlTokenId::NONE; + bReadPRE = bReadListing = bReadXMP = false; + break; // HtmlTokenId::ON hasn't been passed either ! + + case HtmlTokenId::PREFORMTXT_ON: + StartPRE(); + break; + + case HtmlTokenId::PREFORMTXT_OFF: + FinishPRE(); + break; + + case HtmlTokenId::LISTING_ON: + StartListing(); + break; + + case HtmlTokenId::LISTING_OFF: + FinishListing(); + break; + + case HtmlTokenId::XMP_ON: + StartXMP(); + break; + + case HtmlTokenId::XMP_OFF: + FinishXMP(); + break; + + default: + if( bReadPRE ) + nToken = FilterPRE( nToken ); + else if( bReadListing ) + nToken = FilterListing( nToken ); + else if( bReadXMP ) + nToken = FilterXMP( nToken ); + + break; + } + + return nToken; +} + +namespace { + +constexpr bool HTML_ISPRINTABLE(sal_Unicode c) { return c >= 32 && c != 127; } + +} + +HtmlTokenId HTMLParser::ScanText( const sal_Unicode cBreak ) +{ + OUStringBuffer sTmpBuffer( MAX_LEN ); + bool bContinue = true; + bool bEqSignFound = false; + sal_uInt32 cQuote = 0U; + + while( bContinue && IsParserWorking() ) + { + bool bNextCh = true; + switch( nNextCh ) + { + case '&': + bEqSignFound = false; + if( bReadXMP ) + sTmpBuffer.append( '&' ); + else + { + sal_uInt64 nStreamPos = rInput.Tell(); + sal_uInt32 nLinePos = GetLinePos(); + + sal_uInt32 cChar = 0U; + if( '#' == (nNextCh = GetNextChar()) ) + { + nNextCh = GetNextChar(); + const bool bIsHex( 'x' == nNextCh ); + const bool bIsDecOrHex( bIsHex || rtl::isAsciiDigit(nNextCh) ); + if ( bIsDecOrHex ) + { + if ( bIsHex ) + { + nNextCh = GetNextChar(); + while ( rtl::isAsciiHexDigit(nNextCh) ) + { + cChar = cChar * 16U + + ( nNextCh <= '9' + ? sal_uInt32( nNextCh - '0' ) + : ( nNextCh <= 'F' + ? sal_uInt32( nNextCh - 'A' + 10 ) + : sal_uInt32( nNextCh - 'a' + 10 ) ) ); + nNextCh = GetNextChar(); + } + } + else + { + do + { + cChar = cChar * 10U + sal_uInt32( nNextCh - '0'); + nNextCh = GetNextChar(); + } + while( rtl::isAsciiDigit(nNextCh) ); + } + + if( RTL_TEXTENCODING_DONTKNOW != eSrcEnc && + RTL_TEXTENCODING_UCS2 != eSrcEnc && + RTL_TEXTENCODING_UTF8 != eSrcEnc && + cChar < 256 ) + { + const sal_uInt32 convertFlags = + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_DEFAULT | + RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_DEFAULT | + RTL_TEXTTOUNICODE_FLAGS_INVALID_DEFAULT; + + char cEncodedChar = static_cast<char>(cChar); + cChar = OUString(&cEncodedChar, 1, eSrcEnc, convertFlags).toChar(); + if( 0U == cChar ) + { + // If the character could not be + // converted, because a conversion is not + // available, do no conversion at all. + cChar = cEncodedChar; + } + } + } + else + nNextCh = 0U; + + if (!rtl::isUnicodeCodePoint(cChar) + || (linguistic::IsControlChar(cChar) + && cChar != '\r' && cChar != '\n' && cChar != '\t')) + { + cChar = '?'; + } + } + else if( rtl::isAsciiAlpha( nNextCh ) ) + { + OUStringBuffer sEntityBuffer( MAX_ENTITY_LEN ); + sal_Int32 nPos = 0; + do + { + sEntityBuffer.appendUtf32( nNextCh ); + nPos++; + nNextCh = GetNextChar(); + } + while( nPos < MAX_ENTITY_LEN && rtl::isAsciiAlphanumeric( nNextCh ) && + !rInput.eof() ); + + if( IsParserWorking() && !rInput.eof() ) + { + OUString sEntity(sEntityBuffer.getStr(), nPos); + cChar = GetHTMLCharName( sEntity ); + + // not found ( == 0 ): plain text + // or a character which is inserted as attribute + if( 0U == cChar && ';' != nNextCh ) + { + DBG_ASSERT( rInput.Tell() - nStreamPos == + static_cast<sal_uInt64>(nPos+1)*GetCharSize(), + "UTF-8 is failing here" ); + for( sal_Int32 i = nPos-1; i>1; i-- ) + { + nNextCh = sEntityBuffer[i]; + sEntityBuffer.setLength( i ); + sEntity = OUString(sEntityBuffer.getStr(), i); + cChar = GetHTMLCharName( sEntity ); + if( cChar ) + { + rInput.SeekRel( -static_cast<sal_Int64> + (nPos-i)*GetCharSize() ); + nlLinePos -= sal_uInt32(nPos-i); + nPos = i; + ClearTxtConvContext(); + break; + } + } + } + + if( !cChar ) // unknown character? + { + // back in stream, insert '&' + // and restart with next character + sTmpBuffer.append( '&' ); + + DBG_ASSERT( rInput.Tell()-nStreamPos == + static_cast<sal_uInt64>(nPos+1)*GetCharSize(), + "Wrong stream position" ); + DBG_ASSERT( nlLinePos-nLinePos == + static_cast<sal_uInt32>(nPos+1), + "Wrong line position" ); + rInput.Seek( nStreamPos ); + nlLinePos = nLinePos; + ClearTxtConvContext(); + break; + } + + assert(cChar != 0); + + // 1 == Non Breaking Space + // 2 == SoftHyphen + + if (cChar == 1 || cChar == 2) + { + if( '>' == cBreak ) + { + // When reading the content of a tag we have + // to change it to ' ' or '-' + if( 1U == cChar ) + cChar = ' '; + else //2U + cChar = '-'; + } + else + { + // If not scanning a tag return token + aToken.append( sTmpBuffer ); + sTmpBuffer.setLength(0); + + if( !aToken.isEmpty() ) + { + // restart with character + nNextCh = '&'; + DBG_ASSERT( rInput.Tell()-nStreamPos == + static_cast<sal_uInt64>(nPos+1)*GetCharSize(), + "Wrong stream position" ); + DBG_ASSERT( nlLinePos-nLinePos == + static_cast<sal_uInt32>(nPos+1), + "Wrong line position" ); + rInput.Seek( nStreamPos ); + nlLinePos = nLinePos; + ClearTxtConvContext(); + return HtmlTokenId::TEXTTOKEN; + } + + // Hack: _GetNextChar shall not read the + // next character + if( ';' != nNextCh ) + aToken.append( " " ); + if( 1U == cChar ) + return HtmlTokenId::NONBREAKSPACE; + else //2U + return HtmlTokenId::SOFTHYPH; + } + } + } + else + nNextCh = 0U; + } + // &{...};-JavaScript-Macros are not supported any longer. + else if( IsParserWorking() ) + { + sTmpBuffer.append( '&' ); + bNextCh = false; + break; + } + + bNextCh = (';' == nNextCh); + if( cBreak=='>' && (cChar=='\\' || cChar=='\'' || + cChar=='\"' || cChar==' ') ) + { + // ' and " have to be escaped within tags to separate + // them from ' and " enclosing options. + // \ has to be escaped as well. + // Space is protected because it's not a delimiter between + // options. + sTmpBuffer.append( '\\' ); + } + if( IsParserWorking() ) + { + if( cChar ) + sTmpBuffer.appendUtf32( cChar ); + } + else if( SvParserState::Pending==eState && '>'!=cBreak ) + { + // Restart with '&', the remainder is returned as + // text token. + if( !aToken.isEmpty() || !sTmpBuffer.isEmpty() ) + { + // _GetNextChar() returns the previous text and + // during the next execution a new character is read. + // Thus we have to position in front of the '&'. + nNextCh = 0U; + rInput.Seek( nStreamPos - GetCharSize() ); + nlLinePos = nLinePos-1; + ClearTxtConvContext(); + bReadNextChar = true; + } + bNextCh = false; + } + } + break; + case '=': + if( '>'==cBreak && !cQuote ) + bEqSignFound = true; + sTmpBuffer.appendUtf32( nNextCh ); + break; + + case '\\': + if( '>'==cBreak ) + { + // mark within tags + sTmpBuffer.append( '\\' ); + } + sTmpBuffer.append( '\\' ); + break; + + case '\"': + case '\'': + if( '>'==cBreak ) + { + if( bEqSignFound ) + cQuote = nNextCh; + else if( cQuote && (cQuote==nNextCh ) ) + cQuote = 0U; + } + sTmpBuffer.appendUtf32( nNextCh ); + bEqSignFound = false; + break; + + case sal_Unicode(EOF): + if( rInput.eof() ) + { + bContinue = false; + } + // else: ignore, not a valid code point + break; + + case '<': + bEqSignFound = false; + if( '>'==cBreak ) + sTmpBuffer.appendUtf32( nNextCh ); + else + bContinue = false; // break, string is together + break; + + case '\f': + if( '>' == cBreak ) + { + // If scanning options treat it like a space, ... + sTmpBuffer.append( ' ' ); + } + else + { + // otherwise it's a separate token. + bContinue = false; + } + break; + + case '\r': + case '\n': + if( '>'==cBreak ) + { + // cr/lf in tag is handled in GetNextToken_() + sTmpBuffer.appendUtf32( nNextCh ); + break; + } + else if( bReadListing || bReadXMP || bReadPRE || bReadTextArea ) + { + bContinue = false; + break; + } + // Reduce sequence of CR/LF/BLANK/TAB to a single blank + [[fallthrough]]; + case '\t': + if( '\t'==nNextCh && bReadPRE && '>'!=cBreak ) + { + // Pass Tabs up in <PRE> + bContinue = false; + break; + } + [[fallthrough]]; + case '\x0b': + if( '\x0b'==nNextCh && (bReadPRE || bReadXMP ||bReadListing) && + '>'!=cBreak ) + { + break; + } + nNextCh = ' '; + [[fallthrough]]; + case ' ': + sTmpBuffer.appendUtf32( nNextCh ); + if( '>'!=cBreak && (!bReadListing && !bReadXMP && + !bReadPRE && !bReadTextArea) ) + { + // Reduce sequences of Blanks/Tabs/CR/LF to a single blank + do { + nNextCh = GetNextChar(); + if( sal_Unicode(EOF) == nNextCh && rInput.eof() ) + { + if( !aToken.isEmpty() || sTmpBuffer.getLength() > 1 ) + { + // Have seen s.th. aside from blanks? + aToken.append( sTmpBuffer ); + sTmpBuffer.setLength(0); + return HtmlTokenId::TEXTTOKEN; + } + else + // Only read blanks: no text must be returned + // and GetNextToken_ has to read until EOF + return HtmlTokenId::NONE; + } + } while ( ' ' == nNextCh || '\t' == nNextCh || + '\r' == nNextCh || '\n' == nNextCh || + '\x0b' == nNextCh ); + bNextCh = false; + } + break; + + default: + bEqSignFound = false; + if (nNextCh == cBreak && !cQuote) + bContinue = false; + else + { + do { + if (!linguistic::IsControlChar(nNextCh)) + { + // All remaining characters make their way into the text. + sTmpBuffer.appendUtf32( nNextCh ); + } + + nNextCh = GetNextChar(); + if( ( sal_Unicode(EOF) == nNextCh && rInput.eof() ) || + !IsParserWorking() ) + { + if( !sTmpBuffer.isEmpty() ) + aToken.append( sTmpBuffer ); + return HtmlTokenId::TEXTTOKEN; + } + } while( rtl::isAsciiAlpha( nNextCh ) || rtl::isAsciiDigit( nNextCh ) ); + bNextCh = false; + } + } + + if( bContinue && bNextCh ) + nNextCh = GetNextChar(); + } + + if( !sTmpBuffer.isEmpty() ) + aToken.append( sTmpBuffer ); + + return HtmlTokenId::TEXTTOKEN; +} + +HtmlTokenId HTMLParser::GetNextRawToken() +{ + OUStringBuffer sTmpBuffer( MAX_LEN ); + + if( bEndTokenFound ) + { + // During the last execution we already found the end token, + // thus we don't have to search it again. + bReadScript = false; + bReadStyle = false; + aEndToken.clear(); + bEndTokenFound = false; + + return HtmlTokenId::NONE; + } + + // Default return value: HtmlTokenId::RAWDATA + bool bContinue = true; + HtmlTokenId nToken = HtmlTokenId::RAWDATA; + SaveState( HtmlTokenId::NONE ); + while( bContinue && IsParserWorking() ) + { + bool bNextCh = true; + switch( nNextCh ) + { + case '<': + { + // Maybe we've reached the end. + + // Save what we have read previously... + aToken.append( sTmpBuffer ); + sTmpBuffer.setLength(0); + + // and remember position in stream. + sal_uInt64 nStreamPos = rInput.Tell(); + sal_uInt32 nLineNr = GetLineNr(); + sal_uInt32 nLinePos = GetLinePos(); + + // Start of an end token? + bool bOffState = false; + if( '/' == (nNextCh = GetNextChar()) ) + { + bOffState = true; + nNextCh = GetNextChar(); + } + else if( '!' == nNextCh ) + { + sTmpBuffer.appendUtf32( nNextCh ); + nNextCh = GetNextChar(); + } + + // Read following letters + while( (rtl::isAsciiAlpha(nNextCh) || '-'==nNextCh) && + IsParserWorking() && sTmpBuffer.getLength() < MAX_LEN ) + { + sTmpBuffer.appendUtf32( nNextCh ); + nNextCh = GetNextChar(); + } + + OUString aTok( sTmpBuffer.toString() ); + aTok = aTok.toAsciiLowerCase(); + bool bDone = false; + if( bReadScript || !aEndToken.isEmpty() ) + { + if( !bReadComment ) + { + if( aTok.startsWith( OOO_STRING_SVTOOLS_HTML_comment ) ) + { + bReadComment = true; + } + else + { + // A script has to end with "</SCRIPT>". But + // ">" is optional for security reasons + bDone = bOffState && + ( bReadScript + ? aTok == OOO_STRING_SVTOOLS_HTML_script + : aTok == aEndToken ); + } + } + if( bReadComment && '>'==nNextCh && aTok.endsWith( "--" ) ) + { + // End of comment of style <!-----> + bReadComment = false; + } + } + else + { + // Style sheets can be closed by </STYLE>, </HEAD> or <BODY> + if( bOffState ) + bDone = aTok == OOO_STRING_SVTOOLS_HTML_style || + aTok == OOO_STRING_SVTOOLS_HTML_head; + else + bDone = aTok == OOO_STRING_SVTOOLS_HTML_body; + } + + if( bDone ) + { + // Done! Return the previously read string (if requested) + // and continue. + + bContinue = false; + + // nToken==0 means, GetNextToken_ continues to read + if( aToken.isEmpty() && (bReadStyle || bReadScript) ) + { + // Immediately close environment (or context?) + // and parse the end token + bReadScript = false; + bReadStyle = false; + aEndToken.clear(); + nToken = HtmlTokenId::NONE; + } + else + { + // Keep bReadScript/bReadStyle alive + // and parse end token during next execution + bEndTokenFound = true; + } + + // Move backwards in stream to '<' + rInput.Seek( nStreamPos ); + SetLineNr( nLineNr ); + SetLinePos( nLinePos ); + ClearTxtConvContext(); + nNextCh = '<'; + + // Don't append string to token. + sTmpBuffer.setLength( 0 ); + } + else + { + // remember "</" , everything else we find in the buffer + aToken.append( "<" ); + if( bOffState ) + aToken.append( "/" ); + + bNextCh = false; + } + } + break; + case '-': + sTmpBuffer.appendUtf32( nNextCh ); + if( bReadComment ) + { + bool bTwoMinus = false; + nNextCh = GetNextChar(); + while( '-' == nNextCh && IsParserWorking() ) + { + bTwoMinus = true; + sTmpBuffer.appendUtf32( nNextCh ); + nNextCh = GetNextChar(); + } + + if( '>' == nNextCh && IsParserWorking() && bTwoMinus ) + bReadComment = false; + + bNextCh = false; + } + break; + + case '\r': + // \r\n? closes the current text token (even if it's empty) + nNextCh = GetNextChar(); + if( nNextCh=='\n' ) + nNextCh = GetNextChar(); + bContinue = false; + break; + case '\n': + // \n closes the current text token (even if it's empty) + nNextCh = GetNextChar(); + bContinue = false; + break; + case sal_Unicode(EOF): + // eof closes the current text token and behaves like having read + // an end token + if( rInput.eof() ) + { + bContinue = false; + if( !aToken.isEmpty() || !sTmpBuffer.isEmpty() ) + { + bEndTokenFound = true; + } + else + { + bReadScript = false; + bReadStyle = false; + aEndToken.clear(); + nToken = HtmlTokenId::NONE; + } + } + break; + default: + if (!linguistic::IsControlChar(nNextCh) || nNextCh == '\t') + { + // all remaining characters are appended to the buffer + sTmpBuffer.appendUtf32( nNextCh ); + } + break; + } + + if( !bContinue && !sTmpBuffer.isEmpty() ) + { + aToken.append( sTmpBuffer ); + sTmpBuffer.setLength(0); + } + + if( bContinue && bNextCh ) + nNextCh = GetNextChar(); + } + + if( IsParserWorking() ) + SaveState( HtmlTokenId::NONE ); + else + nToken = HtmlTokenId::NONE; + + return nToken; +} + +// Scan next token +HtmlTokenId HTMLParser::GetNextToken_() +{ + HtmlTokenId nRet = HtmlTokenId::NONE; + sSaveToken.clear(); + + if (mnPendingOffToken != HtmlTokenId::NONE) + { + // HtmlTokenId::<TOKEN>_OFF generated for HtmlTokenId::<TOKEN>_ON + nRet = mnPendingOffToken; + mnPendingOffToken = HtmlTokenId::NONE; + aToken.setLength( 0 ); + return nRet; + } + + // Delete options + maOptions.clear(); + + if( !IsParserWorking() ) // Don't continue if already an error occurred + return HtmlTokenId::NONE; + + bool bReadNextCharSave = bReadNextChar; + if( bReadNextChar ) + { + DBG_ASSERT( !bEndTokenFound, + "Read a character despite </SCRIPT> was read?" ); + nNextCh = GetNextChar(); + if( !IsParserWorking() ) // Don't continue if already an error occurred + return HtmlTokenId::NONE; + bReadNextChar = false; + } + + if( bReadScript || bReadStyle || !aEndToken.isEmpty() ) + { + nRet = GetNextRawToken(); + if( nRet != HtmlTokenId::NONE || !IsParserWorking() ) + return nRet; + } + + do { + bool bNextCh = true; + switch( nNextCh ) + { + case '<': + { + sal_uInt64 nStreamPos = rInput.Tell(); + sal_uInt32 nLineNr = GetLineNr(); + sal_uInt32 nLinePos = GetLinePos(); + + bool bOffState = false; + if( '/' == (nNextCh = GetNextChar()) ) + { + bOffState = true; + nNextCh = GetNextChar(); + } + // Assume '<?' is a start of an XML declaration, ignore it. + if (rtl::isAsciiAlpha(nNextCh) || nNextCh == '!' || nNextCh == '?') + { + OUStringBuffer sTmpBuffer; + do { + sTmpBuffer.appendUtf32( nNextCh ); + nNextCh = GetNextChar(); + if (std::u16string_view(sTmpBuffer) == u"![CDATA[") + { + break; + } + } while( '>' != nNextCh && '/' != nNextCh && !rtl::isAsciiWhiteSpace( nNextCh ) && + !linguistic::IsControlChar(nNextCh) && + IsParserWorking() && !rInput.eof() ); + + if( !sTmpBuffer.isEmpty() ) + { + aToken.append( sTmpBuffer ); + sTmpBuffer.setLength(0); + } + + // Skip blanks + while( rtl::isAsciiWhiteSpace( nNextCh ) && IsParserWorking() ) + nNextCh = GetNextChar(); + + if( !IsParserWorking() ) + { + if( SvParserState::Pending == eState ) + bReadNextChar = bReadNextCharSave; + break; + } + + // Search token in table: + sSaveToken = aToken; + aToken = aToken.toString().toAsciiLowerCase(); + + if (!maNamespace.isEmpty() && o3tl::starts_with(aToken, maNamespace)) + aToken.remove( 0, maNamespace.getLength()); + + if( HtmlTokenId::NONE == (nRet = GetHTMLToken( aToken )) ) + // Unknown control + nRet = HtmlTokenId::UNKNOWNCONTROL_ON; + + // If it's a token which can be switched off... + if( bOffState ) + { + if( nRet >= HtmlTokenId::ONOFF_START ) + { + // and there is an off token, return off token instead + nRet = static_cast<HtmlTokenId>(static_cast<int>(nRet) + 1); + } + else if( HtmlTokenId::LINEBREAK!=nRet || !maNamespace.isEmpty()) + { + // and there is no off token, return unknown token. + // (except for </BR>, that is treated like <BR>) + // No exception for XHTML, though. + nRet = HtmlTokenId::UNKNOWNCONTROL_OFF; + } + } + + if( nRet == HtmlTokenId::COMMENT ) + { + // fix: due to being case sensitive use sSaveToken as start of comment + // and append a blank. + aToken = sSaveToken; + if( '>'!=nNextCh ) + aToken.append( " " ); + sal_uInt64 nCStreamPos = 0; + sal_uInt32 nCLineNr = 0; + sal_uInt32 nCLinePos = 0; + sal_Int32 nCStrLen = 0; + + bool bDone = false; + // Read until closing -->. If not found restart at first > + sTmpBuffer = aToken; + while( !bDone && !rInput.eof() && IsParserWorking() ) + { + if( '>'==nNextCh ) + { + if( !nCStreamPos ) + { + nCStreamPos = rInput.Tell(); + nCStrLen = sTmpBuffer.getLength(); + nCLineNr = GetLineNr(); + nCLinePos = GetLinePos(); + } + bDone = sTmpBuffer.getLength() >= 2 && sTmpBuffer[sTmpBuffer.getLength() - 2] == '-' && sTmpBuffer[sTmpBuffer.getLength() - 1] == '-'; + if( !bDone ) + sTmpBuffer.appendUtf32(nNextCh); + } + else if (!linguistic::IsControlChar(nNextCh) + || nNextCh == '\r' || nNextCh == '\n' || nNextCh == '\t') + { + sTmpBuffer.appendUtf32(nNextCh); + } + if( !bDone ) + nNextCh = GetNextChar(); + } + aToken = sTmpBuffer.makeStringAndClear(); + if( !bDone && IsParserWorking() && nCStreamPos ) + { + rInput.Seek( nCStreamPos ); + SetLineNr( nCLineNr ); + SetLinePos( nCLinePos ); + ClearTxtConvContext(); + aToken.truncate(nCStrLen); + nNextCh = '>'; + } + } + else if (nRet == HtmlTokenId::CDATA) + { + // Read until the closing ]]>. + bool bDone = false; + while (!bDone && !rInput.eof() && IsParserWorking()) + { + if (nNextCh == '>') + { + if (sTmpBuffer.getLength() >= 2) + { + bDone = sTmpBuffer[sTmpBuffer.getLength() - 2] == ']' + && sTmpBuffer[sTmpBuffer.getLength() - 1] == ']'; + if (bDone) + { + // Ignore ]] at the end. + sTmpBuffer.setLength(sTmpBuffer.getLength() - 2); + } + } + if (!bDone) + { + sTmpBuffer.appendUtf32(nNextCh); + } + } + else if (!linguistic::IsControlChar(nNextCh)) + { + sTmpBuffer.appendUtf32(nNextCh); + } + if (!bDone) + { + nNextCh = GetNextChar(); + } + } + aToken = sTmpBuffer; + sTmpBuffer.setLength(0); + } + else + { + // TokenString not needed anymore + aToken.setLength( 0 ); + } + + // Read until closing '>' + if( '>' != nNextCh && IsParserWorking() ) + { + ScanText( '>' ); + + // fdo#34666 fdo#36080 fdo#36390: closing "/>"?: + // generate pending HtmlTokenId::<TOKEN>_OFF for HtmlTokenId::<TOKEN>_ON + // Do not convert this to a single HtmlTokenId::<TOKEN>_OFF + // which lead to fdo#56772. + if ((nRet >= HtmlTokenId::ONOFF_START) && o3tl::ends_with(aToken, u"/")) + { + mnPendingOffToken = static_cast<HtmlTokenId>(static_cast<int>(nRet) + 1); // HtmlTokenId::<TOKEN>_ON -> HtmlTokenId::<TOKEN>_OFF + aToken.setLength( aToken.getLength()-1 ); // remove trailing '/' + } + if( sal_Unicode(EOF) == nNextCh && rInput.eof() ) + { + // Move back in front of < and restart there. + // Return < as text. + rInput.Seek( nStreamPos ); + SetLineNr( nLineNr ); + SetLinePos( nLinePos ); + ClearTxtConvContext(); + + aToken = "<"; + nRet = HtmlTokenId::TEXTTOKEN; + nNextCh = GetNextChar(); + bNextCh = false; + break; + } + } + if( SvParserState::Pending == eState ) + bReadNextChar = bReadNextCharSave; + } + else + { + if( bOffState ) + { + // simply throw away everything + ScanText( '>' ); + if( sal_Unicode(EOF) == nNextCh && rInput.eof() ) + { + // Move back in front of < and restart there. + // Return < as text. + rInput.Seek( nStreamPos ); + SetLineNr( nLineNr ); + SetLinePos( nLinePos ); + ClearTxtConvContext(); + + aToken = "<"; + nRet = HtmlTokenId::TEXTTOKEN; + nNextCh = GetNextChar(); + bNextCh = false; + break; + } + if( SvParserState::Pending == eState ) + bReadNextChar = bReadNextCharSave; + aToken.setLength( 0 ); + } + else if( '%' == nNextCh ) + { + nRet = HtmlTokenId::UNKNOWNCONTROL_ON; + + sal_uInt64 nCStreamPos = rInput.Tell(); + sal_uInt32 nCLineNr = GetLineNr(), nCLinePos = GetLinePos(); + + bool bDone = false; + // Read until closing %>. If not found restart at first >. + sal_Unicode nLastTokenChar = !aToken.isEmpty() ? aToken[aToken.getLength() - 1] : 0; + OUStringBuffer aTmpBuffer(aToken); + while( !bDone && !rInput.eof() && IsParserWorking() ) + { + bDone = '>'==nNextCh && nLastTokenChar == '%'; + if( !bDone ) + { + aTmpBuffer.appendUtf32(nNextCh); + nLastTokenChar = aTmpBuffer[aTmpBuffer.getLength() - 1]; + nNextCh = GetNextChar(); + } + } + if( !bDone && IsParserWorking() ) + { + rInput.Seek( nCStreamPos ); + SetLineNr( nCLineNr ); + SetLinePos( nCLinePos ); + ClearTxtConvContext(); + aToken = "<%"; + nRet = HtmlTokenId::TEXTTOKEN; + break; + } + aToken = aTmpBuffer.makeStringAndClear(); + if( IsParserWorking() ) + { + sSaveToken = aToken; + aToken.setLength( 0 ); + } + } + else + { + aToken = "<"; + nRet = HtmlTokenId::TEXTTOKEN; + bNextCh = false; + break; + } + } + + if( IsParserWorking() ) + { + bNextCh = '>' == nNextCh; + switch( nRet ) + { + case HtmlTokenId::TEXTAREA_ON: + bReadTextArea = true; + break; + case HtmlTokenId::TEXTAREA_OFF: + bReadTextArea = false; + break; + case HtmlTokenId::SCRIPT_ON: + if( !bReadTextArea ) + bReadScript = true; + break; + case HtmlTokenId::SCRIPT_OFF: + if( !bReadTextArea ) + { + bReadScript = false; + // JavaScript might modify the stream, + // thus the last character has to be read again. + bReadNextChar = true; + bNextCh = false; + } + break; + + case HtmlTokenId::STYLE_ON: + bReadStyle = true; + break; + case HtmlTokenId::STYLE_OFF: + bReadStyle = false; + break; + default: break; + } + } + } + break; + + case sal_Unicode(EOF): + if( rInput.eof() ) + { + eState = SvParserState::Accepted; + nRet = HtmlTokenId(nNextCh); + } + else + { + // Read normal text. + goto scan_text; + } + break; + + case '\f': + // form feeds are passed upwards separately + nRet = HtmlTokenId::LINEFEEDCHAR; // !!! should be FORMFEEDCHAR + break; + + case '\n': + case '\r': + if( bReadListing || bReadXMP || bReadPRE || bReadTextArea ) + { + sal_Unicode c = GetNextChar(); + if( ( '\n' != nNextCh || '\r' != c ) && + ( '\r' != nNextCh || '\n' != c ) ) + { + bNextCh = false; + nNextCh = c; + } + nRet = HtmlTokenId::NEWPARA; + break; + } + [[fallthrough]]; + case '\t': + if( bReadPRE ) + { + nRet = HtmlTokenId::TABCHAR; + break; + } + [[fallthrough]]; + case ' ': + [[fallthrough]]; + default: + +scan_text: + // "normal" text to come + nRet = ScanText(); + bNextCh = 0 == aToken.getLength(); + + // the text should be processed + if( !bNextCh && eState == SvParserState::Pending ) + { + eState = SvParserState::Working; + bReadNextChar = true; + } + + break; + } + + if( bNextCh && SvParserState::Working == eState ) + { + nNextCh = GetNextChar(); + if( SvParserState::Pending == eState && nRet != HtmlTokenId::NONE && HtmlTokenId::TEXTTOKEN != nRet ) + { + bReadNextChar = true; + eState = SvParserState::Working; + } + } + + } while( nRet == HtmlTokenId::NONE && SvParserState::Working == eState ); + + if( SvParserState::Pending == eState ) + nRet = HtmlTokenId::INVALID; // s.th. invalid + + return nRet; +} + +void HTMLParser::UnescapeToken() +{ + sal_Int32 nPos=0; + + bool bEscape = false; + while( nPos < aToken.getLength() ) + { + bool bOldEscape = bEscape; + bEscape = false; + if( '\\'==aToken[nPos] && !bOldEscape ) + { + aToken.remove( nPos, 1 ); + bEscape = true; + } + else + { + nPos++; + } + } +} + +const HTMLOptions& HTMLParser::GetOptions( HtmlOptionId const *pNoConvertToken ) +{ + // If the options for the current token have already been returned, + // return them once again. + if (!maOptions.empty()) + return maOptions; + + sal_Int32 nPos = 0; + while( nPos < aToken.getLength() ) + { + // A letter? Option beginning here. + if( rtl::isAsciiAlpha( aToken[nPos] ) ) + { + HtmlOptionId nToken; + OUString aValue; + sal_Int32 nStt = nPos; + sal_Unicode cChar = 0; + + // Actually only certain characters allowed. + // Netscape only looks for "=" and white space (c.f. + // Mozilla: PA_FetchRequestedNameValues in libparse/pa_mdl.c) + while( nPos < aToken.getLength() ) + { + cChar = aToken[nPos]; + if ( '=' == cChar ||!HTML_ISPRINTABLE(cChar) || rtl::isAsciiWhiteSpace(cChar) ) + break; + nPos++; + } + + OUString sName( aToken.subView( nStt, nPos-nStt ) ); + + // PlugIns require original token name. Convert to lower case only for searching. + nToken = GetHTMLOption( sName.toAsciiLowerCase() ); // Name is ready + SAL_WARN_IF( nToken==HtmlOptionId::UNKNOWN, "svtools", + "GetOption: unknown HTML option '" << sName << "'" ); + bool bStripCRLF = (nToken < HtmlOptionId::SCRIPT_START || + nToken >= HtmlOptionId::SCRIPT_END) && + (!pNoConvertToken || nToken != *pNoConvertToken); + + while( nPos < aToken.getLength() ) + { + cChar = aToken[nPos]; + if ( HTML_ISPRINTABLE(cChar) && !rtl::isAsciiWhiteSpace(cChar) ) + break; + nPos++; + } + + // Option with value? + if( nPos!=aToken.getLength() && '='==cChar ) + { + nPos++; + + while( nPos < aToken.getLength() ) + { + cChar = aToken[nPos]; + if ( HTML_ISPRINTABLE(cChar) && ' ' != cChar && '\t' != cChar && '\r' != cChar && '\n' != cChar ) + break; + nPos++; + } + + if( nPos != aToken.getLength() ) + { + sal_Int32 nLen = 0; + nStt = nPos; + if( ('"'==cChar) || '\''==cChar ) + { + sal_Unicode cEnd = cChar; + nPos++; nStt++; + bool bDone = false; + bool bEscape = false; + while( nPos < aToken.getLength() && !bDone ) + { + bool bOldEscape = bEscape; + bEscape = false; + cChar = aToken[nPos]; + switch( cChar ) + { + case '\r': + case '\n': + if( bStripCRLF ) + aToken.remove( nPos, 1 ); + else + { + nPos++; + nLen++; + } + break; + case '\\': + if( bOldEscape ) + { + nPos++; + nLen++; + } + else + { + aToken.remove( nPos, 1 ); + bEscape = true; + } + break; + case '"': + case '\'': + bDone = !bOldEscape && cChar==cEnd; + if( !bDone ) + { + nPos++; + nLen++; + } + break; + default: + nPos++; + nLen++; + break; + } + } + if( nPos!=aToken.getLength() ) + nPos++; + } + else + { + // More liberal than the standard: allow all printable characters + bool bEscape = false; + bool bDone = false; + while( nPos < aToken.getLength() && !bDone ) + { + bool bOldEscape = bEscape; + bEscape = false; + sal_Unicode c = aToken[nPos]; + switch( c ) + { + case ' ': + bDone = !bOldEscape; + if( !bDone ) + { + nPos++; + nLen++; + } + break; + + case '\t': + case '\r': + case '\n': + bDone = true; + break; + + case '\\': + if( bOldEscape ) + { + nPos++; + nLen++; + } + else + { + aToken.remove( nPos, 1 ); + bEscape = true; + } + break; + + default: + if( HTML_ISPRINTABLE( c ) ) + { + nPos++; + nLen++; + } + else + bDone = true; + break; + } + } + } + + if( nLen ) + aValue = aToken.subView( nStt, nLen ); + } + } + + // Token is known and can be saved + maOptions.emplace_back(nToken, sName, aValue); + + } + else + // Ignore white space and unexpected characters + nPos++; + } + + return maOptions; +} + +HtmlTokenId HTMLParser::FilterPRE( HtmlTokenId nToken ) +{ + switch( nToken ) + { + // in Netscape they only have impact in not empty paragraphs + case HtmlTokenId::PARABREAK_ON: + nToken = HtmlTokenId::LINEBREAK; + [[fallthrough]]; + case HtmlTokenId::LINEBREAK: + case HtmlTokenId::NEWPARA: + nPre_LinePos = 0; + if( bPre_IgnoreNewPara ) + nToken = HtmlTokenId::NONE; + break; + + case HtmlTokenId::TABCHAR: + { + sal_Int32 nSpaces = 8 - (nPre_LinePos % 8); + DBG_ASSERT( aToken.isEmpty(), "Why is the token not empty?" ); + if (aToken.getLength() < nSpaces) + { + using comphelper::string::padToLength; + OUStringBuffer aBuf(aToken); + aToken = padToLength(aBuf, nSpaces, ' ').makeStringAndClear(); + } + nPre_LinePos += nSpaces; + nToken = HtmlTokenId::TEXTTOKEN; + } + break; + // Keep those + case HtmlTokenId::TEXTTOKEN: + nPre_LinePos += aToken.getLength(); + break; + + case HtmlTokenId::SELECT_ON: + case HtmlTokenId::SELECT_OFF: + case HtmlTokenId::BODY_ON: + case HtmlTokenId::FORM_ON: + case HtmlTokenId::FORM_OFF: + case HtmlTokenId::INPUT: + case HtmlTokenId::OPTION: + case HtmlTokenId::TEXTAREA_ON: + case HtmlTokenId::TEXTAREA_OFF: + + case HtmlTokenId::IMAGE: + case HtmlTokenId::APPLET_ON: + case HtmlTokenId::APPLET_OFF: + case HtmlTokenId::PARAM: + case HtmlTokenId::EMBED: + + case HtmlTokenId::HEAD1_ON: + case HtmlTokenId::HEAD1_OFF: + case HtmlTokenId::HEAD2_ON: + case HtmlTokenId::HEAD2_OFF: + case HtmlTokenId::HEAD3_ON: + case HtmlTokenId::HEAD3_OFF: + case HtmlTokenId::HEAD4_ON: + case HtmlTokenId::HEAD4_OFF: + case HtmlTokenId::HEAD5_ON: + case HtmlTokenId::HEAD5_OFF: + case HtmlTokenId::HEAD6_ON: + case HtmlTokenId::HEAD6_OFF: + case HtmlTokenId::BLOCKQUOTE_ON: + case HtmlTokenId::BLOCKQUOTE_OFF: + case HtmlTokenId::ADDRESS_ON: + case HtmlTokenId::ADDRESS_OFF: + case HtmlTokenId::HORZRULE: + + case HtmlTokenId::CENTER_ON: + case HtmlTokenId::CENTER_OFF: + case HtmlTokenId::DIVISION_ON: + case HtmlTokenId::DIVISION_OFF: + + case HtmlTokenId::SCRIPT_ON: + case HtmlTokenId::SCRIPT_OFF: + case HtmlTokenId::RAWDATA: + + case HtmlTokenId::TABLE_ON: + case HtmlTokenId::TABLE_OFF: + case HtmlTokenId::CAPTION_ON: + case HtmlTokenId::CAPTION_OFF: + case HtmlTokenId::COLGROUP_ON: + case HtmlTokenId::COLGROUP_OFF: + case HtmlTokenId::COL_ON: + case HtmlTokenId::COL_OFF: + case HtmlTokenId::THEAD_ON: + case HtmlTokenId::THEAD_OFF: + case HtmlTokenId::TFOOT_ON: + case HtmlTokenId::TFOOT_OFF: + case HtmlTokenId::TBODY_ON: + case HtmlTokenId::TBODY_OFF: + case HtmlTokenId::TABLEROW_ON: + case HtmlTokenId::TABLEROW_OFF: + case HtmlTokenId::TABLEDATA_ON: + case HtmlTokenId::TABLEDATA_OFF: + case HtmlTokenId::TABLEHEADER_ON: + case HtmlTokenId::TABLEHEADER_OFF: + + case HtmlTokenId::ANCHOR_ON: + case HtmlTokenId::ANCHOR_OFF: + case HtmlTokenId::BOLD_ON: + case HtmlTokenId::BOLD_OFF: + case HtmlTokenId::ITALIC_ON: + case HtmlTokenId::ITALIC_OFF: + case HtmlTokenId::STRIKE_ON: + case HtmlTokenId::STRIKE_OFF: + case HtmlTokenId::STRIKETHROUGH_ON: + case HtmlTokenId::STRIKETHROUGH_OFF: + case HtmlTokenId::UNDERLINE_ON: + case HtmlTokenId::UNDERLINE_OFF: + case HtmlTokenId::BASEFONT_ON: + case HtmlTokenId::BASEFONT_OFF: + case HtmlTokenId::FONT_ON: + case HtmlTokenId::FONT_OFF: + case HtmlTokenId::BLINK_ON: + case HtmlTokenId::BLINK_OFF: + case HtmlTokenId::SPAN_ON: + case HtmlTokenId::SPAN_OFF: + case HtmlTokenId::SUBSCRIPT_ON: + case HtmlTokenId::SUBSCRIPT_OFF: + case HtmlTokenId::SUPERSCRIPT_ON: + case HtmlTokenId::SUPERSCRIPT_OFF: + case HtmlTokenId::BIGPRINT_ON: + case HtmlTokenId::BIGPRINT_OFF: + case HtmlTokenId::SMALLPRINT_OFF: + case HtmlTokenId::SMALLPRINT_ON: + + case HtmlTokenId::EMPHASIS_ON: + case HtmlTokenId::EMPHASIS_OFF: + case HtmlTokenId::CITATION_ON: + case HtmlTokenId::CITATION_OFF: + case HtmlTokenId::STRONG_ON: + case HtmlTokenId::STRONG_OFF: + case HtmlTokenId::CODE_ON: + case HtmlTokenId::CODE_OFF: + case HtmlTokenId::SAMPLE_ON: + case HtmlTokenId::SAMPLE_OFF: + case HtmlTokenId::KEYBOARD_ON: + case HtmlTokenId::KEYBOARD_OFF: + case HtmlTokenId::VARIABLE_ON: + case HtmlTokenId::VARIABLE_OFF: + case HtmlTokenId::DEFINSTANCE_ON: + case HtmlTokenId::DEFINSTANCE_OFF: + case HtmlTokenId::SHORTQUOTE_ON: + case HtmlTokenId::SHORTQUOTE_OFF: + case HtmlTokenId::LANGUAGE_ON: + case HtmlTokenId::LANGUAGE_OFF: + case HtmlTokenId::AUTHOR_ON: + case HtmlTokenId::AUTHOR_OFF: + case HtmlTokenId::PERSON_ON: + case HtmlTokenId::PERSON_OFF: + case HtmlTokenId::ACRONYM_ON: + case HtmlTokenId::ACRONYM_OFF: + case HtmlTokenId::ABBREVIATION_ON: + case HtmlTokenId::ABBREVIATION_OFF: + case HtmlTokenId::INSERTEDTEXT_ON: + case HtmlTokenId::INSERTEDTEXT_OFF: + case HtmlTokenId::DELETEDTEXT_ON: + case HtmlTokenId::DELETEDTEXT_OFF: + case HtmlTokenId::TELETYPE_ON: + case HtmlTokenId::TELETYPE_OFF: + + break; + + // The remainder is treated as an unknown token. + default: + if( nToken != HtmlTokenId::NONE ) + { + nToken = + ( ((nToken >= HtmlTokenId::ONOFF_START) && isOffToken(nToken)) + ? HtmlTokenId::UNKNOWNCONTROL_OFF + : HtmlTokenId::UNKNOWNCONTROL_ON ); + } + break; + } + + bPre_IgnoreNewPara = false; + + return nToken; +} + +HtmlTokenId HTMLParser::FilterXMP( HtmlTokenId nToken ) +{ + switch( nToken ) + { + case HtmlTokenId::NEWPARA: + if( bPre_IgnoreNewPara ) + nToken = HtmlTokenId::NONE; + [[fallthrough]]; + case HtmlTokenId::TEXTTOKEN: + case HtmlTokenId::NONBREAKSPACE: + case HtmlTokenId::SOFTHYPH: + break; // kept + + default: + if( nToken != HtmlTokenId::NONE ) + { + if( (nToken >= HtmlTokenId::ONOFF_START) && isOffToken(nToken) ) + { + sSaveToken = "</" + sSaveToken; + } + else + sSaveToken = "<" + sSaveToken; + if( !aToken.isEmpty() ) + { + UnescapeToken(); + sSaveToken += " "; + aToken.insert(0, sSaveToken); + } + else + aToken = sSaveToken; + aToken.append( ">" ); + nToken = HtmlTokenId::TEXTTOKEN; + } + break; + } + + bPre_IgnoreNewPara = false; + + return nToken; +} + +HtmlTokenId HTMLParser::FilterListing( HtmlTokenId nToken ) +{ + switch( nToken ) + { + case HtmlTokenId::NEWPARA: + if( bPre_IgnoreNewPara ) + nToken = HtmlTokenId::NONE; + [[fallthrough]]; + case HtmlTokenId::TEXTTOKEN: + case HtmlTokenId::NONBREAKSPACE: + case HtmlTokenId::SOFTHYPH: + break; // kept + + default: + if( nToken != HtmlTokenId::NONE ) + { + nToken = + ( ((nToken >= HtmlTokenId::ONOFF_START) && isOffToken(nToken)) + ? HtmlTokenId::UNKNOWNCONTROL_OFF + : HtmlTokenId::UNKNOWNCONTROL_ON ); + } + break; + } + + bPre_IgnoreNewPara = false; + + return nToken; +} + +bool HTMLParser::InternalImgToPrivateURL( OUString& rURL ) +{ + bool bFound = false; + + if( rURL.startsWith( OOO_STRING_SVTOOLS_HTML_internal_icon ) ) + { + OUString aName( rURL.copy(14) ); + switch( aName[0] ) + { + case 'b': + bFound = aName == OOO_STRING_SVTOOLS_HTML_INT_ICON_baddata; + break; + case 'd': + bFound = aName == OOO_STRING_SVTOOLS_HTML_INT_ICON_delayed; + break; + case 'e': + bFound = aName == OOO_STRING_SVTOOLS_HTML_INT_ICON_embed; + break; + case 'i': + bFound = aName == OOO_STRING_SVTOOLS_HTML_INT_ICON_insecure; + break; + case 'n': + bFound = aName == OOO_STRING_SVTOOLS_HTML_INT_ICON_notfound; + break; + } + } + if( bFound ) + { + OUString sTmp ( rURL ); + rURL = OOO_STRING_SVTOOLS_HTML_private_image; + rURL += sTmp; + } + + return bFound; +} + +namespace { + +enum class HtmlMeta { + NONE = 0, + Author, + Description, + Keywords, + Refresh, + Classification, + Created, + ChangedBy, + Changed, + Generator, + SDFootnote, + SDEndnote, + ContentType +}; + +} + +// <META NAME=xxx> +HTMLOptionEnum<HtmlMeta> const aHTMLMetaNameTable[] = +{ + { OOO_STRING_SVTOOLS_HTML_META_author, HtmlMeta::Author }, + { OOO_STRING_SVTOOLS_HTML_META_changed, HtmlMeta::Changed }, + { OOO_STRING_SVTOOLS_HTML_META_changedby, HtmlMeta::ChangedBy }, + { OOO_STRING_SVTOOLS_HTML_META_classification,HtmlMeta::Classification}, + { OOO_STRING_SVTOOLS_HTML_META_content_type, HtmlMeta::ContentType }, + { OOO_STRING_SVTOOLS_HTML_META_created, HtmlMeta::Created }, + { OOO_STRING_SVTOOLS_HTML_META_description, HtmlMeta::Description }, + { OOO_STRING_SVTOOLS_HTML_META_keywords, HtmlMeta::Keywords }, + { OOO_STRING_SVTOOLS_HTML_META_generator, HtmlMeta::Generator }, + { OOO_STRING_SVTOOLS_HTML_META_refresh, HtmlMeta::Refresh }, + { OOO_STRING_SVTOOLS_HTML_META_sdendnote, HtmlMeta::SDEndnote }, + { OOO_STRING_SVTOOLS_HTML_META_sdfootnote, HtmlMeta::SDFootnote }, + { nullptr, HtmlMeta(0) } +}; + + +void HTMLParser::AddMetaUserDefined( OUString const & ) +{ +} + +bool HTMLParser::ParseMetaOptionsImpl( + const uno::Reference<document::XDocumentProperties> & i_xDocProps, + SvKeyValueIterator *i_pHTTPHeader, + const HTMLOptions& aOptions, + rtl_TextEncoding& o_rEnc ) +{ + OUString aName, aContent; + HtmlMeta nAction = HtmlMeta::NONE; + bool bHTTPEquiv = false, bChanged = false; + + for ( size_t i = aOptions.size(); i; ) + { + const HTMLOption& aOption = aOptions[--i]; + switch ( aOption.GetToken() ) + { + case HtmlOptionId::NAME: + aName = aOption.GetString(); + if ( HtmlMeta::NONE==nAction ) + { + aOption.GetEnum( nAction, aHTMLMetaNameTable ); + } + break; + case HtmlOptionId::HTTPEQUIV: + aName = aOption.GetString(); + aOption.GetEnum( nAction, aHTMLMetaNameTable ); + bHTTPEquiv = true; + break; + case HtmlOptionId::CONTENT: + aContent = aOption.GetString(); + break; + case HtmlOptionId::CHARSET: + { + OString sValue(OUStringToOString(aOption.GetString(), RTL_TEXTENCODING_ASCII_US)); + o_rEnc = GetExtendedCompatibilityTextEncoding(rtl_getTextEncodingFromMimeCharset(sValue.getStr())); + break; + } + default: break; + } + } + + if ( bHTTPEquiv || HtmlMeta::Description != nAction ) + { + // if it is not a Description, remove CRs and LFs from CONTENT + aContent = aContent.replaceAll("\r", "").replaceAll("\n", ""); + } + else + { + // convert line endings for Description + aContent = convertLineEnd(aContent, GetSystemLineEnd()); + } + + if ( bHTTPEquiv && i_pHTTPHeader ) + { + // Netscape seems to just ignore a closing ", so we do too + if ( aContent.endsWith("\"") ) + { + aContent = aContent.copy( 0, aContent.getLength() - 1 ); + } + SvKeyValue aKeyValue( aName, aContent ); + i_pHTTPHeader->Append( aKeyValue ); + } + + switch ( nAction ) + { + case HtmlMeta::Author: + if (i_xDocProps.is()) { + i_xDocProps->setAuthor( aContent ); + bChanged = true; + } + break; + case HtmlMeta::Description: + if (i_xDocProps.is()) { + i_xDocProps->setDescription( aContent ); + bChanged = true; + } + break; + case HtmlMeta::Keywords: + if (i_xDocProps.is()) { + i_xDocProps->setKeywords( + ::comphelper::string::convertCommaSeparated(aContent)); + bChanged = true; + } + break; + case HtmlMeta::Classification: + if (i_xDocProps.is()) { + i_xDocProps->setSubject( aContent ); + bChanged = true; + } + break; + + case HtmlMeta::ChangedBy: + if (i_xDocProps.is()) { + i_xDocProps->setModifiedBy( aContent ); + bChanged = true; + } + break; + + case HtmlMeta::Created: + case HtmlMeta::Changed: + if (i_xDocProps.is() && !aContent.isEmpty()) + { + ::util::DateTime uDT; + bool valid = false; + if (comphelper::string::getTokenCount(aContent, ';') == 2) + { + sal_Int32 nIdx{ 0 }; + Date aDate(o3tl::toInt32(o3tl::getToken(aContent, 0, ';', nIdx))); + auto nTime = o3tl::toInt64(o3tl::getToken(aContent, 0, ';', nIdx)); + if (nTime < 0) + nTime = o3tl::saturating_toggle_sign(nTime); + tools::Time aTime(nTime); + DateTime aDateTime(aDate, aTime); + uDT = aDateTime.GetUNODateTime(); + valid = true; + } + else if (utl::ISO8601parseDateTime(aContent, uDT)) + valid = true; + + if (valid) + { + bChanged = true; + if (HtmlMeta::Created == nAction) + i_xDocProps->setCreationDate(uDT); + else + i_xDocProps->setModificationDate(uDT); + } + } + break; + + case HtmlMeta::Refresh: + DBG_ASSERT( !bHTTPEquiv || i_pHTTPHeader, "Lost Reload-URL because of omitted MUST change." ); + break; + + case HtmlMeta::ContentType: + if ( !aContent.isEmpty() ) + { + o_rEnc = GetEncodingByMIME( aContent ); + } + break; + + case HtmlMeta::NONE: + if ( !bHTTPEquiv ) + { + if (i_xDocProps.is()) + { + uno::Reference<beans::XPropertyContainer> xUDProps + = i_xDocProps->getUserDefinedProperties(); + try { + xUDProps->addProperty(aName, + beans::PropertyAttribute::REMOVABLE, + uno::Any(aContent)); + AddMetaUserDefined(aName); + bChanged = true; + } catch (uno::Exception &) { + // ignore + } + } + } + break; + default: + break; + } + + return bChanged; +} + +bool HTMLParser::ParseMetaOptions( + const uno::Reference<document::XDocumentProperties> & i_xDocProps, + SvKeyValueIterator *i_pHeader ) +{ + HtmlOptionId nContentOption = HtmlOptionId::CONTENT; + rtl_TextEncoding eEnc = RTL_TEXTENCODING_DONTKNOW; + + bool bRet = ParseMetaOptionsImpl( i_xDocProps, i_pHeader, + GetOptions(&nContentOption), + eEnc ); + + // If the encoding is set by a META tag, it may only overwrite the + // current encoding if both, the current and the new encoding, are 1-sal_uInt8 + // encodings. Everything else cannot lead to reasonable results. + if (RTL_TEXTENCODING_DONTKNOW != eEnc && + rtl_isOctetTextEncoding( eEnc ) && + rtl_isOctetTextEncoding( GetSrcEncoding() ) ) + { + eEnc = GetExtendedCompatibilityTextEncoding( eEnc ); + SetSrcEncoding( eEnc ); + } + + return bRet; +} + +rtl_TextEncoding HTMLParser::GetEncodingByMIME( const OUString& rMime ) +{ + OUString sType; + OUString sSubType; + INetContentTypeParameterList aParameters; + if (INetContentTypes::parse(rMime, sType, sSubType, &aParameters)) + { + auto const iter = aParameters.find("charset"); + if (iter != aParameters.end()) + { + const INetContentTypeParameter * pCharset = &iter->second; + OString sValue(OUStringToOString(pCharset->m_sValue, RTL_TEXTENCODING_ASCII_US)); + return GetExtendedCompatibilityTextEncoding( rtl_getTextEncodingFromMimeCharset( sValue.getStr() ) ); + } + } + return RTL_TEXTENCODING_DONTKNOW; +} + +rtl_TextEncoding HTMLParser::GetEncodingByHttpHeader( SvKeyValueIterator *pHTTPHeader ) +{ + rtl_TextEncoding eRet = RTL_TEXTENCODING_DONTKNOW; + if( pHTTPHeader ) + { + SvKeyValue aKV; + for( bool bCont = pHTTPHeader->GetFirst( aKV ); bCont; + bCont = pHTTPHeader->GetNext( aKV ) ) + { + if( aKV.GetKey().equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_META_content_type ) ) + { + if( !aKV.GetValue().isEmpty() ) + { + eRet = HTMLParser::GetEncodingByMIME( aKV.GetValue() ); + } + } + } + } + return eRet; +} + +bool HTMLParser::SetEncodingByHTTPHeader( SvKeyValueIterator *pHTTPHeader ) +{ + bool bRet = false; + rtl_TextEncoding eEnc = HTMLParser::GetEncodingByHttpHeader( pHTTPHeader ); + if(RTL_TEXTENCODING_DONTKNOW != eEnc) + { + SetSrcEncoding( eEnc ); + bRet = true; + } + return bRet; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/svrtf/parrtf.cxx b/svtools/source/svrtf/parrtf.cxx new file mode 100644 index 000000000..69fdb9508 --- /dev/null +++ b/svtools/source/svrtf/parrtf.cxx @@ -0,0 +1,689 @@ +/* -*- 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 <sal/log.hxx> + +#include <comphelper/scopeguard.hxx> + +#include <rtl/character.hxx> +#include <rtl/strbuf.hxx> +#include <rtl/tencinfo.h> +#include <rtl/ustrbuf.hxx> +#include <tools/stream.hxx> +#include <tools/debug.hxx> +#include <svtools/rtftoken.h> +#include <svtools/parrtf.hxx> + +const int MAX_STRING_LEN = 1024; + +#define RTF_ISDIGIT( c ) rtl::isAsciiDigit(c) +#define RTF_ISALPHA( c ) rtl::isAsciiAlpha(c) + +SvRTFParser::SvRTFParser( SvStream& rIn, sal_uInt8 nStackSize ) + : SvParser<int>( rIn, nStackSize ) + , nOpenBrackets(0) + , eCodeSet(RTL_TEXTENCODING_MS_1252) + , nUCharOverread(1) +{ + // default is ANSI-CodeSet + SetSrcEncoding( RTL_TEXTENCODING_MS_1252 ); + bRTF_InTextRead = false; +} + +SvRTFParser::~SvRTFParser() +{ +} + + +int SvRTFParser::GetNextToken_() +{ + int nRet = 0; + do { + bool bNextCh = true; + switch( nNextCh ) + { + case '\\': + { + // control characters + nNextCh = GetNextChar(); + switch( nNextCh ) + { + case '{': + case '}': + case '\\': + case '+': // I found it in a RTF-file + case '~': // nonbreaking space + case '-': // optional hyphen + case '_': // nonbreaking hyphen + case '\'': // HexValue + nNextCh = '\\'; + rInput.SeekRel( -1 ); + ScanText(); + nRet = RTF_TEXTTOKEN; + bNextCh = 0 == nNextCh; + break; + + case '*': // ignoreflag + nRet = RTF_IGNOREFLAG; + break; + case ':': // subentry in an index entry + nRet = RTF_SUBENTRYINDEX; + break; + case '|': // formula-character + nRet = RTF_FORMULA; + break; + + case 0x0a: + case 0x0d: + nRet = RTF_PAR; + break; + + default: + if( RTF_ISALPHA( nNextCh ) ) + { + aToken = "\\"; + { + do { + aToken.appendUtf32(nNextCh); + nNextCh = GetNextChar(); + } while( RTF_ISALPHA( nNextCh ) ); + } + + // minus before numeric parameters + bool bNegValue = false; + if( '-' == nNextCh ) + { + bNegValue = true; + nNextCh = GetNextChar(); + } + + // possible numeric parameter + if( RTF_ISDIGIT( nNextCh ) ) + { + OUStringBuffer aNumber; + do { + aNumber.append(static_cast<sal_Unicode>(nNextCh)); + nNextCh = GetNextChar(); + } while( RTF_ISDIGIT( nNextCh ) ); + nTokenValue = OUString::unacquired(aNumber).toInt32(); + if( bNegValue ) + nTokenValue = -nTokenValue; + bTokenHasValue=true; + } + else if( bNegValue ) // restore minus + { + nNextCh = '-'; + rInput.SeekRel( -1 ); + } + if( ' ' == nNextCh ) // blank is part of token! + nNextCh = GetNextChar(); + + // search for the token in the table: + if( 0 == (nRet = GetRTFToken( aToken )) ) + // Unknown Control + nRet = RTF_UNKNOWNCONTROL; + + // bug 76812 - unicode token handled as normal text + bNextCh = false; + switch( nRet ) + { + case RTF_UC: + if( 0 <= nTokenValue ) + { + nUCharOverread = static_cast<sal_uInt8>(nTokenValue); + if (!aParserStates.empty()) + { + //cmc: other ifdef breaks #i3584 + aParserStates.top().nUCharOverread = nUCharOverread; + } + } + aToken.setLength( 0 ); // #i47831# erase token to prevent the token from being treated as text + // read next token + nRet = 0; + break; + + case RTF_UPR: + if (!_inSkipGroup) { + // UPR - overread the group with the ansi + // information + int nNextToken; + do + { + nNextToken = GetNextToken_(); + } + while (nNextToken != '{' && nNextToken != sal_Unicode(EOF)); + + SkipGroup(); + GetNextToken_(); // overread the last bracket + nRet = 0; + } + break; + + case RTF_U: + if( !bRTF_InTextRead ) + { + nRet = RTF_TEXTTOKEN; + aToken = OUStringChar( static_cast<sal_Unicode>(nTokenValue) ); + + // overread the next n "RTF" characters. This + // can be also \{, \}, \'88 + for( sal_uInt8 m = 0; m < nUCharOverread; ++m ) + { + sal_uInt32 cAnsi = nNextCh; + while( 0xD == cAnsi ) + cAnsi = GetNextChar(); + while( 0xA == cAnsi ) + cAnsi = GetNextChar(); + + if( '\\' == cAnsi && + '\'' == GetNextChar() ) + // skip HexValue + GetHexValue(); + nNextCh = GetNextChar(); + } + ScanText(); + bNextCh = 0 == nNextCh; + } + break; + } + } + else if( SvParserState::Pending != eState ) + { + // Bug 34631 - "\ " read on - Blank as character + // eState = SvParserState::Error; + bNextCh = false; + } + break; + } + } + break; + + case sal_Unicode(EOF): + eState = SvParserState::Accepted; + nRet = nNextCh; + break; + + case '{': + { + if( 0 <= nOpenBrackets ) + { + RtfParserState_Impl aState( nUCharOverread, GetSrcEncoding() ); + aParserStates.push( aState ); + } + ++nOpenBrackets; + DBG_ASSERT( + static_cast<size_t>(nOpenBrackets) == aParserStates.size(), + "ParserStateStack unequal to bracket count" ); + nRet = nNextCh; + } + break; + + case '}': + --nOpenBrackets; + if( 0 <= nOpenBrackets ) + { + aParserStates.pop(); + if( !aParserStates.empty() ) + { + const RtfParserState_Impl& rRPS = + aParserStates.top(); + nUCharOverread = rRPS.nUCharOverread; + SetSrcEncoding( rRPS.eCodeSet ); + } + else + { + nUCharOverread = 1; + SetSrcEncoding( GetCodeSet() ); + } + } + DBG_ASSERT( + static_cast<size_t>(nOpenBrackets) == aParserStates.size(), + "ParserStateStack unequal to bracket count" ); + nRet = nNextCh; + break; + + case 0x0d: + case 0x0a: + break; + + default: + // now normal text follows + ScanText(); + nRet = RTF_TEXTTOKEN; + bNextCh = 0 == nNextCh; + break; + } + + if( bNextCh ) + nNextCh = GetNextChar(); + + } while( !nRet && SvParserState::Working == eState ); + return nRet; +} + + +sal_Unicode SvRTFParser::GetHexValue() +{ + // collect Hex values + int n; + sal_Unicode nHexVal = 0; + + for( n = 0; n < 2; ++n ) + { + nHexVal *= 16; + nNextCh = GetNextChar(); + if( nNextCh >= '0' && nNextCh <= '9' ) + nHexVal += (nNextCh - 48); + else if( nNextCh >= 'a' && nNextCh <= 'f' ) + nHexVal += (nNextCh - 87); + else if( nNextCh >= 'A' && nNextCh <= 'F' ) + nHexVal += (nNextCh - 55); + } + return nHexVal; +} + +void SvRTFParser::ScanText() +{ + const sal_Unicode cBreak = 0; + OUStringBuffer aStrBuffer; + bool bContinue = true; + while( bContinue && IsParserWorking() && aStrBuffer.getLength() < MAX_STRING_LEN) + { + bool bNextCh = true; + switch( nNextCh ) + { + case '\\': + { + nNextCh = GetNextChar(); + switch (nNextCh) + { + case '\'': + { + + OStringBuffer aByteString; + while (true) + { + char c = static_cast<char>(GetHexValue()); + /* + * Note: \'00 is a valid internal character in a + * string in RTF. OStringBuffer supports + * appending nulls fine + */ + aByteString.append(c); + + bool bBreak = false; + bool bEOF = false; + char nSlash = '\\'; + while (!bBreak) + { + auto next = GetNextChar(); + if (sal_Unicode(EOF) == next) + { + bEOF = true; + break; + } + if (next>0xFF) // fix for #i43933# and #i35653# + { + if (!aByteString.isEmpty()) + aStrBuffer.append( OStringToOUString(aByteString.makeStringAndClear(), GetSrcEncoding()) ); + aStrBuffer.append(static_cast<sal_Unicode>(next)); + + continue; + } + nSlash = static_cast<char>(next); + while (nSlash == 0xD || nSlash == 0xA) + nSlash = static_cast<char>(GetNextChar()); + + switch (nSlash) + { + case '{': + case '}': + case '\\': + bBreak = true; + break; + default: + aByteString.append(nSlash); + break; + } + } + + if (bEOF) + { + bContinue = false; // abort, string together + break; + } + + nNextCh = GetNextChar(); + + if (nSlash != '\\' || nNextCh != '\'') + { + rInput.SeekRel(-1); + nNextCh = static_cast<unsigned char>(nSlash); + break; + } + } + + bNextCh = false; + + if (!aByteString.isEmpty()) + aStrBuffer.append( OStringToOUString(aByteString.makeStringAndClear(), GetSrcEncoding()) ); + } + break; + case '\\': + case '}': + case '{': + case '+': // I found in a RTF file + aStrBuffer.append(sal_Unicode(nNextCh)); + break; + case '~': // nonbreaking space + aStrBuffer.append(u'\x00A0'); + break; + case '-': // optional hyphen + aStrBuffer.append(u'\x00AD'); + break; + case '_': // nonbreaking hyphen + aStrBuffer.append(u'\x2011'); + break; + + case 'u': + // read UNI-Code characters + { + nNextCh = GetNextChar(); + rInput.SeekRel( -2 ); + + if( '-' == nNextCh || RTF_ISDIGIT( nNextCh ) ) + { + bRTF_InTextRead = true; + + OUString sSave( aToken ); // GetNextToken_() overwrites this + nNextCh = '\\'; + int nToken = GetNextToken_(); + DBG_ASSERT( RTF_U == nToken, "still not a UNI-Code character" ); + // don't convert symbol chars + aStrBuffer.append(static_cast< sal_Unicode >(nTokenValue)); + + // overread the next n "RTF" characters. This + // can be also \{, \}, \'88 + for( sal_uInt8 m = 0; m < nUCharOverread; ++m ) + { + sal_Unicode cAnsi = nNextCh; + while( 0xD == cAnsi ) + cAnsi = GetNextChar(); + while( 0xA == cAnsi ) + cAnsi = GetNextChar(); + + if( '\\' == cAnsi && + '\'' == GetNextChar() ) + // skip HexValue + GetHexValue(); + nNextCh = GetNextChar(); + } + bNextCh = false; + aToken = sSave; + bRTF_InTextRead = false; + } + else if ( 'c' == nNextCh ) + { + // Prevent text breaking into multiple tokens. + rInput.SeekRel( 2 ); + nNextCh = GetNextChar(); + if (RTF_ISDIGIT( nNextCh )) + { + sal_uInt8 nNewOverread = 0 ; + do { + nNewOverread *= 10; + nNewOverread += nNextCh - '0'; + nNextCh = GetNextChar(); + } while ( RTF_ISDIGIT( nNextCh ) ); + nUCharOverread = nNewOverread; + if (!aParserStates.empty()) + aParserStates.top().nUCharOverread = nNewOverread; + } + bNextCh = 0x20 == nNextCh; + } + else + { + nNextCh = '\\'; + bContinue = false; // abort, string together + } + } + break; + + default: + rInput.SeekRel( -1 ); + nNextCh = '\\'; + bContinue = false; // abort, string together + break; + } + } + break; + + case sal_Unicode(EOF): + eState = SvParserState::Error; + [[fallthrough]]; + case '{': + case '}': + bContinue = false; + break; + + case 0x0a: + case 0x0d: + break; + + default: + if( nNextCh == cBreak || aStrBuffer.getLength() >= MAX_STRING_LEN) + bContinue = false; + else + { + do { + // all other characters end up in the text + aStrBuffer.appendUtf32(nNextCh); + + if (sal_Unicode(EOF) == (nNextCh = GetNextChar())) + { + if (!aStrBuffer.isEmpty()) + aToken.append( aStrBuffer ); + return; + } + } while + ( + (RTF_ISALPHA(nNextCh) || RTF_ISDIGIT(nNextCh)) && + (aStrBuffer.getLength() < MAX_STRING_LEN) + ); + bNextCh = false; + } + } + + if( bContinue && bNextCh ) + nNextCh = GetNextChar(); + } + + if (!aStrBuffer.isEmpty()) + aToken.append( aStrBuffer ); +} + + +short SvRTFParser::_inSkipGroup=0; + +void SvRTFParser::SkipGroup() +{ + short nBrackets=1; + if (_inSkipGroup>0) + return; + _inSkipGroup++; +//#i16185# faking \bin keyword + do + { + switch (nNextCh) + { + case '{': + ++nBrackets; + break; + case '}': + if (!--nBrackets) { + _inSkipGroup--; + return; + } + break; + } + int nToken = GetNextToken_(); + if (nToken == RTF_BIN) + { + rInput.SeekRel(-1); + SAL_WARN_IF(nTokenValue < 0, "svtools", "negative value argument for rtf \\bin keyword"); + if (nTokenValue > 0) + rInput.SeekRel(nTokenValue); + nNextCh = GetNextChar(); + } + while (nNextCh==0xa || nNextCh==0xd) + { + nNextCh = GetNextChar(); + } + } while (sal_Unicode(EOF) != nNextCh && IsParserWorking()); + + if( SvParserState::Pending != eState && '}' != nNextCh ) + eState = SvParserState::Error; + _inSkipGroup--; +} + +void SvRTFParser::ReadUnknownData() { SkipGroup(); } +void SvRTFParser::ReadBitmapData() { SkipGroup(); } + + +SvParserState SvRTFParser::CallParser() +{ + char cFirstCh(0); + nNextChPos = rInput.Tell(); + rInput.ReadChar( cFirstCh ); + nNextCh = static_cast<unsigned char>(cFirstCh); + eState = SvParserState::Working; + nOpenBrackets = 0; + eCodeSet = RTL_TEXTENCODING_MS_1252; + SetSrcEncoding( eCodeSet ); + + // the first two tokens should be '{' and \\rtf !! + if( '{' == GetNextToken() && RTF_RTF == GetNextToken() ) + { + AddFirstRef(); + // call ReleaseRef at end of this scope, even in the face of exceptions + comphelper::ScopeGuard g([this] { + if( SvParserState::Pending != eState ) + ReleaseRef(); // now parser is not needed anymore + }); + Continue( 0 ); + } + else + eState = SvParserState::Error; + + return eState; +} + +void SvRTFParser::Continue( int nToken ) +{ +// DBG_ASSERT( SVPAR_CS_DONTKNOW == GetCharSet(), +// "Characterset was changed." ); + + if( !nToken ) + nToken = GetNextToken(); + + bool bLooping = false; + + while (IsParserWorking() && !bLooping) + { + auto nCurrentTokenIndex = m_nTokenIndex; + auto nCurrentToken = nToken; + + SaveState( nToken ); + switch( nToken ) + { + case '}': + if( nOpenBrackets ) + goto NEXTTOKEN; + eState = SvParserState::Accepted; + break; + + case '{': + // an unknown group ? + { + if( RTF_IGNOREFLAG != GetNextToken() ) + nToken = SkipToken(); + else if( RTF_UNKNOWNCONTROL != GetNextToken() ) + nToken = SkipToken( -2 ); + else + { + // filter immediately + ReadUnknownData(); + nToken = GetNextToken(); + if( '}' != nToken ) + eState = SvParserState::Error; + break; // move to next token!! + } + } + goto NEXTTOKEN; + + case RTF_UNKNOWNCONTROL: + break; // skip unknown token + case RTF_NEXTTYPE: + case RTF_ANSITYPE: + eCodeSet = RTL_TEXTENCODING_MS_1252; + SetSrcEncoding( eCodeSet ); + break; + case RTF_MACTYPE: + eCodeSet = RTL_TEXTENCODING_APPLE_ROMAN; + SetSrcEncoding( eCodeSet ); + break; + case RTF_PCTYPE: + eCodeSet = RTL_TEXTENCODING_IBM_437; + SetSrcEncoding( eCodeSet ); + break; + case RTF_PCATYPE: + eCodeSet = RTL_TEXTENCODING_IBM_850; + SetSrcEncoding( eCodeSet ); + break; + case RTF_ANSICPG: + eCodeSet = rtl_getTextEncodingFromWindowsCodePage(nTokenValue); + SetSrcEncoding(eCodeSet); + break; + default: +NEXTTOKEN: + NextToken( nToken ); + break; + } + if( IsParserWorking() ) + SaveState( 0 ); // processed till here, + // continue with new token! + nToken = GetNextToken(); + bLooping = nCurrentTokenIndex == m_nTokenIndex && nToken == nCurrentToken; + } + if( SvParserState::Accepted == eState && 0 < nOpenBrackets ) + eState = SvParserState::Error; +} + +void SvRTFParser::SetEncoding( rtl_TextEncoding eEnc ) +{ + if (eEnc == RTL_TEXTENCODING_DONTKNOW) + eEnc = GetCodeSet(); + + if (!aParserStates.empty()) + aParserStates.top().eCodeSet = eEnc; + SetSrcEncoding(eEnc); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/svrtf/rtfkeywd.cxx b/svtools/source/svrtf/rtfkeywd.cxx new file mode 100644 index 000000000..e4864d7ed --- /dev/null +++ b/svtools/source/svrtf/rtfkeywd.cxx @@ -0,0 +1,1202 @@ +/* -*- 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 <o3tl/string_view.hxx> +#include <rtl/ustring.hxx> +#include <svtools/rtfkeywd.hxx> +#include <svtools/rtftoken.h> + +#include <algorithm> +#include <string_view> + +namespace { + +// the table is still to be sorted +struct RTF_TokenEntry +{ + std::u16string_view sToken; + int nToken; +}; + +} + +// Flag: RTF-token table has been sorted +static bool bSortKeyWords = false; + +static RTF_TokenEntry aRTFTokenTab[] = { +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_IGNORE), RTF_IGNOREFLAG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RTF), RTF_RTF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ANSI), RTF_ANSITYPE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MAC), RTF_MACTYPE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PC), RTF_PCTYPE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PCA), RTF_PCATYPE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NEXTCSET), RTF_NEXTTYPE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_STYLESHEET), RTF_STYLESHEET}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SBASEDON), RTF_SBASEDON}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SNEXT), RTF_SNEXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FONTTBL), RTF_FONTTBL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DEFF), RTF_DEFF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FNIL), RTF_FNIL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FROMAN), RTF_FROMAN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FSWISS), RTF_FSWISS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FMODERN), RTF_FMODERN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FSCRIPT), RTF_FSCRIPT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FDECOR), RTF_FDECOR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTECH), RTF_FTECH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FCHARSET), RTF_FCHARSET}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FALT), RTF_FALT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FPRQ), RTF_FPRQ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_COLORTBL), RTF_COLORTBL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RED), RTF_RED}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_GREEN), RTF_GREEN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BLUE), RTF_BLUE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CF), RTF_CF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CB), RTF_CB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_INFO), RTF_INFO}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TITLE), RTF_TITLE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SUBJECT), RTF_SUBJECT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AUTHOR), RTF_AUTHOR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OPERATOR), RTF_OPERATOR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_KEYWORDS), RTF_KEYWORDS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_COMMENT), RTF_COMMENT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_VERSION), RTF_VERSION}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DOCCOMM), RTF_DOCCOMM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_VERN), RTF_VERN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CREATIM), RTF_CREATIM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_REVTIM), RTF_REVTIM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PRINTIM), RTF_PRINTIM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BUPTIM), RTF_BUPTIM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_EDMINS), RTF_EDMINS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOFPAGES), RTF_NOFPAGES}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOFWORDS), RTF_NOFWORDS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOFCHARS), RTF_NOFCHARS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ID), RTF_ID}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_YR), RTF_YR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MO), RTF_MO}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DY), RTF_DY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HR), RTF_HR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MIN), RTF_MIN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ANNOTATION), RTF_ANNOTATION}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ATNID), RTF_ATNID}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FOOTNOTE), RTF_FOOTNOTE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FOOTER), RTF_FOOTER}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FOOTERL), RTF_FOOTERL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FOOTERR), RTF_FOOTERR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FOOTERF), RTF_FOOTERF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HEADER), RTF_HEADER}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HEADERL), RTF_HEADERL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HEADERR), RTF_HEADERR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HEADERF), RTF_HEADERF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_XE), RTF_XE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BXE), RTF_BXE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_IXE), RTF_IXE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RXE), RTF_RXE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TXE), RTF_TXE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TC), RTF_TC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TCF), RTF_TCF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TCL), RTF_TCL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BKMKSTART), RTF_BKMKSTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BKMKEND), RTF_BKMKEND}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICT), RTF_PICT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICW), RTF_PICW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICH), RTF_PICH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_WBMBITSPIXEL), RTF_WBMBITSPIXEL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_WBMPLANES), RTF_WBMPLANES}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_WBMWIDTHBYTES), RTF_WBMWIDTHBYTES}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICWGOAL), RTF_PICWGOAL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICHGOAL), RTF_PICHGOAL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BIN), RTF_BIN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICSCALEX), RTF_PICSCALEX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICSCALEY), RTF_PICSCALEY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICSCALED), RTF_PICSCALED}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_WBITMAP), RTF_WBITMAP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_WMETAFILE), RTF_WMETAFILE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MACPICT), RTF_MACPICT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICCROPT), RTF_PICCROPT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICCROPB), RTF_PICCROPB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICCROPL), RTF_PICCROPL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICCROPR), RTF_PICCROPR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FIELD), RTF_FIELD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLDDIRTY), RTF_FLDDIRTY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLDEDIT), RTF_FLDEDIT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLDLOCK), RTF_FLDLOCK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLDPRIV), RTF_FLDPRIV}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLDINST), RTF_FLDINST}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLDRSLT), RTF_FLDRSLT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PAPERW), RTF_PAPERW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PAPERH), RTF_PAPERH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MARGL), RTF_MARGL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MARGR), RTF_MARGR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MARGT), RTF_MARGT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MARGB), RTF_MARGB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FACINGP), RTF_FACINGP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_GUTTER), RTF_GUTTER}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DEFTAB), RTF_DEFTAB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_WIDOWCTRL), RTF_WIDOWCTRL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HYPHHOTZ), RTF_HYPHHOTZ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNSEP), RTF_FTNSEP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNSEPC), RTF_FTNSEPC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNCN), RTF_FTNCN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ENDNOTES), RTF_ENDNOTES}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ENDDOC), RTF_ENDDOC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNTJ), RTF_FTNTJ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNBJ), RTF_FTNBJ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNSTART), RTF_FTNSTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNRESTART), RTF_FTNRESTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNSTART), RTF_PGNSTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LINESTART), RTF_LINESTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LANDSCAPE), RTF_LANDSCAPE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FRACWIDTH), RTF_FRACWIDTH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NEXTFILE), RTF_NEXTFILE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TEMPLATE), RTF_TEMPLATE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MAKEBACKUP), RTF_MAKEBACKUP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DEFFORMAT), RTF_DEFFORMAT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_REVISIONS), RTF_REVISIONS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MARGMIRROR), RTF_MARGMIRROR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_REVPROP), RTF_REVPROP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_REVBAR), RTF_REVBAR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SECTD), RTF_SECTD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SBKNONE), RTF_SBKNONE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SBKCOL), RTF_SBKCOL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SBKPAGE), RTF_SBKPAGE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SBKEVEN), RTF_SBKEVEN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SBKODD), RTF_SBKODD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNSTARTS), RTF_PGNSTARTS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNCONT), RTF_PGNCONT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNRESTART), RTF_PGNRESTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNDEC), RTF_PGNDEC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNUCRM), RTF_PGNUCRM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNLCRM), RTF_PGNLCRM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNUCLTR), RTF_PGNUCLTR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNLCLTR), RTF_PGNLCLTR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNX), RTF_PGNX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNY), RTF_PGNY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HEADERY), RTF_HEADERY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FOOTERY), RTF_FOOTERY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LINEMOD), RTF_LINEMOD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LINEX), RTF_LINEX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LINESTARTS), RTF_LINESTARTS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LINERESTART), RTF_LINERESTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LINEPPAGE), RTF_LINEPAGE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LINECONT), RTF_LINECONT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_VERTALT), RTF_VERTALT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_VERTALB), RTF_VERTALB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_VERTALC), RTF_VERTALC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_VERTALJ), RTF_VERTALJ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_COLS), RTF_COLS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_COLSX), RTF_COLSX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_COLNO), RTF_COLNO}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_COLSR), RTF_COLSR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_COLW), RTF_COLW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LINEBETCOL), RTF_LINEBETCOL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ENDNHERE), RTF_ENDNHERE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TITLEPG), RTF_TITLEPG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PARD), RTF_PARD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_S), RTF_S}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_QL), RTF_QL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_QR), RTF_QR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_QJ), RTF_QJ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_QC), RTF_QC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FI), RTF_FI}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LI), RTF_LI}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LIN), RTF_LIN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RI), RTF_RI}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RIN), RTF_RIN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SB), RTF_SB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SA), RTF_SA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SL), RTF_SL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_INTBL), RTF_INTBL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_KEEP), RTF_KEEP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_KEEPN), RTF_KEEPN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LEVEL), RTF_LEVEL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SBYS), RTF_SBYS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PAGEBB), RTF_PAGEBB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOLINE), RTF_NOLINE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TX), RTF_TX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TQL), RTF_TQL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TQR), RTF_TQR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TQC), RTF_TQC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TQDEC), RTF_TQDEC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TB), RTF_TB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRT), RTF_BRDRT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRB), RTF_BRDRB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRL), RTF_BRDRL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRR), RTF_BRDRR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BOX), RTF_BOX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRS), RTF_BRDRS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRTH), RTF_BRDRTH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRSH), RTF_BRDRSH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRDB), RTF_BRDRDB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRDOT), RTF_BRDRDOT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRHAIR), RTF_BRDRHAIR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRSP), RTF_BRSP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TLDOT), RTF_TLDOT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TLHYPH), RTF_TLHYPH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TLUL), RTF_TLUL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TLTH), RTF_TLTH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSX), RTF_POSX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSXC), RTF_POSXC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSXI), RTF_POSXI}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSXL), RTF_POSXL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSXO), RTF_POSXO}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSXR), RTF_POSXR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSY), RTF_POSY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSYIL), RTF_POSYIL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSYT), RTF_POSYT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSYC), RTF_POSYC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSYB), RTF_POSYB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ABSW), RTF_ABSW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DXFRTEXT), RTF_DXFRTEXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PVMRG), RTF_PVMRG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PVPG), RTF_PVPG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PHMRG), RTF_PHMRG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PHPG), RTF_PHPG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PHCOL), RTF_PHCOL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBRDRB), RTF_CLBRDRB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBRDRT), RTF_CLBRDRT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBRDRL), RTF_CLBRDRL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBRDRR), RTF_CLBRDRR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLPADL), RTF_CLPADL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLPADT), RTF_CLPADT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLPADB), RTF_CLPADB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLPADR), RTF_CLPADR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLPADFL), RTF_CLPADFL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLPADFT), RTF_CLPADFT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLPADFB), RTF_CLPADFB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLPADFR), RTF_CLPADFR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TROWD), RTF_TROWD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRQL), RTF_TRQL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRQR), RTF_TRQR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRQC), RTF_TRQC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRGAPH), RTF_TRGAPH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRRH), RTF_TRRH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRLEFT), RTF_TRLEFT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CELLX), RTF_CELLX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLMGF), RTF_CLMGF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLMRG), RTF_CLMRG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PLAIN), RTF_PLAIN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_B), RTF_B}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_I), RTF_I}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_STRIKE), RTF_STRIKE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OUTL), RTF_OUTL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHAD), RTF_SHAD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SCAPS), RTF_SCAPS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CAPS), RTF_CAPS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_V), RTF_V}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_F), RTF_F}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FS), RTF_FS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_EXPND), RTF_EXPND}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_EXPNDTW), RTF_EXPNDTW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_KERNING), RTF_KERNING}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_UL), RTF_UL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULW), RTF_ULW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULD), RTF_ULD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULDB), RTF_ULDB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULNONE), RTF_ULNONE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_UP), RTF_UP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DN), RTF_DN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_REVISED), RTF_REVISED}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SUB), RTF_SUB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOSUPERSUB), RTF_NOSUPERSUB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SUPER), RTF_SUPER}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHDATE), RTF_CHDATE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHTIME), RTF_CHTIME}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHPGN), RTF_CHPGN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHFTN), RTF_CHFTN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHATN), RTF_CHATN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHFTNSEP), RTF_CHFTNSEP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHFTNSEPC), RTF_CHFTNSEPC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FORMULA), RTF_FORMULA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOBREAK), RTF_NONBREAKINGSPACE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OPTHYPH), RTF_OPTIONALHYPHEN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOBRKHYPH), RTF_NONBREAKINGHYPHEN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HEXCHAR), RTF_HEX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CELL), RTF_CELL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ROW), RTF_ROW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PAR), RTF_PAR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SECT), RTF_SECT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PAGE), RTF_PAGE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_COLUMN), RTF_COLUM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LINE), RTF_LINE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TAB), RTF_TAB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SUBENTRY), RTF_SUBENTRYINDEX}, + +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DEFLANG), RTF_DEFLANG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LANG), RTF_LANG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PMMETAFILE), RTF_OSMETAFILE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DIBITMAP), RTF_DIBITMAP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_KEYCODE), RTF_KEYCODE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FN), RTF_FNKEY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ALT), RTF_ALTKEY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHIFT), RTF_SHIFTKEY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CTRL), RTF_CTRLKEY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHDPL), RTF_CHDATEL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHDPA), RTF_CHDATEA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_EMDASH), RTF_EMDASH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ENDASH), RTF_ENDASH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BULLET), RTF_BULLET}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LQUOTE), RTF_LQUOTE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RQUOTE), RTF_RQUOTE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LDBLQUOTE), RTF_LDBLQUOTE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RDBLQUOTE), RTF_RDBLQUOTE}, + +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BKMKCOLF), RTF_BKMKCOLF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BKMKCOLL), RTF_BKMKCOLL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PSOVER), RTF_PSOVER}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DOCTEMP), RTF_DOCTEMP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BINFSXN), RTF_BINFSXN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BINSXN), RTF_BINSXN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGWSXN), RTF_PGWSXN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGHSXN), RTF_PGHSXN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MARGLSXN), RTF_MARGLSXN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MARGRSXN), RTF_MARGRSXN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MARGTSXN), RTF_MARGTSXN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MARGBSXN), RTF_MARGBSXN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_GUTTERSXN), RTF_GUTTERSXN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LNDSCPSXN), RTF_LNDSCPSXN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FACPGSXN), RTF_FACPGSXN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TLEQ), RTF_TLEQ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRBTW), RTF_BRDRBTW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRBAR), RTF_BRDRBAR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRW), RTF_BRDRW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRCF), RTF_BRDRCF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ABSH), RTF_ABSH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PVPARA), RTF_PVPARA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOWRAP), RTF_NOWRAP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DFRMTXTX), RTF_DFRMTXTX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DFRMTXTY), RTF_DFRMTXTY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DROPCAPLI), RTF_DROPCAPLI}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DROPCAPT), RTF_DROPCAPT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ABSNOOVRLP), RTF_ABSNOOVRLP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSNEGX), RTF_POSNEGX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSNEGY), RTF_POSNEGY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DELETED), RTF_DELETED}, + +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHADING), RTF_SHADING}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BGHORIZ), RTF_BGHORIZ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BGVERT), RTF_BGVERT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BGFDIAG), RTF_BGFDIAG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BGBDIAG), RTF_BGBDIAG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BGCROSS), RTF_BGCROSS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BGDCROSS), RTF_BGDCROSS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BGDKHORIZ), RTF_BGDKHORIZ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BGDKVERT), RTF_BGDKVERT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BGDKFDIAG), RTF_BGDKFDIAG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BGDKBDIAG), RTF_BGDKBDIAG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BGDKCROSS), RTF_BGDKCROSS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BGDKDCROSS), RTF_BGDKDCROSS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CFPAT), RTF_CFPAT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CBPAT), RTF_CBPAT}, + +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLSHDNG), RTF_CLSHDNG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBGHORIZ), RTF_CLBGHORIZ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBGVERT), RTF_CLBGVERT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBGFDIAG), RTF_CLBGFDIAG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBGBDIAG), RTF_CLBGBDIAG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBGCROSS), RTF_CLBGCROSS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBGDCROSS), RTF_CLBGDCROSS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBGDKHOR), RTF_CLBGDKHOR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBGDKVERT), RTF_CLBGDKVERT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBGDKFDIAG), RTF_CLBGDKFDIAG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBGDKBDIAG), RTF_CLBGDKBDIAG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBGDKCROSS), RTF_CLBGDKCROSS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLBGDKDCROSS), RTF_CLBGDKDCROSS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLCFPAT), RTF_CLCFPAT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLCBPAT), RTF_CLCBPAT}, + +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AB), RTF_AB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ACAPS), RTF_ACAPS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ACF), RTF_ACF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ADDITIVE), RTF_ADDITIVE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ADN), RTF_ADN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AENDDOC), RTF_AENDDOC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AENDNOTES), RTF_AENDNOTES}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AEXPND), RTF_AEXPND}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AF), RTF_AF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFS), RTF_AFS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNBJ), RTF_AFTNBJ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNCN), RTF_AFTNCN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNALC), RTF_AFTNNALC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNAR), RTF_AFTNNAR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNAUC), RTF_AFTNNAUC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNCHI), RTF_AFTNNCHI}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNRLC), RTF_AFTNNRLC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNRUC), RTF_AFTNNRUC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNRESTART), RTF_AFTNRESTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNRSTCONT), RTF_AFTNRSTCONT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNSEP), RTF_AFTNSEP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNSEPC), RTF_AFTNSEPC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNSTART), RTF_AFTNSTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNTJ), RTF_AFTNTJ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AI), RTF_AI}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ALANG), RTF_ALANG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ALLPROT), RTF_ALLPROT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ANNOTPROT), RTF_ANNOTPROT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AOUTL), RTF_AOUTL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ASCAPS), RTF_ASCAPS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ASHAD), RTF_ASHAD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ASTRIKE), RTF_ASTRIKE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ATNAUTHOR), RTF_ATNAUTHOR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ATNICN), RTF_ATNICN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ATNREF), RTF_ATNREF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ATNTIME), RTF_ATNTIME}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ATRFEND), RTF_ATRFEND}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ATRFSTART), RTF_ATRFSTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AUL), RTF_AUL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AULD), RTF_AULD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AULDB), RTF_AULDB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AULNONE), RTF_AULNONE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AULW), RTF_AULW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AUP), RTF_AUP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BKMKPUB), RTF_BKMKPUB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRDASH), RTF_BRDRDASH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRKFRM), RTF_BRKFRM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CCHS), RTF_CCHS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CPG), RTF_CPG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CS), RTF_CS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CVMME), RTF_CVMME}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DATAFIELD), RTF_DATAFIELD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DO), RTF_DO}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DOBXCOLUMN), RTF_DOBXCOLUMN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DOBXMARGIN), RTF_DOBXMARGIN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DOBXPAGE), RTF_DOBXPAGE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DOBYMARGIN), RTF_DOBYMARGIN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DOBYPAGE), RTF_DOBYPAGE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DOBYPARA), RTF_DOBYPARA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DODHGT), RTF_DODHGT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DOLOCK), RTF_DOLOCK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPAENDHOL), RTF_DPAENDHOL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPAENDL), RTF_DPAENDL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPAENDSOL), RTF_DPAENDSOL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPAENDW), RTF_DPAENDW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPARC), RTF_DPARC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPARCFLIPX), RTF_DPARCFLIPX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPARCFLIPY), RTF_DPARCFLIPY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPASTARTHOL), RTF_DPASTARTHOL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPASTARTL), RTF_DPASTARTL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPASTARTSOL), RTF_DPASTARTSOL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPASTARTW), RTF_DPASTARTW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCALLOUT), RTF_DPCALLOUT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCOA), RTF_DPCOA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCOACCENT), RTF_DPCOACCENT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCOBESTFIT), RTF_DPCOBESTFIT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCOBORDER), RTF_DPCOBORDER}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCODABS), RTF_DPCODABS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCODBOTTOM), RTF_DPCODBOTTOM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCODCENTER), RTF_DPCODCENTER}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCODTOP), RTF_DPCODTOP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCOLENGTH), RTF_DPCOLENGTH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCOMINUSX), RTF_DPCOMINUSX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCOMINUSY), RTF_DPCOMINUSY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCOOFFSET), RTF_DPCOOFFSET}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCOSMARTA), RTF_DPCOSMARTA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCOTDOUBLE), RTF_DPCOTDOUBLE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCOTRIGHT), RTF_DPCOTRIGHT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCOTSINGLE), RTF_DPCOTSINGLE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCOTTRIPLE), RTF_DPCOTTRIPLE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCOUNT), RTF_DPCOUNT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPELLIPSE), RTF_DPELLIPSE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPENDGROUP), RTF_DPENDGROUP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPFILLBGCB), RTF_DPFILLBGCB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPFILLBGCG), RTF_DPFILLBGCG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPFILLBGCR), RTF_DPFILLBGCR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPFILLBGGRAY), RTF_DPFILLBGGRAY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPFILLBGPAL), RTF_DPFILLBGPAL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPFILLFGCB), RTF_DPFILLFGCB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPFILLFGCG), RTF_DPFILLFGCG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPFILLFGCR), RTF_DPFILLFGCR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPFILLFGGRAY), RTF_DPFILLFGGRAY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPFILLFGPAL), RTF_DPFILLFGPAL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPFILLPAT), RTF_DPFILLPAT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPGROUP), RTF_DPGROUP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPLINE), RTF_DPLINE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPLINECOB), RTF_DPLINECOB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPLINECOG), RTF_DPLINECOG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPLINECOR), RTF_DPLINECOR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPLINEDADO), RTF_DPLINEDADO}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPLINEDADODO), RTF_DPLINEDADODO}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPLINEDASH), RTF_DPLINEDASH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPLINEDOT), RTF_DPLINEDOT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPLINEGRAY), RTF_DPLINEGRAY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPLINEHOLLOW), RTF_DPLINEHOLLOW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPLINEPAL), RTF_DPLINEPAL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPLINESOLID), RTF_DPLINESOLID}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPLINEW), RTF_DPLINEW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPPOLYCOUNT), RTF_DPPOLYCOUNT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPPOLYGON), RTF_DPPOLYGON}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPPOLYLINE), RTF_DPPOLYLINE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPPTX), RTF_DPPTX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPPTY), RTF_DPPTY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPRECT), RTF_DPRECT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPROUNDR), RTF_DPROUNDR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPSHADOW), RTF_DPSHADOW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPSHADX), RTF_DPSHADX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPSHADY), RTF_DPSHADY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPTXBX), RTF_DPTXBX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPTXBXMAR), RTF_DPTXBXMAR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPTXBXTEXT), RTF_DPTXBXTEXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPX), RTF_DPX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPXSIZE), RTF_DPXSIZE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPY), RTF_DPY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPYSIZE), RTF_DPYSIZE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DS), RTF_DS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_EMSPACE), RTF_EMSPACE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ENSPACE), RTF_ENSPACE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FBIDI), RTF_FBIDI}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FET), RTF_FET}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FID), RTF_FID}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FILE), RTF_FILE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FILETBL), RTF_FILETBL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLDALT), RTF_FLDALT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FNETWORK), RTF_FNETWORK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FONTEMB), RTF_FONTEMB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FONTFILE), RTF_FONTFILE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FORMDISP), RTF_FORMDISP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FORMPROT), RTF_FORMPROT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FORMSHADE), RTF_FORMSHADE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FOSNUM), RTF_FOSNUM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FRELATIVE), RTF_FRELATIVE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNALT), RTF_FTNALT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNIL), RTF_FTNIL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNALC), RTF_FTNNALC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNAR), RTF_FTNNAR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNAUC), RTF_FTNNAUC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNCHI), RTF_FTNNCHI}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNRLC), RTF_FTNNRLC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNRUC), RTF_FTNNRUC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNRSTCONT), RTF_FTNRSTCONT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNRSTPG), RTF_FTNRSTPG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTTRUETYPE), RTF_FTTRUETYPE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FVALIDDOS), RTF_FVALIDDOS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FVALIDHPFS), RTF_FVALIDHPFS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FVALIDMAC), RTF_FVALIDMAC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FVALIDNTFS), RTF_FVALIDNTFS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HYPHAUTO), RTF_HYPHAUTO}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HYPHCAPS), RTF_HYPHCAPS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HYPHCONSEC), RTF_HYPHCONSEC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HYPHPAR), RTF_HYPHPAR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LINKSELF), RTF_LINKSELF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LINKSTYLES), RTF_LINKSTYLES}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LTRCH), RTF_LTRCH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LTRDOC), RTF_LTRDOC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LTRMARK), RTF_LTRMARK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LTRPAR), RTF_LTRPAR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LTRROW), RTF_LTRROW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LTRSECT), RTF_LTRSECT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOCOLBAL), RTF_NOCOLBAL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOEXTRASPRL), RTF_NOEXTRASPRL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOTABIND), RTF_NOTABIND}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOWIDCTLPAR), RTF_NOWIDCTLPAR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJALIAS), RTF_OBJALIAS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJALIGN), RTF_OBJALIGN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJAUTLINK), RTF_OBJAUTLINK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJCLASS), RTF_OBJCLASS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJCROPB), RTF_OBJCROPB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJCROPL), RTF_OBJCROPL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJCROPR), RTF_OBJCROPR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJCROPT), RTF_OBJCROPT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJDATA), RTF_OBJDATA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJECT), RTF_OBJECT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJEMB), RTF_OBJEMB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJH), RTF_OBJH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJICEMB), RTF_OBJICEMB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJLINK), RTF_OBJLINK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJLOCK), RTF_OBJLOCK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJNAME), RTF_OBJNAME}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJPUB), RTF_OBJPUB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJSCALEX), RTF_OBJSCALEX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJSCALEY), RTF_OBJSCALEY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJSECT), RTF_OBJSECT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJSETSIZE), RTF_OBJSETSIZE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJSUB), RTF_OBJSUB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJTIME), RTF_OBJTIME}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJTRANSY), RTF_OBJTRANSY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJUPDATE), RTF_OBJUPDATE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJW), RTF_OBJW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OTBLRUL), RTF_OTBLRUL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNHN), RTF_PGNHN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNHNSC), RTF_PGNHNSC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNHNSH), RTF_PGNHNSH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNHNSM), RTF_PGNHNSM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNHNSN), RTF_PGNHNSN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNHNSP), RTF_PGNHNSP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICBMP), RTF_PICBMP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICBPP), RTF_PICBPP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PN), RTF_PN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNACROSS), RTF_PNACROSS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNB), RTF_PNB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNCAPS), RTF_PNCAPS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNCARD), RTF_PNCARD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNCF), RTF_PNCF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNDEC), RTF_PNDEC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNF), RTF_PNF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNFS), RTF_PNFS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNHANG), RTF_PNHANG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNI), RTF_PNI}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNINDENT), RTF_PNINDENT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNLCLTR), RTF_PNLCLTR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNLCRM), RTF_PNLCRM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNLVL), RTF_PNLVL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNLVLBLT), RTF_PNLVLBLT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNLVLBODY), RTF_PNLVLBODY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNLVLCONT), RTF_PNLVLCONT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNNUMONCE), RTF_PNNUMONCE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNORD), RTF_PNORD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNORDT), RTF_PNORDT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNPREV), RTF_PNPREV}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNQC), RTF_PNQC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNQL), RTF_PNQL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNQR), RTF_PNQR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNRESTART), RTF_PNRESTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNSCAPS), RTF_PNSCAPS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNSECLVL), RTF_PNSECLVL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNSP), RTF_PNSP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNSTART), RTF_PNSTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNSTRIKE), RTF_PNSTRIKE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNTEXT), RTF_PNTEXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNTXTA), RTF_PNTXTA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNTXTB), RTF_PNTXTB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNUCLTR), RTF_PNUCLTR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNUCRM), RTF_PNUCRM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNUL), RTF_PNUL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNULD), RTF_PNULD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNULDB), RTF_PNULDB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNULNONE), RTF_PNULNONE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNULW), RTF_PNULW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PRCOLBL), RTF_PRCOLBL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PRINTDATA), RTF_PRINTDATA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PSZ), RTF_PSZ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PUBAUTO), RTF_PUBAUTO}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RESULT), RTF_RESULT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_REVAUTH), RTF_REVAUTH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_REVDTTM), RTF_REVDTTM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_REVPROT), RTF_REVPROT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_REVTBL), RTF_REVTBL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RSLTBMP), RTF_RSLTBMP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RSLTMERGE), RTF_RSLTMERGE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RSLTPICT), RTF_RSLTPICT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RSLTRTF), RTF_RSLTRTF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RSLTTXT), RTF_RSLTTXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RTLCH), RTF_RTLCH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RTLDOC), RTF_RTLDOC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RTLMARK), RTF_RTLMARK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RTLPAR), RTF_RTLPAR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RTLROW), RTF_RTLROW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_RTLSECT), RTF_RTLSECT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SEC), RTF_SEC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SECTNUM), RTF_SECTNUM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SECTUNLOCKED), RTF_SECTUNLOCKED}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SLMULT), RTF_SLMULT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SOFTCOL), RTF_SOFTCOL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SOFTLHEIGHT), RTF_SOFTLHEIGHT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SOFTLINE), RTF_SOFTLINE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SOFTPAGE), RTF_SOFTPAGE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SPRSSPBF), RTF_SPRSSPBF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SPRSTSP), RTF_SPRSTSP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SUBDOCUMENT), RTF_SUBDOCUMENT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SWPBDR), RTF_SWPBDR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TCN), RTF_TCN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRANSMF), RTF_TRANSMF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRBRDRB), RTF_TRBRDRB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRBRDRH), RTF_TRBRDRH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRBRDRL), RTF_TRBRDRL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRBRDRR), RTF_TRBRDRR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRBRDRT), RTF_TRBRDRT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRBRDRV), RTF_TRBRDRV}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRHDR), RTF_TRHDR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRKEEP), RTF_TRKEEP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRPADDB), RTF_TRPADDB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRPADDL), RTF_TRPADDL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRPADDR), RTF_TRPADDR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRPADDT), RTF_TRPADDT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRPADDFB), RTF_TRPADDFB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRPADDFL), RTF_TRPADDFL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRPADDFR), RTF_TRPADDFR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRPADDFT), RTF_TRPADDFT}, + + +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_WRAPTRSP), RTF_WRAPTRSP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_XEF), RTF_XEF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ZWJ), RTF_ZWJ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ZWNJ), RTF_ZWNJ}, + + +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ABSLOCK), RTF_ABSLOCK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ADJUSTRIGHT), RTF_ADJUSTRIGHT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNCHOSUNG), RTF_AFTNNCHOSUNG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNCNUM), RTF_AFTNNCNUM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNDBAR), RTF_AFTNNDBAR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNDBNUM), RTF_AFTNNDBNUM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNDBNUMD), RTF_AFTNNDBNUMD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNDBNUMK), RTF_AFTNNDBNUMK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNDBNUMT), RTF_AFTNNDBNUMT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNGANADA), RTF_AFTNNGANADA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNGBNUM), RTF_AFTNNGBNUM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNGBNUMD), RTF_AFTNNGBNUMD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNGBNUMK), RTF_AFTNNGBNUMK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNGBNUML), RTF_AFTNNGBNUML}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNZODIAC), RTF_AFTNNZODIAC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNZODIACD), RTF_AFTNNZODIACD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_AFTNNZODIACL), RTF_AFTNNZODIACL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ANIMTEXT), RTF_ANIMTEXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ANSICPG), RTF_ANSICPG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BACKGROUND), RTF_BACKGROUND}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BDBFHDR), RTF_BDBFHDR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BLIPTAG), RTF_BLIPTAG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BLIPUID), RTF_BLIPUID}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BLIPUPI), RTF_BLIPUPI}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRART), RTF_BRDRART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRDASHD), RTF_BRDRDASHD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRDASHDD), RTF_BRDRDASHDD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRDASHDOTSTR),RTF_BRDRDASHDOTSTR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRDASHSM), RTF_BRDRDASHSM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRINSET), RTF_BRDRINSET}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDROUTSET), RTF_BRDROUTSET}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDREMBOSS), RTF_BRDREMBOSS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRENGRAVE), RTF_BRDRENGRAVE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRFRAME), RTF_BRDRFRAME}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRTHTNLG), RTF_BRDRTHTNLG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRTHTNMG), RTF_BRDRTHTNMG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRTHTNSG), RTF_BRDRTHTNSG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRTNTHLG), RTF_BRDRTNTHLG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRTNTHMG), RTF_BRDRTNTHMG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRTNTHSG), RTF_BRDRTNTHSG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRTNTHTNLG), RTF_BRDRTNTHTNLG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRTNTHTNMG), RTF_BRDRTNTHTNMG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRTNTHTNSG), RTF_BRDRTNTHTNSG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRTRIPLE), RTF_BRDRTRIPLE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRWAVY), RTF_BRDRWAVY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDRWAVYDB), RTF_BRDRWAVYDB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CATEGORY), RTF_CATEGORY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CGRID), RTF_CGRID}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHARSCALEX), RTF_CHARSCALEX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHBGBDIAG), RTF_CHBGBDIAG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHBGCROSS), RTF_CHBGCROSS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHBGDCROSS), RTF_CHBGDCROSS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHBGDKBDIAG), RTF_CHBGDKBDIAG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHBGDKCROSS), RTF_CHBGDKCROSS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHBGDKDCROSS), RTF_CHBGDKDCROSS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHBGDKFDIAG), RTF_CHBGDKFDIAG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHBGDKHORIZ), RTF_CHBGDKHORIZ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHBGDKVERT), RTF_CHBGDKVERT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHBGFDIAG), RTF_CHBGFDIAG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHBGHORIZ), RTF_CHBGHORIZ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHBGVERT), RTF_CHBGVERT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHBRDR), RTF_CHBRDR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHCBPAT), RTF_CHCBPAT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHCFPAT), RTF_CHCFPAT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CHSHDNG), RTF_CHSHDNG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLTXLRTB), RTF_CLTXLRTB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLTXTBRL), RTF_CLTXTBRL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLVERTALB), RTF_CLVERTALB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLVERTALC), RTF_CLVERTALC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLVERTALT), RTF_CLVERTALT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLVMGF), RTF_CLVMGF}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLVMRG), RTF_CLVMRG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLTXTBRLV), RTF_CLTXTBRLV}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLTXBTLR), RTF_CLTXBTLR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CLTXLRTBV), RTF_CLTXLRTBV}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_COMPANY), RTF_COMPANY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CRAUTH), RTF_CRAUTH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_CRDATE), RTF_CRDATE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DATE), RTF_DATE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DEFLANGFE), RTF_DEFLANGFE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DFRAUTH), RTF_DFRAUTH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DFRDATE), RTF_DFRDATE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DFRSTART), RTF_DFRSTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DFRSTOP), RTF_DFRSTOP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DFRXST), RTF_DFRXST}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DGMARGIN), RTF_DGMARGIN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DNTBLNSBDB), RTF_DNTBLNSBDB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DOCTYPE), RTF_DOCTYPE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DOCVAR), RTF_DOCVAR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DPCODESCENT), RTF_DPCODESCENT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_EMBO), RTF_EMBO}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_EMFBLIP), RTF_EMFBLIP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_EXPSHRTN), RTF_EXPSHRTN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FAAUTO), RTF_FAAUTO}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FBIAS), RTF_FBIAS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFDEFRES), RTF_FFDEFRES}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFDEFTEXT), RTF_FFDEFTEXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFENTRYMCR), RTF_FFENTRYMCR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFEXITMCR), RTF_FFEXITMCR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFFORMAT), RTF_FFFORMAT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFHASLISTBOX), RTF_FFHASLISTBOX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFHELPTEXT), RTF_FFHELPTEXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFHPS), RTF_FFHPS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFL), RTF_FFL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFMAXLEN), RTF_FFMAXLEN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFNAME), RTF_FFNAME}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFOWNHELP), RTF_FFOWNHELP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFOWNSTAT), RTF_FFOWNSTAT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFPROT), RTF_FFPROT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFRECALC), RTF_FFRECALC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFRES), RTF_FFRES}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFSIZE), RTF_FFSIZE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFSTATTEXT), RTF_FFSTATTEXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFTYPE), RTF_FFTYPE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FFTYPETXT), RTF_FFTYPETXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLDTYPE), RTF_FLDTYPE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FNAME), RTF_FNAME}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FORMFIELD), RTF_FORMFIELD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FROMTEXT), RTF_FROMTEXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNCHOSUNG), RTF_FTNNCHOSUNG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNCNUM), RTF_FTNNCNUM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNDBAR), RTF_FTNNDBAR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNDBNUM), RTF_FTNNDBNUM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNDBNUMD), RTF_FTNNDBNUMD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNDBNUMK), RTF_FTNNDBNUMK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNDBNUMT), RTF_FTNNDBNUMT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNGANADA), RTF_FTNNGANADA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNGBNUM), RTF_FTNNGBNUM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNGBNUMD), RTF_FTNNGBNUMD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNGBNUMK), RTF_FTNNGBNUMK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNGBNUML), RTF_FTNNGBNUML}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNZODIAC), RTF_FTNNZODIAC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNZODIACD), RTF_FTNNZODIACD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FTNNZODIACL), RTF_FTNNZODIACL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_G), RTF_G}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_GCW), RTF_GCW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_GRIDTBL), RTF_GRIDTBL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HIGHLIGHT), RTF_HIGHLIGHT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HLFR), RTF_HLFR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HLINKBASE), RTF_HLINKBASE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HLLOC), RTF_HLLOC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HLSRC), RTF_HLSRC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ILVL), RTF_ILVL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_IMPR), RTF_IMPR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_JPEGBLIP), RTF_JPEGBLIP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LEVELFOLLOW), RTF_LEVELFOLLOW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LEVELINDENT), RTF_LEVELINDENT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LEVELJC), RTF_LEVELJC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LEVELLEGAL), RTF_LEVELLEGAL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LEVELNFC), RTF_LEVELNFC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LEVELNORESTART),RTF_LEVELNORESTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LEVELNUMBERS), RTF_LEVELNUMBERS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LEVELOLD), RTF_LEVELOLD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LEVELPREV), RTF_LEVELPREV}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LEVELPREVSPACE),RTF_LEVELPREVSPACE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LEVELSPACE), RTF_LEVELSPACE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LEVELSTARTAT), RTF_LEVELSTARTAT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LEVELTEXT), RTF_LEVELTEXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LINKVAL), RTF_LINKVAL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LIST), RTF_LIST}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LISTID), RTF_LISTID}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LISTLEVEL), RTF_LISTLEVEL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LISTNAME), RTF_LISTNAME}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LISTOVERRIDE), RTF_LISTOVERRIDE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LISTOVERRIDECOUNT), RTF_LISTOVERRIDECOUNT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LISTOVERRIDEFORMAT), RTF_LISTOVERRIDEFORMAT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LISTOVERRIDESTART), RTF_LISTOVERRIDESTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LISTOVERRIDETABLE), RTF_LISTOVERRIDETABLE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LISTRESTARTHDN),RTF_LISTRESTARTHDN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LISTSIMPLE), RTF_LISTSIMPLE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LISTTABLE), RTF_LISTTABLE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LISTTEMPLATEID),RTF_LISTTEMPLATEID}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LISTTEXT), RTF_LISTTEXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LS), RTF_LS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LYTEXCTTP), RTF_LYTEXCTTP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LYTPRTMET), RTF_LYTPRTMET}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MANAGER), RTF_MANAGER}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_MSMCAP), RTF_MSMCAP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOFCHARSWS), RTF_NOFCHARSWS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOLEAD), RTF_NOLEAD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NONSHPPICT), RTF_NONSHPPICT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOSECTEXPAND), RTF_NOSECTEXPAND}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOSNAPLINEGRID),RTF_NOSNAPLINEGRID}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOSPACEFORUL), RTF_NOSPACEFORUL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOULTRLSPC), RTF_NOULTRLSPC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOXLATTOYEN), RTF_NOXLATTOYEN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJATTPH), RTF_OBJATTPH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJHTML), RTF_OBJHTML}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OBJOCX), RTF_OBJOCX}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLDLINEWRAP), RTF_OLDLINEWRAP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OUTLINELEVEL), RTF_OUTLINELEVEL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OVERLAY), RTF_OVERLAY}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PANOSE), RTF_PANOSE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGBRDRB), RTF_PGBRDRB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGBRDRFOOT), RTF_PGBRDRFOOT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGBRDRHEAD), RTF_PGBRDRHEAD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGBRDRL), RTF_PGBRDRL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGBRDROPT), RTF_PGBRDROPT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGBRDRR), RTF_PGBRDRR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGBRDRSNAP), RTF_PGBRDRSNAP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGBRDRT), RTF_PGBRDRT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNCHOSUNG), RTF_PGNCHOSUNG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNCNUM), RTF_PGNCNUM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNDBNUMK), RTF_PGNDBNUMK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNDBNUMT), RTF_PGNDBNUMT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNGANADA), RTF_PGNGANADA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNGBNUM), RTF_PGNGBNUM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNGBNUMD), RTF_PGNGBNUMD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNGBNUMK), RTF_PGNGBNUMK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNGBNUML), RTF_PGNGBNUML}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNZODIAC), RTF_PGNZODIAC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNZODIACD), RTF_PGNZODIACD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGNZODIACL), RTF_PGNZODIACL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PICPROP), RTF_PICPROP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNAIUEO), RTF_PNAIUEO}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNAIUEOD), RTF_PNAIUEOD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNCHOSUNG), RTF_PNCHOSUNG}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNDBNUMD), RTF_PNDBNUMD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNDBNUMK), RTF_PNDBNUMK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNDBNUML), RTF_PNDBNUML}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNDBNUMT), RTF_PNDBNUMT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNGANADA), RTF_PNGANADA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNGBLIP), RTF_PNGBLIP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNGBNUM), RTF_PNGBNUM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNGBNUMD), RTF_PNGBNUMD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNGBNUMK), RTF_PNGBNUMK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNGBNUML), RTF_PNGBNUML}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNRAUTH), RTF_PNRAUTH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNRDATE), RTF_PNRDATE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNRNFC), RTF_PNRNFC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNRNOT), RTF_PNRNOT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNRPNBR), RTF_PNRPNBR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNRRGB), RTF_PNRRGB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNRSTART), RTF_PNRSTART}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNRSTOP), RTF_PNRSTOP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNRXST), RTF_PNRXST}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNZODIAC), RTF_PNZODIAC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNZODIACD), RTF_PNZODIACD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PNZODIACL), RTF_PNZODIACL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LFOLEVEL), RTF_LFOLEVEL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSYIN), RTF_POSYIN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_POSYOUT), RTF_POSYOUT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PRIVATE), RTF_PRIVATE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PROPNAME), RTF_PROPNAME}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PROPTYPE), RTF_PROPTYPE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_REVAUTHDEL), RTF_REVAUTHDEL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_REVDTTMDEL), RTF_REVDTTMDEL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SAUTOUPD), RTF_SAUTOUPD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SECTDEFAULTCL), RTF_SECTDEFAULTCL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SECTEXPAND), RTF_SECTEXPAND}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SECTLINEGRID), RTF_SECTLINEGRID}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SECTSPECIFYCL), RTF_SECTSPECIFYCL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SECTSPECIFYL), RTF_SECTSPECIFYL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHIDDEN), RTF_SHIDDEN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPBOTTOM), RTF_SHPBOTTOM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPBXCOLUMN), RTF_SHPBXCOLUMN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPBXMARGIN), RTF_SHPBXMARGIN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPBXPAGE), RTF_SHPBXPAGE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPBYMARGIN), RTF_SHPBYMARGIN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPBYPAGE), RTF_SHPBYPAGE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPBYPARA), RTF_SHPBYPARA}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPFBLWTXT), RTF_SHPFBLWTXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPFHDR), RTF_SHPFHDR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPGRP), RTF_SHPGRP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPINST), RTF_SHPINST}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPLEFT), RTF_SHPLEFT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPLID), RTF_SHPLID}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPLOCKANCHOR), RTF_SHPLOCKANCHOR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPPICT), RTF_SHPPICT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPRIGHT), RTF_SHPRIGHT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPRSLT), RTF_SHPRSLT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPTOP), RTF_SHPTOP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPTXT), RTF_SHPTXT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPWRK), RTF_SHPWRK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPWR), RTF_SHPWR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHPZ), RTF_SHPZ}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SPRSBSP), RTF_SPRSBSP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SPRSLNSP), RTF_SPRSLNSP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SPRSTSM), RTF_SPRSTSM}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_STATICVAL), RTF_STATICVAL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_STEXTFLOW), RTF_STEXTFLOW}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_STRIKED), RTF_STRIKED}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SUBFONTBYSIZE), RTF_SUBFONTBYSIZE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TCELLD), RTF_TCELLD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TIME), RTF_TIME}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TRUNCATEFONTHEIGHT), RTF_TRUNCATEFONTHEIGHT}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_UC), RTF_UC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_UD), RTF_UD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULDASH), RTF_ULDASH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULDASHD), RTF_ULDASHD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULDASHDD), RTF_ULDASHDD}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULTH), RTF_ULTH}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULWAVE), RTF_ULWAVE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULC), RTF_ULC}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_U), RTF_U}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_UPR), RTF_UPR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_USERPROPS), RTF_USERPROPS}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_VIEWKIND), RTF_VIEWKIND}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_VIEWSCALE), RTF_VIEWSCALE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_VIEWZK), RTF_VIEWZK}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_WIDCTLPAR), RTF_WIDCTLPAR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_WINDOWCAPTION), RTF_WINDOWCAPTION}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_WPEQN), RTF_WPEQN}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_WPJST), RTF_WPJST}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_WPSP), RTF_WPSP}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_YXE), RTF_YXE}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FRMTXLRTB), RTF_FRMTXLRTB}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FRMTXTBRL), RTF_FRMTXTBRL}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FRMTXBTLR), RTF_FRMTXBTLR}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FRMTXLRTBV), RTF_FRMTXLRTBV}, +{std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FRMTXTBRLV), RTF_FRMTXTBRLV}, + +// MS-2000 Tokens + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULTHD), RTF_ULTHD}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULTHDASH), RTF_ULTHDASH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULLDASH), RTF_ULLDASH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULTHLDASH), RTF_ULTHLDASH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULTHDASHD), RTF_ULTHDASHD}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULTHDASHDD), RTF_ULTHDASHDD}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULHWAVE), RTF_ULHWAVE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ULULDBWAVE), RTF_ULULDBWAVE}, + + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LOCH), RTF_LOCH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HICH), RTF_HICH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DBCH), RTF_DBCH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_LANGFE), RTF_LANGFE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ADEFLANG), RTF_ADEFLANG}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ADEFF), RTF_ADEFF}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ACCNONE), RTF_ACCNONE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ACCDOT), RTF_ACCDOT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ACCCOMMA), RTF_ACCCOMMA}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TWOINONE), RTF_TWOINONE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HORZVERT), RTF_HORZVERT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FAHANG), RTF_FAHANG}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FAVAR), RTF_FAVAR}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FACENTER), RTF_FACENTER}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FAROMAN), RTF_FAROMAN}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FAFIXED), RTF_FAFIXED}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOCWRAP), RTF_NOCWRAP}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_NOOVERFLOW), RTF_NOOVERFLOW}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_ASPALPHA), RTF_ASPALPHA}, + +// SWG specific attributes + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_GRFALIGNV), RTF_GRF_ALIGNV}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_GRFALIGNH), RTF_GRF_ALIGNH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_GRFMIRROR), RTF_GRF_MIRROR}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HEADERYB), RTF_HEADER_YB}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HEADERXL), RTF_HEADER_XL}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HEADERXR), RTF_HEADER_XR}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FOOTERYT), RTF_FOOTER_YT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FOOTERXL), RTF_FOOTER_XL}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FOOTERXR), RTF_FOOTER_XR}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HEADERYH), RTF_HEADER_YH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FOOTERYH), RTF_FOOTER_YH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BALANCEDCOLUMN),RTF_BALANCED_COLUMN}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_UPDNPROP), RTF_SWG_ESCPROP}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PRTDATA), RTF_SWG_PRTDATA}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BKMKKEY), RTF_BKMK_KEY}, + +// Attributes for fly frames + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLYPRINT), RTF_FLYPRINT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLYOPAQUE), RTF_FLYOPAQUE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLYPRTCTD), RTF_FLYPRTCTD}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLYMAINCNT), RTF_FLYMAINCNT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLYVERT), RTF_FLYVERT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLYHORZ), RTF_FLYHORZ}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DFRMTXTL), RTF_FLYOUTLEFT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DFRMTXTR), RTF_FLYOUTRIGHT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DFRMTXTU), RTF_FLYOUTUPPER}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_DFRMTXTW), RTF_FLYOUTLOWER}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLYANCHOR), RTF_FLYANCHOR}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLYCNTNT), RTF_FLY_CNTNT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLYCOLUMN), RTF_FLY_COLUMN}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLYPAGE), RTF_FLY_PAGE}, + + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDBOX), RTF_BRDBOX}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDLNCOL), RTF_BRDLINE_COL}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDLNIN), RTF_BRDLINE_IN}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDLNOUT), RTF_BRDLINE_OUT}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_BRDLNDIST), RTF_BRDLINE_DIST}, + + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHADOW), RTF_SHADOW}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHDWDIST), RTF_SHDW_DIST}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHDWSTYLE), RTF_SHDW_STYLE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHDWCOL), RTF_SHDW_COL}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHDWFCOL), RTF_SHDW_FCOL}, + + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_FLYINPARA), RTF_FLY_INPARA}, + + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGDSCTBL), RTF_PGDSCTBL}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGDSC), RTF_PGDSC}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGDSCUSE), RTF_PGDSCUSE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGDSCNXT), RTF_PGDSCNXT}, + + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HYPHEN), RTF_HYPHEN}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HYPHLEAD), RTF_HYPHLEAD}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HYPHTRAIL), RTF_HYPHTRAIL}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_HYPHMAX), RTF_HYPHMAX}, + + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_TLSWG), RTF_TLSWG}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGBRK), RTF_PGBRK}, + + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_PGDSCNO), RTF_PGDSCNO}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SOUTLVL), RTF_SOUTLVL}, + + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SHP), RTF_SHP}, + /* + {OUStringLiteral(OOO_STRING_SVTOOLS_RTF_SHPLEFT), RTF_SHPLEFT} + {OUStringLiteral(OOO_STRING_SVTOOLS_RTF_SHPTOP), RTF_SHPTOP} + {OUStringLiteral(OOO_STRING_SVTOOLS_RTF_SHPBOTTOM), RTF_SHPBOTTOM} + {OUStringLiteral(OOO_STRING_SVTOOLS_RTF_SHPRIGHT), RTF_SHPRIGHT} + */ + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SN), RTF_SN}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SV), RTF_SV}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_SP), RTF_SP}, + +// Support for overline attributes + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OL), RTF_OL}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLW), RTF_OLW}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLD), RTF_OLD}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLDB), RTF_OLDB}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLNONE), RTF_OLNONE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLDASH), RTF_OLDASH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLDASHD), RTF_OLDASHD}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLDASHDD), RTF_OLDASHDD}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLTH), RTF_OLTH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLWAVE), RTF_OLWAVE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLC), RTF_OLC}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLTHD), RTF_OLTHD}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLTHDASH), RTF_OLTHDASH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLLDASH), RTF_OLLDASH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLTHLDASH), RTF_OLTHLDASH}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLTHDASHD), RTF_OLTHDASHD}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLTHDASHDD), RTF_OLTHDASHDD}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLHWAVE), RTF_OLHWAVE}, + {std::u16string_view(u"" OOO_STRING_SVTOOLS_RTF_OLOLDBWAVE), RTF_OLOLDBWAVE} +}; + + +int GetRTFToken( std::u16string_view rSearch ) +{ + if( !bSortKeyWords ) + { + std::sort( std::begin(aRTFTokenTab), std::end(aRTFTokenTab), + [](const RTF_TokenEntry & lhs, const RTF_TokenEntry & rhs) + { + return lhs.sToken < rhs.sToken; + } ); + bSortKeyWords = true; + } + + auto findCompare = [](const RTF_TokenEntry & lhs, std::u16string_view s) + { + return o3tl::compareToIgnoreAsciiCase(s, lhs.sToken) > 0; + }; + auto findIt = std::lower_bound( std::begin(aRTFTokenTab), std::end(aRTFTokenTab), rSearch, findCompare); + if (findIt != std::end(aRTFTokenTab) && o3tl::compareToIgnoreAsciiCase(rSearch, findIt->sToken)==0) + return findIt->nToken; + + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/svrtf/rtfout.cxx b/svtools/source/svrtf/rtfout.cxx new file mode 100644 index 000000000..b06a04eed --- /dev/null +++ b/svtools/source/svrtf/rtfout.cxx @@ -0,0 +1,192 @@ +/* -*- 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/debug.hxx> +#include <tools/stream.hxx> +#include <tools/solar.h> +#include <rtl/string.hxx> +#include <svtools/rtfkeywd.hxx> +#include <svtools/rtfout.hxx> + +namespace { + +SvStream& Out_Hex( SvStream& rStream, sal_uLong nHex, sal_uInt8 nLen ) +{ + char aNToABuf[] = "0000000000000000"; + + DBG_ASSERT( nLen < sizeof(aNToABuf), "too many places" ); + if( nLen >= sizeof(aNToABuf) ) + nLen = (sizeof(aNToABuf)-1); + + // set pointer to end of buffer + char* pStr = aNToABuf + (sizeof(aNToABuf)-1); + for( sal_uInt8 n = 0; n < nLen; ++n ) + { + *(--pStr) = static_cast<char>(nHex & 0xf ) + 48; + if( *pStr > '9' ) + *pStr += 39; + nHex >>= 4; + } + return rStream.WriteCharPtr( pStr ); +} + +// Ideally, this function should work on (sal_uInt32) Unicode scalar values +// instead of (sal_Unicode) UTF-16 code units. However, at least "Rich Text +// Format (RTF) Specification Version 1.9.1" available at +// <https://www.microsoft.com/en-us/download/details.aspx?id=10725> does not +// look like it allows non-BMP Unicode characters >= 0x10000 in the \uN notation +// (it only talks about "Unicode character", but then explains how values of N +// greater than 32767 will be expressed as negative signed 16-bit numbers, so +// that smells like \uN is limited to BMP). +// However the "Mathematics" section has an example that shows the code point +// U+1D44E being encoded as UTF-16 surrogate pair "\u-10187?\u-9138?", so +// sal_Unicode actually works fine here. +SvStream& Out_Char(SvStream& rStream, sal_Unicode c, + int *pUCMode, rtl_TextEncoding eDestEnc) +{ + const char* pStr = nullptr; + switch (c) + { + case 0x1: + case 0x2: + // this are control character of our textattributes and will never be + // written + break; + case 0xA0: + rStream.WriteCharPtr( "\\~" ); + break; + case 0xAD: + rStream.WriteCharPtr( "\\-" ); + break; + case 0x2011: + rStream.WriteCharPtr( "\\_" ); + break; + case '\n': + pStr = OOO_STRING_SVTOOLS_RTF_LINE; + break; + case '\t': + pStr = OOO_STRING_SVTOOLS_RTF_TAB; + break; + default: + switch(c) + { + case 149: + pStr = OOO_STRING_SVTOOLS_RTF_BULLET; + break; + case 150: + pStr = OOO_STRING_SVTOOLS_RTF_ENDASH; + break; + case 151: + pStr = OOO_STRING_SVTOOLS_RTF_EMDASH; + break; + case 145: + pStr = OOO_STRING_SVTOOLS_RTF_LQUOTE; + break; + case 146: + pStr = OOO_STRING_SVTOOLS_RTF_RQUOTE; + break; + case 147: + pStr = OOO_STRING_SVTOOLS_RTF_LDBLQUOTE; + break; + case 148: + pStr = OOO_STRING_SVTOOLS_RTF_RDBLQUOTE; + break; + } + + if (pStr) + break; + + switch (c) + { + case '\\': + case '}': + case '{': + rStream.WriteChar( '\\' ).WriteChar( char(c) ); + break; + default: + if (c >= ' ' && c <= '~') + rStream.WriteChar( char(c) ); + else + { + //If we can't convert to the dest encoding, or if + //it's an uncommon multibyte sequence which most + //readers won't be able to handle correctly, then + //export as unicode + OUString sBuf(&c, 1); + OString sConverted; + sal_uInt32 const nFlags = + RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR | + RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR; + bool bWriteAsUnicode = !(sBuf.convertToString(&sConverted, + eDestEnc, nFlags)) + || (RTL_TEXTENCODING_UTF8==eDestEnc); // #i43933# do not export UTF-8 chars in RTF; + if (bWriteAsUnicode) + { + (void)sBuf.convertToString(&sConverted, + eDestEnc, OUSTRING_TO_OSTRING_CVTFLAGS); + } + const sal_Int32 nLen = sConverted.getLength(); + + if (bWriteAsUnicode && pUCMode) + { + // then write as unicode - character + if (*pUCMode != nLen) + { + // #i47831# add an additional whitespace, so that + // "document whitespaces" are not ignored.; + rStream.WriteCharPtr( "\\uc" ) + .WriteOString( OString::number(nLen) ).WriteCharPtr( " " ); + *pUCMode = nLen; + } + rStream.WriteCharPtr( "\\u" ) + .WriteCharPtr( OString::number( + static_cast<sal_Int32>(c)).getStr() ); + } + + for (sal_Int32 nI = 0; nI < nLen; ++nI) + { + rStream.WriteCharPtr( "\\'" ); + Out_Hex(rStream, sConverted[nI], 2); + } + } + break; + } + break; + } + + if (pStr) + rStream.WriteCharPtr( pStr ).WriteChar( ' ' ); + + return rStream; +} + +} + +SvStream& RTFOutFuncs::Out_String( SvStream& rStream, const OUString& rStr, + rtl_TextEncoding eDestEnc) +{ + int nUCMode = 1; + for (sal_Int32 n = 0; n < rStr.getLength(); ++n) + Out_Char(rStream, rStr[n], &nUCMode, eDestEnc); + if (nUCMode != 1) + rStream.WriteCharPtr( "\\uc1" ).WriteCharPtr( " " ); // #i47831# add an additional whitespace, so that "document whitespaces" are not ignored.; + return rStream; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/svrtf/svparser.cxx b/svtools/source/svrtf/svparser.cxx new file mode 100644 index 000000000..d4b22fe13 --- /dev/null +++ b/svtools/source/svrtf/svparser.cxx @@ -0,0 +1,671 @@ +/* -*- 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 <svtools/svparser.hxx> +#include <svtools/htmltokn.h> +#include <tools/stream.hxx> +#include <tools/debug.hxx> +#include <rtl/textcvt.h> +#include <rtl/tencinfo.h> +#include <rtl/character.hxx> +#include <sal/log.hxx> +#include <unicode/ucsdet.h> + +#include <vector> + +// structure to store the actual data +template<typename T> +struct SvParser_Impl +{ + OUString aToken; // gescanntes Token + sal_uInt64 nFilePos; // actual position in stream + sal_uInt32 nlLineNr; // actual line number + sal_uInt32 nlLinePos; // actual column number + tools::Long nTokenValue; // extra value (RTF) + bool bTokenHasValue; // indicates whether nTokenValue is valid + T nToken; // actual Token + sal_uInt32 nNextCh; // actual character + T nSaveToken; // the token from Continue + + rtl_TextToUnicodeConverter hConv; + rtl_TextToUnicodeContext hContext; + + SvParser_Impl() + : nFilePos(0) + , nlLineNr(0) + , nlLinePos(0) + , nTokenValue(0) + , bTokenHasValue(false) + , nToken(static_cast<T>(0)) + , nNextCh(0) + , nSaveToken(static_cast<T>(0)) + , hConv( nullptr ) + , hContext( reinterpret_cast<rtl_TextToUnicodeContext>(1) ) + { + } + +}; + + +template<typename T> +SvParser<T>::TokenStackType::TokenStackType() + : nTokenValue(0) + , bTokenHasValue(false) + , nTokenId(static_cast<T>(0)) +{ +} + +// Constructor +template<typename T> +SvParser<T>::SvParser( SvStream& rIn, sal_uInt8 nStackSize ) + : rInput( rIn ) + , nlLineNr( 1 ) + , nlLinePos( 1 ) + , pImplData( nullptr ) + , m_nTokenIndex(0) + , nTokenValue( 0 ) + , bTokenHasValue( false ) + , eState( SvParserState::NotStarted ) + , eSrcEnc( RTL_TEXTENCODING_DONTKNOW ) + , nNextChPos(0) + , nNextCh(0) + , bSwitchToUCS2(false) + , bRTF_InTextRead(false) + , nTokenStackSize( nStackSize ) + , nTokenStackPos( 0 ) +{ + eState = SvParserState::NotStarted; + if( nTokenStackSize < 3 ) + nTokenStackSize = 3; + pTokenStack.reset(new TokenStackType[ nTokenStackSize ]); + pTokenStackPos = pTokenStack.get(); +} + +template<typename T> +SvParser<T>::~SvParser() +{ + if( pImplData && pImplData->hConv ) + { + rtl_destroyTextToUnicodeContext( pImplData->hConv, + pImplData->hContext ); + rtl_destroyTextToUnicodeConverter( pImplData->hConv ); + } + + pTokenStack.reset(); +} + +template<typename T> SvParserState SvParser<T>::GetStatus() const { return eState; } +template<typename T> sal_uInt32 SvParser<T>::GetLineNr() const { return nlLineNr; } +template<typename T> sal_uInt32 SvParser<T>::GetLinePos() const { return nlLinePos; } +template<typename T> void SvParser<T>::IncLineNr() { ++nlLineNr; } +template<typename T> sal_uInt32 SvParser<T>::IncLinePos() { return ++nlLinePos; } +template<typename T> void SvParser<T>::SetLineNr( sal_uInt32 nlNum ) { nlLineNr = nlNum; } +template<typename T> void SvParser<T>::SetLinePos( sal_uInt32 nlPos ) { nlLinePos = nlPos; } +template<typename T> bool SvParser<T>::IsParserWorking() const { return SvParserState::Working == eState; } +template<typename T> rtl_TextEncoding SvParser<T>::GetSrcEncoding() const { return eSrcEnc; } +template<typename T> void SvParser<T>::SetSwitchToUCS2( bool bSet ) { bSwitchToUCS2 = bSet; } +template<typename T> bool SvParser<T>::IsSwitchToUCS2() const { return bSwitchToUCS2; } +template<typename T> sal_uInt16 SvParser<T>::GetCharSize() const { return (RTL_TEXTENCODING_UCS2 == eSrcEnc) ? 2 : 1; } +template<typename T> Link<LinkParamNone*,void> SvParser<T>::GetAsynchCallLink() const +{ + return LINK( const_cast<SvParser*>(this), SvParser, NewDataRead ); +} + +template<typename T> +void SvParser<T>::ClearTxtConvContext() +{ + if( pImplData && pImplData->hConv ) + rtl_resetTextToUnicodeContext( pImplData->hConv, pImplData->hContext ); +} + +template<typename T> +void SvParser<T>::SetSrcEncoding( rtl_TextEncoding eEnc ) +{ + if( eEnc == eSrcEnc ) + return; + + if( pImplData && pImplData->hConv ) + { + rtl_destroyTextToUnicodeContext( pImplData->hConv, + pImplData->hContext ); + rtl_destroyTextToUnicodeConverter( pImplData->hConv ); + pImplData->hConv = nullptr; + pImplData->hContext = reinterpret_cast<rtl_TextToUnicodeContext>(1); + } + + if( rtl_isOctetTextEncoding(eEnc) || + RTL_TEXTENCODING_UCS2 == eEnc ) + { + eSrcEnc = eEnc; + if( !pImplData ) + pImplData.reset(new SvParser_Impl<T>); + pImplData->hConv = rtl_createTextToUnicodeConverter( eSrcEnc ); + DBG_ASSERT( pImplData->hConv, + "SvParser::SetSrcEncoding: no converter for source encoding" ); + if( !pImplData->hConv ) + eSrcEnc = RTL_TEXTENCODING_DONTKNOW; + else + pImplData->hContext = + rtl_createTextToUnicodeContext( pImplData->hConv ); + } + else + { + SAL_WARN( "svtools", + "SvParser::SetSrcEncoding: invalid source encoding" ); + eSrcEnc = RTL_TEXTENCODING_DONTKNOW; + } +} + +template<typename T> +void SvParser<T>::RereadLookahead() +{ + rInput.Seek(nNextChPos); + nNextCh = GetNextChar(); +} + +template<typename T> +sal_uInt32 SvParser<T>::GetNextChar() +{ + sal_uInt32 c = 0U; + + // When reading multiple bytes, we don't have to care about the file + // position when we run into the pending state. The file position is + // maintained by SaveState/RestoreState. + if( bSwitchToUCS2 && 0 == rInput.Tell() ) + { + rInput.StartReadingUnicodeText(RTL_TEXTENCODING_DONTKNOW); + if (rInput.good()) + { + sal_uInt64 nPos = rInput.Tell(); + if (nPos == 2) + eSrcEnc = RTL_TEXTENCODING_UCS2; + else if (nPos == 3) + SetSrcEncoding(RTL_TEXTENCODING_UTF8); + else // Try to detect encoding without BOM + { + std::vector<char> buf(65535); // Arbitrarily chosen 64KiB buffer + const size_t nSize = rInput.ReadBytes(buf.data(), buf.size()); + rInput.Seek(0); + if (nSize > 0) + { + UErrorCode uerr = U_ZERO_ERROR; + UCharsetDetector* ucd = ucsdet_open(&uerr); + ucsdet_setText(ucd, buf.data(), nSize, &uerr); + if (const UCharsetMatch* match = ucsdet_detect(ucd, &uerr)) + { + const char* pEncodingName = ucsdet_getName(match, &uerr); + + if (U_SUCCESS(uerr)) + { + if (strcmp("UTF-8", pEncodingName) == 0) + { + SetSrcEncoding(RTL_TEXTENCODING_UTF8); + } + else if (strcmp("UTF-16LE", pEncodingName) == 0) + { + eSrcEnc = RTL_TEXTENCODING_UCS2; + rInput.SetEndian(SvStreamEndian::LITTLE); + } + else if (strcmp("UTF-16BE", pEncodingName) == 0) + { + eSrcEnc = RTL_TEXTENCODING_UCS2; + rInput.SetEndian(SvStreamEndian::BIG); + } + } + } + + ucsdet_close(ucd); + } + } + } + bSwitchToUCS2 = false; + } + + bool bErr; + nNextChPos = rInput.Tell(); + + if( RTL_TEXTENCODING_UCS2 == eSrcEnc ) + { + sal_Unicode cUC; + rInput.ReadUtf16(cUC); + bErr = !rInput.good(); + if( !bErr ) + { + c = cUC; + if (rtl::isHighSurrogate(cUC)) + { + const sal_uInt64 nPos = rInput.Tell(); + rInput.ReadUtf16(cUC); + if (rtl::isLowSurrogate(cUC)) // can only be true when ReadUtf16 succeeded + c = rtl::combineSurrogates(c, cUC); + else + rInput.Seek(nPos); // process lone high surrogate + } + } + } + else + { + sal_Size nChars = 0; + do + { + char c1; // signed, that's the text converter expects + rInput.ReadChar( c1 ); + bErr = !rInput.good(); + if( !bErr ) + { + if ( + RTL_TEXTENCODING_DONTKNOW == eSrcEnc || + RTL_TEXTENCODING_SYMBOL == eSrcEnc + ) + { + // no conversion shall take place + c = reinterpret_cast<unsigned char&>( c1 ); + nChars = 1; + } + else + { + assert(pImplData && pImplData->hConv && "no text converter!"); + + sal_Unicode cUC; + sal_uInt32 nInfo = 0; + sal_Size nCvtBytes; + nChars = rtl_convertTextToUnicode( + pImplData->hConv, pImplData->hContext, + &c1, 1, &cUC, 1, + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR| + RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR| + RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR, + &nInfo, &nCvtBytes); + if( (nInfo&RTL_TEXTTOUNICODE_INFO_SRCBUFFERTOOSMALL) != 0 ) + { + // The conversion wasn't successful because we haven't + // read enough characters. + if( pImplData->hContext != reinterpret_cast<rtl_TextToUnicodeContext>(1) ) + { + sal_Unicode sCh[2]; + while( (nInfo&RTL_TEXTTOUNICODE_INFO_SRCBUFFERTOOSMALL) != 0 ) + { + rInput.ReadChar( c1 ); + bErr = !rInput.good(); + if( bErr ) + break; + + nChars = rtl_convertTextToUnicode( + pImplData->hConv, pImplData->hContext, + &c1, 1, sCh , 2, + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR| + RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR| + RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR, + &nInfo, &nCvtBytes); + } + if( !bErr ) + { + if( 1 == nChars && 0 == nInfo ) + { + c = sal_uInt32( sCh[0] ); + } + else if( 2 == nChars && 0 == nInfo ) + { + c = rtl::combineSurrogates( sCh[0], sCh[1] ); + } + else if( 0 != nChars || 0 != nInfo ) + { + DBG_ASSERT( (nInfo&RTL_TEXTTOUNICODE_INFO_SRCBUFFERTOOSMALL) == 0, + "source buffer is too small" ); + DBG_ASSERT( (nInfo&~(RTL_TEXTTOUNICODE_INFO_SRCBUFFERTOOSMALL)) == 0, + "there is a conversion error" ); + DBG_ASSERT( 0 == nChars, + "there is a converted character, but an error" ); + // There are still errors, but nothing we can + // do + c = '?'; + nChars = 1; + } + } + } + else + { + char sBuffer[10]; + sBuffer[0] = c1; + sal_uInt16 nLen = 1; + while( (nInfo&RTL_TEXTTOUNICODE_INFO_SRCBUFFERTOOSMALL) != 0 && + nLen < 10 ) + { + rInput.ReadChar( c1 ); + bErr = !rInput.good(); + if( bErr ) + break; + + sBuffer[nLen++] = c1; + nChars = rtl_convertTextToUnicode( + pImplData->hConv, nullptr, sBuffer, nLen, &cUC, 1, + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR| + RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR| + RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR, + &nInfo, &nCvtBytes); + } + if( !bErr ) + { + if( 1 == nChars && 0 == nInfo ) + { + DBG_ASSERT( nCvtBytes == nLen, + "no all bytes have been converted!" ); + c = cUC; + } + else + { + DBG_ASSERT( (nInfo&RTL_TEXTTOUNICODE_INFO_SRCBUFFERTOOSMALL) == 0, + "source buffer is too small" ); + DBG_ASSERT( (nInfo&~(RTL_TEXTTOUNICODE_INFO_SRCBUFFERTOOSMALL)) == 0, + "there is a conversion error" ); + DBG_ASSERT( 0 == nChars, + "there is a converted character, but an error" ); + + // There are still errors, so we use the first + // character and restart after that. + c = reinterpret_cast<unsigned char&>( sBuffer[0] ); + rInput.SeekRel( -(nLen-1) ); + nChars = 1; + } + } + } + } + else if( 1 == nChars && 0 == nInfo ) + { + // The conversion was successful + DBG_ASSERT( nCvtBytes == 1, + "no all bytes have been converted!" ); + c = cUC; + } + else if( 0 != nChars || 0 != nInfo ) + { + DBG_ASSERT( 0 == nChars, + "there is a converted character, but an error" ); + DBG_ASSERT( 0 != nInfo, + "there is no converted character and no error" ); + // #73398#: If the character could not be converted, + // because a conversion is not available, do no conversion at all. + c = reinterpret_cast<unsigned char&>( c1 ); + nChars = 1; + + } + } + } + } + while( 0 == nChars && !bErr ); + } + + if ( ! rtl::isUnicodeScalarValue( c ) ) + c = '?' ; + + if( bErr ) + { + if( ERRCODE_IO_PENDING == rInput.GetError() ) + { + eState = SvParserState::Pending; + return c; + } + else + return sal_Unicode(EOF); + } + + if( c == '\n' ) + { + IncLineNr(); + SetLinePos( 1 ); + } + else + IncLinePos(); + + return c; +} + +template<typename T> +T SvParser<T>::GetNextToken() +{ + T nRet = static_cast<T>(0); + + if( !nTokenStackPos ) + { + aToken.setLength( 0 ); // empty token buffer + nTokenValue = -1; // marker for no value read + bTokenHasValue = false; + + nRet = GetNextToken_(); + if( SvParserState::Pending == eState ) + return nRet; + } + + ++pTokenStackPos; + if( pTokenStackPos == pTokenStack.get() + nTokenStackSize ) + pTokenStackPos = pTokenStack.get(); + + // pop from stack ?? + if( nTokenStackPos ) + { + --nTokenStackPos; + nTokenValue = pTokenStackPos->nTokenValue; + bTokenHasValue = pTokenStackPos->bTokenHasValue; + aToken = pTokenStackPos->sToken; + nRet = pTokenStackPos->nTokenId; + ++m_nTokenIndex; + } + // no, now push actual value on stack + else if( SvParserState::Working == eState ) + { + pTokenStackPos->sToken = aToken; + pTokenStackPos->nTokenValue = nTokenValue; + pTokenStackPos->bTokenHasValue = bTokenHasValue; + pTokenStackPos->nTokenId = nRet; + ++m_nTokenIndex; + } + else if( SvParserState::Accepted != eState && SvParserState::Pending != eState ) + eState = SvParserState::Error; // an error occurred + + return nRet; +} + +template<typename T> +T SvParser<T>::SkipToken( short nCnt ) // "skip" n Tokens backward +{ + pTokenStackPos = GetStackPtr( nCnt ); + short nTmp = nTokenStackPos - nCnt; + if( nTmp < 0 ) + nTmp = 0; + else if( nTmp > nTokenStackSize ) + nTmp = nTokenStackSize; + nTokenStackPos = sal_uInt8(nTmp); + + m_nTokenIndex -= nTmp; + + // restore values + aToken = pTokenStackPos->sToken; + nTokenValue = pTokenStackPos->nTokenValue; + bTokenHasValue = pTokenStackPos->bTokenHasValue; + + return pTokenStackPos->nTokenId; +} + +template<typename T> +typename SvParser<T>::TokenStackType* SvParser<T>::GetStackPtr( short nCnt ) +{ + sal_uInt8 nCurrentPos = sal_uInt8(pTokenStackPos - pTokenStack.get()); + if( nCnt > 0 ) + { + if( nCnt >= nTokenStackSize ) + nCnt = (nTokenStackSize-1); + if( nCurrentPos + nCnt < nTokenStackSize ) + nCurrentPos = sal::static_int_cast< sal_uInt8 >(nCurrentPos + nCnt); + else + nCurrentPos = sal::static_int_cast< sal_uInt8 >( + nCurrentPos + (nCnt - nTokenStackSize)); + } + else if( nCnt < 0 ) + { + if( -nCnt >= nTokenStackSize ) + nCnt = -nTokenStackSize+1; + if( -nCnt <= nCurrentPos ) + nCurrentPos = sal::static_int_cast< sal_uInt8 >(nCurrentPos + nCnt); + else + nCurrentPos = sal::static_int_cast< sal_uInt8 >( + nCurrentPos + (nCnt + nTokenStackSize)); + } + return pTokenStack.get() + nCurrentPos; +} + +// to read asynchronous from SvStream + +template<typename T> +T SvParser<T>::GetSaveToken() const +{ + return pImplData ? pImplData->nSaveToken : static_cast<T>(0); +} + +template<typename T> +void SvParser<T>::SaveState( T nToken ) +{ + // save actual status + if( !pImplData ) + { + pImplData.reset(new SvParser_Impl<T>); + pImplData->nSaveToken = static_cast<T>(0); + } + + pImplData->nFilePos = rInput.Tell(); + pImplData->nToken = nToken; + + pImplData->aToken = aToken; + pImplData->nlLineNr = nlLineNr; + pImplData->nlLinePos = nlLinePos; + pImplData->nTokenValue= nTokenValue; + pImplData->bTokenHasValue = bTokenHasValue; + pImplData->nNextCh = nNextCh; +} + +template<typename T> +void SvParser<T>::RestoreState() +{ + // restore old status + if( !pImplData ) + return; + + if( ERRCODE_IO_PENDING == rInput.GetError() ) + rInput.ResetError(); + aToken = pImplData->aToken; + nlLineNr = pImplData->nlLineNr; + nlLinePos = pImplData->nlLinePos; + nTokenValue= pImplData->nTokenValue; + bTokenHasValue=pImplData->bTokenHasValue; + nNextCh = pImplData->nNextCh; + + pImplData->nSaveToken = pImplData->nToken; + + rInput.Seek( pImplData->nFilePos ); +} + +template<typename T> +void SvParser<T>::Continue( T ) +{ +} + + +// expanded out version of +// IMPL_LINK_NOARG( SvParser, NewDataRead, LinkParamNone*, void ) +// since it can't cope with template methods +template<typename T> +void SvParser<T>::LinkStubNewDataRead(void * instance, LinkParamNone* data) { + return static_cast<SvParser<T> *>(instance)->NewDataRead(data); +} +template<typename T> +void SvParser<T>::NewDataRead(SAL_UNUSED_PARAMETER LinkParamNone*) +{ + switch( eState ) + { + case SvParserState::Pending: + eState = SvParserState::Working; + RestoreState(); + + Continue( pImplData->nToken ); + + if( ERRCODE_IO_PENDING == rInput.GetError() ) + rInput.ResetError(); + + if( SvParserState::Pending != eState ) + ReleaseRef(); // ready otherwise! + break; + + case SvParserState::NotStarted: + case SvParserState::Working: + break; + + default: + ReleaseRef(); // ready otherwise! + break; + } +} + +template class SVT_DLLPUBLIC SvParser<int>; +template class SVT_DLLPUBLIC SvParser<HtmlTokenId>; + +/*======================================================================== + * + * SvKeyValueIterator. + * + *======================================================================*/ + +typedef std::vector<SvKeyValue> SvKeyValueList_Impl; + +struct SvKeyValueIterator::Impl +{ + SvKeyValueList_Impl maList; + sal_uInt16 mnPos; + + Impl() : mnPos(0) {} +}; + +SvKeyValueIterator::SvKeyValueIterator() : mpImpl(new Impl) {} + +SvKeyValueIterator::~SvKeyValueIterator() = default; + +bool SvKeyValueIterator::GetFirst (SvKeyValue &rKeyVal) +{ + mpImpl->mnPos = mpImpl->maList.size(); + return GetNext (rKeyVal); +} + +bool SvKeyValueIterator::GetNext (SvKeyValue &rKeyVal) +{ + if (mpImpl->mnPos > 0) + { + rKeyVal = mpImpl->maList[--mpImpl->mnPos]; + return true; + } + else + { + // Nothing to do. + return false; + } +} + +void SvKeyValueIterator::Append (const SvKeyValue &rKeyVal) +{ + mpImpl->maList.push_back(rKeyVal); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/table/cellvalueconversion.cxx b/svtools/source/table/cellvalueconversion.cxx new file mode 100644 index 000000000..53177af0d --- /dev/null +++ b/svtools/source/table/cellvalueconversion.cxx @@ -0,0 +1,376 @@ +/* -*- 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 "cellvalueconversion.hxx" + +#include <com/sun/star/util/NumberFormatsSupplier.hpp> +#include <com/sun/star/util/NumberFormatter.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/Time.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/util/XNumberFormatTypes.hpp> +#include <com/sun/star/util/NumberFormat.hpp> +#include <sal/log.hxx> +#include <tools/date.hxx> +#include <tools/time.hxx> +#include <tools/diagnose_ex.h> +#include <tools/long.hxx> +#include <unotools/syslocale.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <comphelper/processfactory.hxx> + +#include <limits> +#include <memory> + +namespace svt +{ +using namespace ::com::sun::star::uno; +using ::com::sun::star::util::XNumberFormatter; +using ::com::sun::star::util::NumberFormatter; +using ::com::sun::star::util::XNumberFormatsSupplier; +using ::com::sun::star::util::NumberFormatsSupplier; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::lang::Locale; +using ::com::sun::star::util::DateTime; +using ::com::sun::star::util::XNumberFormatTypes; + +namespace NumberFormat = ::com::sun::star::util::NumberFormat; + +//= helper + +namespace +{ +double lcl_convertDateToDays(sal_uInt16 const i_day, sal_uInt16 const i_month, + sal_Int16 const i_year) +{ + tools::Long const nNullDateDays = ::Date::DateToDays(1, 1, 1900); + tools::Long const nValueDateDays = ::Date::DateToDays(i_day, i_month, i_year); + + return nValueDateDays - nNullDateDays; +} + +double lcl_convertTimeToDays(tools::Long const i_hours, tools::Long const i_minutes, + tools::Long const i_seconds, tools::Long const i_100thSeconds) +{ + return tools::Time(i_hours, i_minutes, i_seconds, i_100thSeconds).GetTimeInDays(); +} +} + +//= StandardFormatNormalizer + +StandardFormatNormalizer::StandardFormatNormalizer(Reference<XNumberFormatter> const& i_formatter, + ::sal_Int32 const i_numberFormatType) + : m_nFormatKey(0) +{ + try + { + ENSURE_OR_THROW(i_formatter.is(), "StandardFormatNormalizer: no formatter!"); + Reference<XNumberFormatsSupplier> const xSupplier(i_formatter->getNumberFormatsSupplier(), + UNO_SET_THROW); + Reference<XNumberFormatTypes> const xTypes(xSupplier->getNumberFormats(), UNO_QUERY_THROW); + m_nFormatKey = xTypes->getStandardFormat(i_numberFormatType, + SvtSysLocale().GetLanguageTag().getLocale()); + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.table"); + } +} + +//= DoubleNormalization + +namespace +{ +class DoubleNormalization : public StandardFormatNormalizer +{ +public: + explicit DoubleNormalization(Reference<XNumberFormatter> const& i_formatter) + : StandardFormatNormalizer(i_formatter, NumberFormat::NUMBER) + { + } + + virtual double convertToDouble(Any const& i_value) const override + { + double returnValue = std::numeric_limits<double>::quiet_NaN(); + OSL_VERIFY(i_value >>= returnValue); + return returnValue; + } +}; + +//= IntegerNormalization + +class IntegerNormalization : public StandardFormatNormalizer +{ +public: + explicit IntegerNormalization(Reference<XNumberFormatter> const& i_formatter) + : StandardFormatNormalizer(i_formatter, NumberFormat::NUMBER) + { + } + + virtual double convertToDouble(Any const& i_value) const override + { + sal_Int64 value(0); + OSL_VERIFY(i_value >>= value); + return value; + } +}; + +//= BooleanNormalization + +class BooleanNormalization : public StandardFormatNormalizer +{ +public: + explicit BooleanNormalization(Reference<XNumberFormatter> const& i_formatter) + : StandardFormatNormalizer(i_formatter, NumberFormat::LOGICAL) + { + } + + virtual double convertToDouble(Any const& i_value) const override + { + bool value(false); + OSL_VERIFY(i_value >>= value); + return value ? 1 : 0; + } +}; + +//= DateTimeNormalization + +class DateTimeNormalization : public StandardFormatNormalizer +{ +public: + explicit DateTimeNormalization(Reference<XNumberFormatter> const& i_formatter) + : StandardFormatNormalizer(i_formatter, NumberFormat::DATETIME) + { + } + + virtual double convertToDouble(Any const& i_value) const override + { + double returnValue = std::numeric_limits<double>::quiet_NaN(); + + // extract actual UNO value + DateTime aDateTimeValue; + ENSURE_OR_RETURN(i_value >>= aDateTimeValue, "allowed for DateTime values only", + returnValue); + + // date part + returnValue + = lcl_convertDateToDays(aDateTimeValue.Day, aDateTimeValue.Month, aDateTimeValue.Year); + + // time part + returnValue += lcl_convertTimeToDays(aDateTimeValue.Hours, aDateTimeValue.Minutes, + aDateTimeValue.Seconds, aDateTimeValue.NanoSeconds); + + // done + return returnValue; + } +}; + +//= DateNormalization + +class DateNormalization : public StandardFormatNormalizer +{ +public: + explicit DateNormalization(Reference<XNumberFormatter> const& i_formatter) + : StandardFormatNormalizer(i_formatter, NumberFormat::DATE) + { + } + + virtual double convertToDouble(Any const& i_value) const override + { + double returnValue = std::numeric_limits<double>::quiet_NaN(); + + // extract + css::util::Date aDateValue; + ENSURE_OR_RETURN(i_value >>= aDateValue, "allowed for Date values only", returnValue); + + // convert + returnValue = lcl_convertDateToDays(aDateValue.Day, aDateValue.Month, aDateValue.Year); + + // done + return returnValue; + } +}; + +//= TimeNormalization + +class TimeNormalization : public StandardFormatNormalizer +{ +public: + explicit TimeNormalization(Reference<XNumberFormatter> const& i_formatter) + : StandardFormatNormalizer(i_formatter, NumberFormat::TIME) + { + } + + virtual double convertToDouble(Any const& i_value) const override + { + double returnValue = std::numeric_limits<double>::quiet_NaN(); + + // extract + css::util::Time aTimeValue; + ENSURE_OR_RETURN(i_value >>= aTimeValue, "allowed for tools::Time values only", + returnValue); + + // convert + returnValue += lcl_convertTimeToDays(aTimeValue.Hours, aTimeValue.Minutes, + aTimeValue.Seconds, aTimeValue.NanoSeconds); + + // done + return returnValue; + } +}; +} + +//= operations + +bool CellValueConversion::ensureNumberFormatter() +{ + if (bAttemptedFormatterCreation) + return xNumberFormatter.is(); + bAttemptedFormatterCreation = true; + + try + { + Reference<XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + // a number formatter + Reference<XNumberFormatter> const xFormatter(NumberFormatter::create(xContext), + UNO_QUERY_THROW); + + // a supplier of number formats + Locale aLocale = SvtSysLocale().GetLanguageTag().getLocale(); + + Reference<XNumberFormatsSupplier> const xSupplier + = NumberFormatsSupplier::createWithLocale(xContext, aLocale); + + // ensure a NullDate we will assume later on + css::util::Date const aNullDate(1, 1, 1900); + Reference<XPropertySet> const xFormatSettings(xSupplier->getNumberFormatSettings(), + UNO_SET_THROW); + xFormatSettings->setPropertyValue("NullDate", Any(aNullDate)); + + // knit + xFormatter->attachNumberFormatsSupplier(xSupplier); + + // done + xNumberFormatter = xFormatter; + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.table"); + } + + return xNumberFormatter.is(); +} + +bool CellValueConversion::getValueNormalizer(Type const& i_valueType, + std::shared_ptr<StandardFormatNormalizer>& o_formatter) +{ + auto pos = aNormalizers.find(i_valueType.getTypeName()); + if (pos == aNormalizers.end()) + { + // never encountered this type before + o_formatter.reset(); + + OUString const sTypeName(i_valueType.getTypeName()); + TypeClass const eTypeClass = i_valueType.getTypeClass(); + + if (sTypeName == ::cppu::UnoType<DateTime>::get().getTypeName()) + { + o_formatter = std::make_shared<DateTimeNormalization>(xNumberFormatter); + } + else if (sTypeName == ::cppu::UnoType<css::util::Date>::get().getTypeName()) + { + o_formatter = std::make_shared<DateNormalization>(xNumberFormatter); + } + else if (sTypeName == ::cppu::UnoType<css::util::Time>::get().getTypeName()) + { + o_formatter = std::make_shared<TimeNormalization>(xNumberFormatter); + } + else if (sTypeName == ::cppu::UnoType<sal_Bool>::get().getTypeName()) + { + o_formatter = std::make_shared<BooleanNormalization>(xNumberFormatter); + } + else if (sTypeName == ::cppu::UnoType<double>::get().getTypeName() + || sTypeName == ::cppu::UnoType<float>::get().getTypeName()) + { + o_formatter = std::make_shared<DoubleNormalization>(xNumberFormatter); + } + else if ((eTypeClass == TypeClass_BYTE) || (eTypeClass == TypeClass_SHORT) + || (eTypeClass == TypeClass_UNSIGNED_SHORT) || (eTypeClass == TypeClass_LONG) + || (eTypeClass == TypeClass_UNSIGNED_LONG) || (eTypeClass == TypeClass_HYPER)) + { + o_formatter = std::make_shared<IntegerNormalization>(xNumberFormatter); + } + else + { + SAL_WARN("svtools.table", "unsupported type '" << sTypeName << "'!"); + } + aNormalizers[sTypeName] = o_formatter; + } + else + o_formatter = pos->second; + + return bool(o_formatter); +} + +//= CellValueConversion + +CellValueConversion::CellValueConversion() + : xNumberFormatter() + , bAttemptedFormatterCreation(false) + , aNormalizers() +{ +} + +CellValueConversion::~CellValueConversion() {} + +OUString CellValueConversion::convertToString(const Any& i_value) +{ + OUString sStringValue; + if (!i_value.hasValue()) + return sStringValue; + + if (!(i_value >>= sStringValue)) + { + if (ensureNumberFormatter()) + { + std::shared_ptr<StandardFormatNormalizer> pNormalizer; + if (getValueNormalizer(i_value.getValueType(), pNormalizer)) + { + try + { + double const formatterCompliantValue = pNormalizer->convertToDouble(i_value); + sal_Int32 const formatKey = pNormalizer->getFormatKey(); + sStringValue = xNumberFormatter->convertNumberToString(formatKey, + formatterCompliantValue); + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.table"); + } + } + } + } + + return sStringValue; +} + +} // namespace svt + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/table/cellvalueconversion.hxx b/svtools/source/table/cellvalueconversion.hxx new file mode 100644 index 000000000..2e05707e5 --- /dev/null +++ b/svtools/source/table/cellvalueconversion.hxx @@ -0,0 +1,72 @@ +/* -*- 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 <com/sun/star/util/XNumberFormatter.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <unordered_map> +#include <memory> + +namespace svt +{ +class StandardFormatNormalizer +{ +public: + /** converts the given <code>Any</code> into a <code>double</code> value to be fed into a number formatter + */ + virtual double convertToDouble(css::uno::Any const& i_value) const = 0; + + /** returns the format key to be used for formatting values + */ + sal_Int32 getFormatKey() const { return m_nFormatKey; } + +protected: + StandardFormatNormalizer(css::uno::Reference<css::util::XNumberFormatter> const& i_formatter, + ::sal_Int32 const i_numberFormatType); + + virtual ~StandardFormatNormalizer() {} + +private: + ::sal_Int32 m_nFormatKey; +}; + +class CellValueConversion +{ +public: + CellValueConversion(); + ~CellValueConversion(); + + OUString convertToString(const css::uno::Any& i_cellValue); + +private: + bool ensureNumberFormatter(); + bool getValueNormalizer(css::uno::Type const& i_valueType, + std::shared_ptr<StandardFormatNormalizer>& o_formatter); + + typedef std::unordered_map<OUString, std::shared_ptr<StandardFormatNormalizer>> NormalizerCache; + + css::uno::Reference<css::util::XNumberFormatter> xNumberFormatter; + bool bAttemptedFormatterCreation; + NormalizerCache aNormalizers; +}; + +} // namespace svt + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/table/defaultinputhandler.cxx b/svtools/source/table/defaultinputhandler.cxx new file mode 100644 index 000000000..0263b2968 --- /dev/null +++ b/svtools/source/table/defaultinputhandler.cxx @@ -0,0 +1,186 @@ +/* -*- 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 <table/defaultinputhandler.hxx> +#include <table/tablecontrolinterface.hxx> + +#include <vcl/event.hxx> +#include <osl/diagnose.h> + + +namespace svt::table +{ + + + //= DefaultInputHandler + + + DefaultInputHandler::DefaultInputHandler() + { + aMouseFunctions.push_back( new ColumnResize ); + aMouseFunctions.push_back( new RowSelection ); + aMouseFunctions.push_back( new ColumnSortHandler ); + } + + + DefaultInputHandler::~DefaultInputHandler() + { + } + + + bool DefaultInputHandler::delegateMouseEvent( ITableControl& i_control, const MouseEvent& i_event, + FunctionResult ( MouseFunction::*i_handlerMethod )( ITableControl&, const MouseEvent& ) ) + { + if ( pActiveFunction.is() ) + { + bool furtherHandler = false; + switch ( (pActiveFunction.get()->*i_handlerMethod)( i_control, i_event ) ) + { + case ActivateFunction: + OSL_ENSURE( false, "lcl_delegateMouseEvent: unexpected - function already *is* active!" ); + break; + case ContinueFunction: + break; + case DeactivateFunction: + pActiveFunction.clear(); + break; + case SkipFunction: + furtherHandler = true; + break; + } + if ( !furtherHandler ) + // handled the event + return true; + } + + // ask all other handlers + bool handled = false; + for (auto const& mouseFunction : aMouseFunctions) + { + if (handled) + break; + if (mouseFunction == pActiveFunction) + // we already invoked this function + continue; + + switch ( (mouseFunction.get()->*i_handlerMethod)( i_control, i_event ) ) + { + case ActivateFunction: + pActiveFunction = mouseFunction; + handled = true; + break; + case ContinueFunction: + case DeactivateFunction: + OSL_ENSURE( false, "lcl_delegateMouseEvent: unexpected: inactive handler cannot be continued or deactivated!" ); + break; + case SkipFunction: + handled = false; + break; + } + } + return handled; + } + + + bool DefaultInputHandler::MouseMove( ITableControl& i_tableControl, const MouseEvent& i_event ) + { + return delegateMouseEvent( i_tableControl, i_event, &MouseFunction::handleMouseMove ); + } + + + bool DefaultInputHandler::MouseButtonDown( ITableControl& i_tableControl, const MouseEvent& i_event ) + { + return delegateMouseEvent( i_tableControl, i_event, &MouseFunction::handleMouseDown ); + } + + + bool DefaultInputHandler::MouseButtonUp( ITableControl& i_tableControl, const MouseEvent& i_event ) + { + return delegateMouseEvent( i_tableControl, i_event, &MouseFunction::handleMouseUp ); + } + + + bool DefaultInputHandler::KeyInput( ITableControl& _rControl, const KeyEvent& rKEvt ) + { + bool bHandled = false; + + const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); + sal_uInt16 nKeyCode = rKeyCode.GetCode(); + + struct ActionMapEntry + { + sal_uInt16 nKeyCode; + sal_uInt16 nKeyModifier; + TableControlAction eAction; + } + static const aKnownActions[] = { + { KEY_DOWN, 0, cursorDown }, + { KEY_UP, 0, cursorUp }, + { KEY_LEFT, 0, cursorLeft }, + { KEY_RIGHT, 0, cursorRight }, + { KEY_HOME, 0, cursorToLineStart }, + { KEY_END, 0, cursorToLineEnd }, + { KEY_PAGEUP, 0, cursorPageUp }, + { KEY_PAGEDOWN, 0, cursorPageDown }, + { KEY_PAGEUP, KEY_MOD1, cursorToFirstLine }, + { KEY_PAGEDOWN, KEY_MOD1, cursorToLastLine }, + { KEY_HOME, KEY_MOD1, cursorTopLeft }, + { KEY_END, KEY_MOD1, cursorBottomRight }, + { KEY_SPACE, KEY_MOD1, cursorSelectRow }, + { KEY_UP, KEY_SHIFT, cursorSelectRowUp }, + { KEY_DOWN, KEY_SHIFT, cursorSelectRowDown }, + { KEY_END, KEY_SHIFT, cursorSelectRowAreaBottom }, + { KEY_HOME, KEY_SHIFT, cursorSelectRowAreaTop }, + + { 0, 0, invalidTableControlAction } + }; + + const ActionMapEntry* pActions = aKnownActions; + for ( ; pActions->eAction != invalidTableControlAction; ++pActions ) + { + if ( ( pActions->nKeyCode == nKeyCode ) && ( pActions->nKeyModifier == rKeyCode.GetModifier() ) ) + { + bHandled = _rControl.dispatchAction( pActions->eAction ); + break; + } + } + + return bHandled; + } + + + bool DefaultInputHandler::GetFocus( ITableControl& _rControl ) + { + _rControl.showCursor(); + return false; // continue processing + } + + + bool DefaultInputHandler::LoseFocus( ITableControl& _rControl ) + { + _rControl.hideCursor(); + return false; // continue processing + } + + +} // namespace svt::table + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/table/gridtablerenderer.cxx b/svtools/source/table/gridtablerenderer.cxx new file mode 100644 index 000000000..f814fc64d --- /dev/null +++ b/svtools/source/table/gridtablerenderer.cxx @@ -0,0 +1,598 @@ +/* -*- 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 "cellvalueconversion.hxx" +#include <table/gridtablerenderer.hxx> +#include <table/tablesort.hxx> + +#include <com/sun/star/graphic/XGraphic.hpp> + +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <vcl/window.hxx> +#include <vcl/image.hxx> +#include <vcl/virdev.hxx> +#include <vcl/decoview.hxx> +#include <vcl/settings.hxx> + + +namespace svt::table +{ + using ::css::uno::Any; + using ::css::uno::Reference; + using ::css::uno::UNO_QUERY; + using ::css::uno::XInterface; + using ::css::uno::TypeClass_INTERFACE; + using ::css::graphic::XGraphic; + using ::css::style::HorizontalAlignment; + using ::css::style::HorizontalAlignment_CENTER; + using ::css::style::HorizontalAlignment_RIGHT; + using ::css::style::VerticalAlignment; + using ::css::style::VerticalAlignment_MIDDLE; + using ::css::style::VerticalAlignment_BOTTOM; + + + //= CachedSortIndicator + + namespace { + + class CachedSortIndicator + { + public: + CachedSortIndicator() + : m_lastHeaderHeight( 0 ) + , m_lastArrowColor( COL_TRANSPARENT ) + { + } + + BitmapEx const & getBitmapFor(vcl::RenderContext const & i_device, tools::Long const i_headerHeight, + StyleSettings const & i_style, bool const i_sortAscending); + + private: + tools::Long m_lastHeaderHeight; + Color m_lastArrowColor; + BitmapEx m_sortAscending; + BitmapEx m_sortDescending; + }; + + } + + BitmapEx const & CachedSortIndicator::getBitmapFor(vcl::RenderContext const& i_device, tools::Long const i_headerHeight, + StyleSettings const & i_style, bool const i_sortAscending ) + { + BitmapEx& rBitmap(i_sortAscending ? m_sortAscending : m_sortDescending); + if (rBitmap.IsEmpty() || (i_headerHeight != m_lastHeaderHeight) || (i_style.GetActiveColor() != m_lastArrowColor)) + { + tools::Long const nSortIndicatorWidth = 2 * i_headerHeight / 3; + tools::Long const nSortIndicatorHeight = 2 * nSortIndicatorWidth / 3; + + Point const aBitmapPos( 0, 0 ); + Size const aBitmapSize( nSortIndicatorWidth, nSortIndicatorHeight ); + ScopedVclPtrInstance< VirtualDevice > aDevice(i_device, DeviceFormat::DEFAULT, + DeviceFormat::DEFAULT); + aDevice->SetOutputSizePixel( aBitmapSize ); + + DecorationView aDecoView(aDevice.get()); + aDecoView.DrawSymbol(tools::Rectangle(aBitmapPos, aBitmapSize), + i_sortAscending ? SymbolType::SPIN_UP : SymbolType::SPIN_DOWN, + i_style.GetActiveColor()); + + rBitmap = aDevice->GetBitmapEx(aBitmapPos, aBitmapSize); + m_lastHeaderHeight = i_headerHeight; + m_lastArrowColor = i_style.GetActiveColor(); + } + return rBitmap; + } + + + //= GridTableRenderer_Impl + + struct GridTableRenderer_Impl + { + ITableModel& rModel; + RowPos nCurrentRow; + bool bUseGridLines; + CachedSortIndicator aSortIndicator; + CellValueConversion aStringConverter; + + explicit GridTableRenderer_Impl( ITableModel& _rModel ) + : rModel( _rModel ) + , nCurrentRow( ROW_INVALID ) + , bUseGridLines( true ) + , aSortIndicator( ) + , aStringConverter() + { + } + }; + + + //= helper + + namespace + { + tools::Rectangle lcl_getContentArea( GridTableRenderer_Impl const & i_impl, tools::Rectangle const & i_cellArea ) + { + tools::Rectangle aContentArea( i_cellArea ); + if ( i_impl.bUseGridLines ) + { + aContentArea.AdjustRight( -1 ); + aContentArea.AdjustBottom( -1 ); + } + return aContentArea; + } + tools::Rectangle lcl_getTextRenderingArea( tools::Rectangle const & i_contentArea ) + { + tools::Rectangle aTextArea( i_contentArea ); + aTextArea.AdjustLeft(2 ); aTextArea.AdjustRight( -2 ); + aTextArea.AdjustTop( 1 ); aTextArea.AdjustBottom( -1 ); + return aTextArea; + } + + DrawTextFlags lcl_getAlignmentTextDrawFlags( GridTableRenderer_Impl const & i_impl, ColPos const i_columnPos ) + { + DrawTextFlags nVertFlag = DrawTextFlags::Top; + VerticalAlignment const eVertAlign = i_impl.rModel.getVerticalAlign(); + switch ( eVertAlign ) + { + case VerticalAlignment_MIDDLE: nVertFlag = DrawTextFlags::VCenter; break; + case VerticalAlignment_BOTTOM: nVertFlag = DrawTextFlags::Bottom; break; + default: + break; + } + + DrawTextFlags nHorzFlag = DrawTextFlags::Left; + HorizontalAlignment const eHorzAlign = i_impl.rModel.getColumnCount() > 0 + ? i_impl.rModel.getColumnModel( i_columnPos )->getHorizontalAlign() + : HorizontalAlignment_CENTER; + switch ( eHorzAlign ) + { + case HorizontalAlignment_CENTER: nHorzFlag = DrawTextFlags::Center; break; + case HorizontalAlignment_RIGHT: nHorzFlag = DrawTextFlags::Right; break; + default: + break; + } + + return nVertFlag | nHorzFlag; + } + + } + + + //= GridTableRenderer + + + GridTableRenderer::GridTableRenderer( ITableModel& _rModel ) + :m_pImpl( new GridTableRenderer_Impl( _rModel ) ) + { + } + + + GridTableRenderer::~GridTableRenderer() + { + } + + + bool GridTableRenderer::useGridLines() const + { + return m_pImpl->bUseGridLines; + } + + + void GridTableRenderer::useGridLines( bool const i_use ) + { + m_pImpl->bUseGridLines = i_use; + } + + + namespace + { + Color lcl_getEffectiveColor(std::optional<Color> const& i_modelColor, + StyleSettings const& i_styleSettings, + Color const& (StyleSettings::*i_getDefaultColor) () const) + { + if (!!i_modelColor) + return *i_modelColor; + return (i_styleSettings.*i_getDefaultColor)(); + } + } + + + void GridTableRenderer::PaintHeaderArea(vcl::RenderContext& rRenderContext, const tools::Rectangle& _rArea, + bool _bIsColHeaderArea, bool _bIsRowHeaderArea, const StyleSettings& _rStyle) + { + OSL_PRECOND(_bIsColHeaderArea || _bIsRowHeaderArea, "GridTableRenderer::PaintHeaderArea: invalid area flags!"); + + rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR); + + Color const background = lcl_getEffectiveColor(m_pImpl->rModel.getHeaderBackgroundColor(), + _rStyle, &StyleSettings::GetDialogColor); + rRenderContext.SetFillColor(background); + + rRenderContext.SetLineColor(); + rRenderContext.DrawRect(_rArea); + + // delimiter lines at bottom/right + std::optional<Color> aLineColor(m_pImpl->rModel.getLineColor()); + Color const lineColor = !aLineColor ? _rStyle.GetSeparatorColor() : *aLineColor; + rRenderContext.SetLineColor(lineColor); + rRenderContext.DrawLine(_rArea.BottomLeft(), _rArea.BottomRight()); + rRenderContext.DrawLine(_rArea.BottomRight(), _rArea.TopRight()); + + rRenderContext.Pop(); + } + + + void GridTableRenderer::PaintColumnHeader( + ColPos _nCol, + vcl::RenderContext& rRenderContext, + const tools::Rectangle& _rArea, const StyleSettings& _rStyle) + { + rRenderContext.Push(vcl::PushFlags::LINECOLOR); + + OUString sHeaderText; + PColumnModel const pColumn = m_pImpl->rModel.getColumnModel( _nCol ); + DBG_ASSERT( pColumn, "GridTableRenderer::PaintColumnHeader: invalid column model object!" ); + if ( pColumn ) + sHeaderText = pColumn->getName(); + + Color const textColor = lcl_getEffectiveColor( m_pImpl->rModel.getTextColor(), _rStyle, &StyleSettings::GetFieldTextColor ); + rRenderContext.SetTextColor(textColor); + + tools::Rectangle const aTextRect( lcl_getTextRenderingArea( lcl_getContentArea( *m_pImpl, _rArea ) ) ); + DrawTextFlags nDrawTextFlags = lcl_getAlignmentTextDrawFlags( *m_pImpl, _nCol ) | DrawTextFlags::Clip; + if (!m_pImpl->rModel.isEnabled()) + nDrawTextFlags |= DrawTextFlags::Disable; + rRenderContext.DrawText( aTextRect, sHeaderText, nDrawTextFlags ); + + std::optional<Color> const aLineColor( m_pImpl->rModel.getLineColor() ); + Color const lineColor = !aLineColor ? _rStyle.GetSeparatorColor() : *aLineColor; + rRenderContext.SetLineColor( lineColor ); + rRenderContext.DrawLine( _rArea.BottomRight(), _rArea.TopRight()); + rRenderContext.DrawLine( _rArea.BottomLeft(), _rArea.BottomRight() ); + + // draw sort indicator if the model data is sorted by the given column + ITableDataSort const * pSortAdapter = m_pImpl->rModel.getSortAdapter(); + ColumnSort aCurrentSortOrder; + if ( pSortAdapter != nullptr ) + aCurrentSortOrder = pSortAdapter->getCurrentSortOrder(); + if ( aCurrentSortOrder.nColumnPos == _nCol ) + { + tools::Long const nHeaderHeight( _rArea.GetHeight() ); + BitmapEx const aIndicatorBitmap = m_pImpl->aSortIndicator.getBitmapFor(rRenderContext, nHeaderHeight, _rStyle, + aCurrentSortOrder.eSortDirection == ColumnSortAscending); + Size const aBitmapSize( aIndicatorBitmap.GetSizePixel() ); + tools::Long const nSortIndicatorPaddingX = 2; + tools::Long const nSortIndicatorPaddingY = ( nHeaderHeight - aBitmapSize.Height() ) / 2; + + if ( nDrawTextFlags & DrawTextFlags::Right ) + { + // text is right aligned => draw the sort indicator at the left hand side + rRenderContext.DrawBitmapEx(Point(_rArea.Left() + nSortIndicatorPaddingX, _rArea.Top() + nSortIndicatorPaddingY), + aIndicatorBitmap); + } + else + { + // text is left-aligned or centered => draw the sort indicator at the right hand side + rRenderContext.DrawBitmapEx(Point(_rArea.Right() - nSortIndicatorPaddingX - aBitmapSize.Width(), nSortIndicatorPaddingY), + aIndicatorBitmap); + } + } + + rRenderContext.Pop(); + } + + + void GridTableRenderer::PrepareRow(RowPos _nRow, bool i_hasControlFocus, bool _bSelected, vcl::RenderContext& rRenderContext, + const tools::Rectangle& _rRowArea, const StyleSettings& _rStyle) + { + // remember the row for subsequent calls to the other ->ITableRenderer methods + m_pImpl->nCurrentRow = _nRow; + + rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR); + + Color backgroundColor = _rStyle.GetFieldColor(); + + Color const activeSelectionBackColor = lcl_getEffectiveColor(m_pImpl->rModel.getActiveSelectionBackColor(), + _rStyle, &StyleSettings::GetHighlightColor); + if (_bSelected) + { + // selected rows use the background color from the style + backgroundColor = i_hasControlFocus + ? activeSelectionBackColor + : lcl_getEffectiveColor(m_pImpl->rModel.getInactiveSelectionBackColor(), _rStyle, &StyleSettings::GetDeactiveColor); + } + else + { + std::optional< std::vector<Color> > aRowColors = m_pImpl->rModel.getRowBackgroundColors(); + if (!aRowColors) + { + // use alternating default colors + Color const fieldColor = _rStyle.GetFieldColor(); + if (_rStyle.GetHighContrastMode() || ((m_pImpl->nCurrentRow % 2) == 0)) + { + backgroundColor = fieldColor; + } + else + { + Color hilightColor = activeSelectionBackColor; + hilightColor.SetRed( 9 * ( fieldColor.GetRed() - hilightColor.GetRed() ) / 10 + hilightColor.GetRed() ); + hilightColor.SetGreen( 9 * ( fieldColor.GetGreen() - hilightColor.GetGreen() ) / 10 + hilightColor.GetGreen() ); + hilightColor.SetBlue( 9 * ( fieldColor.GetBlue() - hilightColor.GetBlue() ) / 10 + hilightColor.GetBlue() ); + backgroundColor = hilightColor; + } + } + else + { + if (aRowColors->empty()) + { + // all colors have the same background color + backgroundColor = _rStyle.GetFieldColor(); + } + else + { + backgroundColor = aRowColors->at(m_pImpl->nCurrentRow % aRowColors->size()); + } + } + } + + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(backgroundColor); + rRenderContext.DrawRect(_rRowArea); + + rRenderContext.Pop(); + } + + + void GridTableRenderer::PaintRowHeader(vcl::RenderContext& rRenderContext, + const tools::Rectangle& _rArea, const StyleSettings& _rStyle) + { + rRenderContext.Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::TEXTCOLOR ); + + std::optional<Color> const aLineColor( m_pImpl->rModel.getLineColor() ); + Color const lineColor = !aLineColor ? _rStyle.GetSeparatorColor() : *aLineColor; + rRenderContext.SetLineColor(lineColor); + rRenderContext.DrawLine(_rArea.BottomLeft(), _rArea.BottomRight()); + + Any const rowHeading( m_pImpl->rModel.getRowHeading( m_pImpl->nCurrentRow ) ); + OUString const rowTitle( m_pImpl->aStringConverter.convertToString( rowHeading ) ); + if (!rowTitle.isEmpty()) + { + Color const textColor = lcl_getEffectiveColor(m_pImpl->rModel.getHeaderTextColor(), + _rStyle, &StyleSettings::GetFieldTextColor); + rRenderContext.SetTextColor(textColor); + + tools::Rectangle const aTextRect(lcl_getTextRenderingArea(lcl_getContentArea(*m_pImpl, _rArea))); + DrawTextFlags nDrawTextFlags = lcl_getAlignmentTextDrawFlags(*m_pImpl, 0) | DrawTextFlags::Clip; + if (!m_pImpl->rModel.isEnabled()) + nDrawTextFlags |= DrawTextFlags::Disable; + // TODO: is using the horizontal alignment of the 0'th column a good idea here? This is pretty ... arbitrary .. + rRenderContext.DrawText(aTextRect, rowTitle, nDrawTextFlags); + } + + rRenderContext.Pop(); + } + + + struct GridTableRenderer::CellRenderContext + { + OutputDevice& rDevice; + tools::Rectangle const aContentArea; + StyleSettings const & rStyle; + ColPos const nColumn; + bool const bSelected; + bool const bHasControlFocus; + + CellRenderContext( OutputDevice& i_device, tools::Rectangle const & i_contentArea, + StyleSettings const & i_style, ColPos const i_column, bool const i_selected, bool const i_hasControlFocus ) + :rDevice( i_device ) + ,aContentArea( i_contentArea ) + ,rStyle( i_style ) + ,nColumn( i_column ) + ,bSelected( i_selected ) + ,bHasControlFocus( i_hasControlFocus ) + { + } + }; + + + void GridTableRenderer::PaintCell(ColPos const i_column, bool _bSelected, bool i_hasControlFocus, + vcl::RenderContext& rRenderContext, const tools::Rectangle& _rArea, const StyleSettings& _rStyle) + { + rRenderContext.Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); + + tools::Rectangle const aContentArea(lcl_getContentArea(*m_pImpl, _rArea)); + CellRenderContext const aCellRenderContext(rRenderContext, aContentArea, _rStyle, i_column, _bSelected, i_hasControlFocus); + impl_paintCellContent(aCellRenderContext); + + if ( m_pImpl->bUseGridLines ) + { + ::std::optional< ::Color > aLineColor( m_pImpl->rModel.getLineColor() ); + ::Color lineColor = !aLineColor ? _rStyle.GetSeparatorColor() : *aLineColor; + + if ( _bSelected && !aLineColor ) + { + // if no line color is specified by the model, use the usual selection color for lines in selected cells + lineColor = i_hasControlFocus + ? lcl_getEffectiveColor( m_pImpl->rModel.getActiveSelectionBackColor(), _rStyle, &StyleSettings::GetHighlightColor ) + : lcl_getEffectiveColor( m_pImpl->rModel.getInactiveSelectionBackColor(), _rStyle, &StyleSettings::GetDeactiveColor ); + } + + rRenderContext.SetLineColor( lineColor ); + rRenderContext.DrawLine( _rArea.BottomLeft(), _rArea.BottomRight() ); + rRenderContext.DrawLine( _rArea.BottomRight(), _rArea.TopRight() ); + } + + rRenderContext.Pop(); + } + + + void GridTableRenderer::impl_paintCellImage( CellRenderContext const & i_context, Image const & i_image ) + { + Point imagePos( Point( i_context.aContentArea.Left(), i_context.aContentArea.Top() ) ); + Size imageSize = i_image.GetSizePixel(); + if ( i_context.aContentArea.GetWidth() > imageSize.Width() ) + { + const HorizontalAlignment eHorzAlign = m_pImpl->rModel.getColumnModel( i_context.nColumn )->getHorizontalAlign(); + switch ( eHorzAlign ) + { + case HorizontalAlignment_CENTER: + imagePos.AdjustX(( i_context.aContentArea.GetWidth() - imageSize.Width() ) / 2 ); + break; + case HorizontalAlignment_RIGHT: + imagePos.setX( i_context.aContentArea.Right() - imageSize.Width() ); + break; + default: + break; + } + + } + else + imageSize.setWidth( i_context.aContentArea.GetWidth() ); + + if ( i_context.aContentArea.GetHeight() > imageSize.Height() ) + { + const VerticalAlignment eVertAlign = m_pImpl->rModel.getVerticalAlign(); + switch ( eVertAlign ) + { + case VerticalAlignment_MIDDLE: + imagePos.AdjustY(( i_context.aContentArea.GetHeight() - imageSize.Height() ) / 2 ); + break; + case VerticalAlignment_BOTTOM: + imagePos.setY( i_context.aContentArea.Bottom() - imageSize.Height() ); + break; + default: + break; + } + } + else + imageSize.setHeight( i_context.aContentArea.GetHeight() - 1 ); + DrawImageFlags const nStyle = m_pImpl->rModel.isEnabled() ? DrawImageFlags::NONE : DrawImageFlags::Disable; + i_context.rDevice.DrawImage( imagePos, imageSize, i_image, nStyle ); + } + + + void GridTableRenderer::impl_paintCellContent( CellRenderContext const & i_context ) + { + Any aCellContent; + m_pImpl->rModel.getCellContent( i_context.nColumn, m_pImpl->nCurrentRow, aCellContent ); + + if ( aCellContent.getValueTypeClass() == TypeClass_INTERFACE ) + { + Reference< XInterface > const xContentInterface( aCellContent, UNO_QUERY ); + if ( !xContentInterface.is() ) + // allowed. kind of. + return; + + Reference< XGraphic > const xGraphic( aCellContent, UNO_QUERY ); + ENSURE_OR_RETURN_VOID( xGraphic.is(), "GridTableRenderer::impl_paintCellContent: only XGraphic interfaces (or NULL) are supported for painting." ); + + const Image aImage( xGraphic ); + impl_paintCellImage( i_context, aImage ); + return; + } + + const OUString sText( m_pImpl->aStringConverter.convertToString( aCellContent ) ); + impl_paintCellText( i_context, sText ); + } + + + void GridTableRenderer::impl_paintCellText( CellRenderContext const & i_context, OUString const & i_text ) + { + if ( i_context.bSelected ) + { + ::Color const textColor = i_context.bHasControlFocus + ? lcl_getEffectiveColor( m_pImpl->rModel.getActiveSelectionTextColor(), i_context.rStyle, &StyleSettings::GetHighlightTextColor ) + : lcl_getEffectiveColor( m_pImpl->rModel.getInactiveSelectionTextColor(), i_context.rStyle, &StyleSettings::GetDeactiveTextColor ); + i_context.rDevice.SetTextColor( textColor ); + } + else + { + ::Color const textColor = lcl_getEffectiveColor( m_pImpl->rModel.getTextColor(), i_context.rStyle, &StyleSettings::GetFieldTextColor ); + i_context.rDevice.SetTextColor( textColor ); + } + + tools::Rectangle const textRect( lcl_getTextRenderingArea( i_context.aContentArea ) ); + DrawTextFlags nDrawTextFlags = lcl_getAlignmentTextDrawFlags( *m_pImpl, i_context.nColumn ) | DrawTextFlags::Clip; + if ( !m_pImpl->rModel.isEnabled() ) + nDrawTextFlags |= DrawTextFlags::Disable; + i_context.rDevice.DrawText( textRect, i_text, nDrawTextFlags ); + } + + + void GridTableRenderer::ShowCellCursor( vcl::Window& _rView, const tools::Rectangle& _rCursorRect) + { + _rView.ShowFocus( _rCursorRect ); + } + + + void GridTableRenderer::HideCellCursor( vcl::Window& _rView ) + { + _rView.HideFocus(); + } + + + bool GridTableRenderer::FitsIntoCell( Any const & i_cellContent, + OutputDevice& i_targetDevice, tools::Rectangle const & i_targetArea ) const + { + if ( !i_cellContent.hasValue() ) + return true; + + if ( i_cellContent.getValueTypeClass() == TypeClass_INTERFACE ) + { + Reference< XInterface > const xContentInterface( i_cellContent, UNO_QUERY ); + if ( !xContentInterface.is() ) + return true; + + Reference< XGraphic > const xGraphic( i_cellContent, UNO_QUERY ); + if ( xGraphic.is() ) + // for the moment, assume it fits. We can always scale it down during painting ... + return true; + + OSL_ENSURE( false, "GridTableRenderer::FitsIntoCell: only XGraphic interfaces (or NULL) are supported for painting." ); + return true; + } + + OUString const sText( m_pImpl->aStringConverter.convertToString( i_cellContent ) ); + if ( sText.isEmpty() ) + return true; + + tools::Rectangle const aTargetArea( lcl_getTextRenderingArea( lcl_getContentArea( *m_pImpl, i_targetArea ) ) ); + + tools::Long const nTextHeight = i_targetDevice.GetTextHeight(); + if ( nTextHeight > aTargetArea.GetHeight() ) + return false; + + tools::Long const nTextWidth = i_targetDevice.GetTextWidth( sText ); + return nTextWidth <= aTargetArea.GetWidth(); + } + + + bool GridTableRenderer::GetFormattedCellString( Any const & i_cellValue, OUString & o_cellString ) const + { + o_cellString = m_pImpl->aStringConverter.convertToString( i_cellValue ); + + return true; + } + + +} // namespace svt::table + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/table/mousefunction.cxx b/svtools/source/table/mousefunction.cxx new file mode 100644 index 000000000..8bb30390a --- /dev/null +++ b/svtools/source/table/mousefunction.cxx @@ -0,0 +1,274 @@ +/* -*- 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 <mousefunction.hxx> +#include <table/tablecontrolinterface.hxx> +#include <table/tablesort.hxx> + +#include <tools/diagnose_ex.h> +#include <vcl/ptrstyle.hxx> + +namespace svt::table +{ + + + //= ColumnResize + + + FunctionResult ColumnResize::handleMouseMove( ITableControl& i_tableControl, MouseEvent const & i_event ) + { + Point const aPoint = i_event.GetPosPixel(); + + if ( m_nResizingColumn == COL_INVALID ) + { + // if we hit a column divider, change the mouse pointer accordingly + PointerStyle aNewPointer( PointerStyle::Arrow ); + TableCell const tableCell = i_tableControl.hitTest( aPoint ); + if ( ( tableCell.nRow == ROW_COL_HEADERS ) && ( tableCell.eArea == ColumnDivider ) ) + { + aNewPointer = PointerStyle::HSplit; + } + i_tableControl.setPointer( aNewPointer ); + + return SkipFunction; // TODO: is this correct? + } + + ::Size const tableSize = i_tableControl.getTableSizePixel(); + + // set proper pointer + PointerStyle aNewPointer( PointerStyle::Arrow ); + ColumnMetrics const & columnMetrics( i_tableControl.getColumnMetrics( m_nResizingColumn ) ); + if ( ( aPoint.X() > tableSize.Width() ) + || ( aPoint.X() < columnMetrics.nStartPixel ) + ) + { + aNewPointer = PointerStyle::NotAllowed; + } + else + { + aNewPointer = PointerStyle::HSplit; + } + i_tableControl.setPointer( aNewPointer ); + + // show tracking line + i_tableControl.hideTracking(); + i_tableControl.showTracking( + tools::Rectangle( + Point( aPoint.X(), 0 ), + Size( 1, tableSize.Height() ) + ), + ShowTrackFlags::Split | ShowTrackFlags::TrackWindow + ); + + return ContinueFunction; + } + + + FunctionResult ColumnResize::handleMouseDown( ITableControl& i_tableControl, MouseEvent const & i_event ) + { + if ( m_nResizingColumn != COL_INVALID ) + { + OSL_ENSURE( false, "ColumnResize::handleMouseDown: suspicious: MouseButtonDown while still tracking?" ); + return ContinueFunction; + } + + TableCell const tableCell( i_tableControl.hitTest( i_event.GetPosPixel() ) ); + if ( tableCell.nRow == ROW_COL_HEADERS ) + { + if ( ( tableCell.nColumn != COL_INVALID ) + && ( tableCell.eArea == ColumnDivider ) + ) + { + m_nResizingColumn = tableCell.nColumn; + i_tableControl.captureMouse(); + return ActivateFunction; + } + } + + return SkipFunction; + } + + + FunctionResult ColumnResize::handleMouseUp( ITableControl& i_tableControl, MouseEvent const & i_event ) + { + if ( m_nResizingColumn == COL_INVALID ) + return SkipFunction; + + Point const aPoint = i_event.GetPosPixel(); + + i_tableControl.hideTracking(); + PColumnModel const pColumn = i_tableControl.getModel()->getColumnModel( m_nResizingColumn ); + tools::Long const maxWidthLogical = pColumn->getMaxWidth(); + tools::Long const minWidthLogical = pColumn->getMinWidth(); + + // new position of mouse + tools::Long const requestedEnd = aPoint.X(); + + // old position of right border + tools::Long const oldEnd = i_tableControl.getColumnMetrics( m_nResizingColumn ).nEndPixel; + + // position of left border if cursor in the to-be-resized column + tools::Long const columnStart = i_tableControl.getColumnMetrics( m_nResizingColumn ).nStartPixel; + tools::Long const requestedWidth = requestedEnd - columnStart; + // TODO: this is not correct, strictly: It assumes that the mouse was pressed exactly on the "end" pos, + // but for a while now, we have relaxed this, and allow clicking a few pixels aside, too + + if ( requestedEnd >= columnStart ) + { + tools::Long requestedWidthLogical = i_tableControl.pixelWidthToAppFont( requestedWidth ); + // respect column width limits + if ( oldEnd > requestedEnd ) + { + // column has become smaller, check against minimum width + if ( ( minWidthLogical != 0 ) && ( requestedWidthLogical < minWidthLogical ) ) + requestedWidthLogical = minWidthLogical; + } + else if ( oldEnd < requestedEnd ) + { + // column has become larger, check against max width + if ( ( maxWidthLogical != 0 ) && ( requestedWidthLogical >= maxWidthLogical ) ) + requestedWidthLogical = maxWidthLogical; + } + pColumn->setWidth( requestedWidthLogical ); + i_tableControl.invalidate( TableArea::All ); + } + + i_tableControl.setPointer( PointerStyle::Arrow ); + i_tableControl.releaseMouse(); + + m_nResizingColumn = COL_INVALID; + return DeactivateFunction; + } + + + //= RowSelection + + + FunctionResult RowSelection::handleMouseMove( ITableControl&, MouseEvent const & ) + { + return SkipFunction; + } + + + FunctionResult RowSelection::handleMouseDown( ITableControl& i_tableControl, MouseEvent const & i_event ) + { + bool handled = false; + + TableCell const tableCell( i_tableControl.hitTest( i_event.GetPosPixel() ) ); + if ( tableCell.nRow >= 0 ) + { + if ( i_tableControl.getSelEngine()->GetSelectionMode() == SelectionMode::NONE ) + { + i_tableControl.activateCell( tableCell.nColumn, tableCell.nRow ); + handled = true; + } + else + { + handled = i_tableControl.getSelEngine()->SelMouseButtonDown( i_event ); + } + } + + if ( handled ) + m_bActive = true; + return handled ? ActivateFunction : SkipFunction; + } + + + FunctionResult RowSelection::handleMouseUp( ITableControl& i_tableControl, MouseEvent const & i_event ) + { + TableCell const tableCell = i_tableControl.hitTest( i_event.GetPosPixel() ); + if ( tableCell.nRow >= 0 ) + { + if ( i_tableControl.getSelEngine()->GetSelectionMode() != SelectionMode::NONE ) + { + i_tableControl.getSelEngine()->SelMouseButtonUp( i_event ); + } + } + if ( m_bActive ) + { + m_bActive = false; + return DeactivateFunction; + } + return SkipFunction; + } + + + //= ColumnSortHandler + + + FunctionResult ColumnSortHandler::handleMouseMove( ITableControl&, MouseEvent const & ) + { + return SkipFunction; + } + + + FunctionResult ColumnSortHandler::handleMouseDown( ITableControl& i_tableControl, MouseEvent const & i_event ) + { + if ( m_nActiveColumn != COL_INVALID ) + { + OSL_ENSURE( false, "ColumnSortHandler::handleMouseDown: called while already active - suspicious!" ); + return ContinueFunction; + } + + if ( i_tableControl.getModel()->getSortAdapter() == nullptr ) + // no sorting support at the model + return SkipFunction; + + TableCell const tableCell( i_tableControl.hitTest( i_event.GetPosPixel() ) ); + if ( ( tableCell.nRow != ROW_COL_HEADERS ) || ( tableCell.nColumn < 0 ) ) + return SkipFunction; + + // TODO: ensure the column header is rendered in some special way, indicating its current state + + m_nActiveColumn = tableCell.nColumn; + return ActivateFunction; + } + + + FunctionResult ColumnSortHandler::handleMouseUp( ITableControl& i_tableControl, MouseEvent const & i_event ) + { + if ( m_nActiveColumn == COL_INVALID ) + return SkipFunction; + + TableCell const tableCell( i_tableControl.hitTest( i_event.GetPosPixel() ) ); + if ( ( tableCell.nRow == ROW_COL_HEADERS ) && ( tableCell.nColumn == m_nActiveColumn ) ) + { + ITableDataSort* pSort = i_tableControl.getModel()->getSortAdapter(); + ENSURE_OR_RETURN( pSort != nullptr, "ColumnSortHandler::handleMouseUp: somebody is mocking with us!", DeactivateFunction ); + // in handleMousButtonDown, the model claimed to have sort support ... + + ColumnSortDirection eSortDirection = ColumnSortAscending; + ColumnSort const aCurrentSort = pSort->getCurrentSortOrder(); + if ( aCurrentSort.nColumnPos == m_nActiveColumn ) + // invert existing sort order + eSortDirection = ( aCurrentSort.eSortDirection == ColumnSortAscending ) ? ColumnSortDescending : ColumnSortAscending; + + pSort->sortByColumn( m_nActiveColumn, eSortDirection ); + } + + m_nActiveColumn = COL_INVALID; + return DeactivateFunction; + } + + +} // namespace svt::table + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/table/tablecontrol.cxx b/svtools/source/table/tablecontrol.cxx new file mode 100644 index 000000000..e341b1ea4 --- /dev/null +++ b/svtools/source/table/tablecontrol.cxx @@ -0,0 +1,639 @@ +/* -*- 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 <table/tablecontrol.hxx> + +#include "tablecontrol_impl.hxx" +#include "tabledatawindow.hxx" + +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> + +#include <sal/log.hxx> +#include <tools/diagnose_ex.h> +#include <unotools/accessiblestatesethelper.hxx> +#include <vcl/settings.hxx> +#include <vcl/vclevent.hxx> + +using namespace ::com::sun::star::uno; +using ::com::sun::star::accessibility::XAccessible; +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::lang; +using namespace utl; + +namespace svt::table +{ + + + namespace AccessibleEventId = ::com::sun::star::accessibility::AccessibleEventId; + + + //= TableControl + + + TableControl::TableControl( vcl::Window* _pParent, WinBits _nStyle ) + :Control( _pParent, _nStyle ) + ,m_pImpl( std::make_shared<TableControl_Impl>( *this ) ) + { + TableDataWindow& rDataWindow = m_pImpl->getDataWindow(); + rDataWindow.SetSelectHdl( LINK( this, TableControl, ImplSelectHdl ) ); + + // by default, use the background as determined by the style settings + const Color aWindowColor( GetSettings().GetStyleSettings().GetFieldColor() ); + SetBackground( Wallpaper( aWindowColor ) ); + GetOutDev()->SetFillColor( aWindowColor ); + + SetCompoundControl( true ); + } + + + TableControl::~TableControl() + { + disposeOnce(); + } + + void TableControl::dispose() + { + CallEventListeners( VclEventId::ObjectDying ); + + m_pImpl->setModel( PTableModel() ); + m_pImpl->disposeAccessible(); + m_pImpl.reset(); + Control::dispose(); + } + + + void TableControl::GetFocus() + { + if ( !m_pImpl || !m_pImpl->getInputHandler()->GetFocus( *m_pImpl ) ) + Control::GetFocus(); + } + + + void TableControl::LoseFocus() + { + if ( !m_pImpl || !m_pImpl->getInputHandler()->LoseFocus( *m_pImpl ) ) + Control::LoseFocus(); + } + + + void TableControl::KeyInput( const KeyEvent& rKEvt ) + { + if ( !m_pImpl->getInputHandler()->KeyInput( *m_pImpl, rKEvt ) ) + Control::KeyInput( rKEvt ); + else + { + if ( m_pImpl->isAccessibleAlive() ) + { + m_pImpl->commitCellEvent( AccessibleEventId::STATE_CHANGED, + Any( AccessibleStateType::FOCUSED ), + Any() + ); + // Huh? What the heck? Why do we unconditionally notify a STATE_CHANGE/FOCUSED after each and every + // (handled) key stroke? + + m_pImpl->commitTableEvent( AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, + Any(), + Any() + ); + // ditto: Why do we notify this unconditionally? We should find the right place to notify the + // ACTIVE_DESCENDANT_CHANGED event. + // Also, we should check if STATE_CHANGED/FOCUSED is really necessary: finally, the children are + // transient, aren't they? + } + } + } + + + void TableControl::StateChanged( StateChangedType i_nStateChange ) + { + Control::StateChanged( i_nStateChange ); + + // forward certain settings to the data window + switch ( i_nStateChange ) + { + case StateChangedType::ControlFocus: + m_pImpl->invalidateSelectedRows(); + break; + + case StateChangedType::ControlBackground: + if ( IsControlBackground() ) + getDataWindow().SetControlBackground( GetControlBackground() ); + else + getDataWindow().SetControlBackground(); + break; + + case StateChangedType::ControlForeground: + if ( IsControlForeground() ) + getDataWindow().SetControlForeground( GetControlForeground() ); + else + getDataWindow().SetControlForeground(); + break; + + case StateChangedType::ControlFont: + if ( IsControlFont() ) + getDataWindow().SetControlFont( GetControlFont() ); + else + getDataWindow().SetControlFont(); + break; + default:; + } + } + + + void TableControl::Resize() + { + Control::Resize(); + m_pImpl->onResize(); + } + + + void TableControl::SetModel( const PTableModel& _pModel ) + { + m_pImpl->setModel( _pModel ); + } + + + PTableModel TableControl::GetModel() const + { + return m_pImpl->getModel(); + } + + + sal_Int32 TableControl::GetCurrentRow() const + { + return m_pImpl->getCurrentRow(); + } + + + sal_Int32 TableControl::GetCurrentColumn() const + { + return m_pImpl->getCurrentColumn(); + } + + + void TableControl::GoTo( ColPos _nColumn, RowPos _nRow ) + { + m_pImpl->goTo( _nColumn, _nRow ); + } + + + void TableControl::GoToCell(sal_Int32 _nColPos, sal_Int32 _nRowPos) + { + m_pImpl->goTo( _nColPos, _nRowPos ); + } + + + sal_Int32 TableControl::GetSelectedRowCount() const + { + return sal_Int32( m_pImpl->getSelectedRowCount() ); + } + + + sal_Int32 TableControl::GetSelectedRowIndex( sal_Int32 const i_selectionIndex ) const + { + return m_pImpl->getSelectedRowIndex( i_selectionIndex ); + } + + + bool TableControl::IsRowSelected( sal_Int32 const i_rowIndex ) const + { + return m_pImpl->isRowSelected( i_rowIndex ); + } + + + void TableControl::SelectRow( sal_Int32 const i_rowIndex, bool const i_select ) + { + ENSURE_OR_RETURN_VOID( ( i_rowIndex >= 0 ) && ( i_rowIndex < m_pImpl->getModel()->getRowCount() ), + "TableControl::SelectRow: invalid row index!" ); + + if ( i_select ) + { + if ( !m_pImpl->markRowAsSelected( i_rowIndex ) ) + // nothing to do + return; + } + else + { + m_pImpl->markRowAsDeselected( i_rowIndex ); + } + + m_pImpl->invalidateRowRange( i_rowIndex, i_rowIndex ); + Select(); + } + + + void TableControl::SelectAllRows( bool const i_select ) + { + if ( i_select ) + { + if ( !m_pImpl->markAllRowsAsSelected() ) + // nothing to do + return; + } + else + { + if ( !m_pImpl->markAllRowsAsDeselected() ) + // nothing to do + return; + } + + + Invalidate(); + // TODO: can't we do better than this, and invalidate only the rows which changed? + Select(); + } + + + ITableControl& TableControl::getTableControlInterface() + { + return *m_pImpl; + } + + + SelectionEngine* TableControl::getSelEngine() + { + return m_pImpl->getSelEngine(); + } + + + vcl::Window& TableControl::getDataWindow() + { + return m_pImpl->getDataWindow(); + } + + + Reference< XAccessible > TableControl::CreateAccessible() + { + vcl::Window* pParent = GetAccessibleParentWindow(); + ENSURE_OR_RETURN( pParent, "TableControl::CreateAccessible - parent not found", nullptr ); + + return m_pImpl->getAccessible( *pParent ); + } + + + Reference<XAccessible> TableControl::CreateAccessibleControl( sal_Int32 ) + { + SAL_WARN( "svtools", "TableControl::CreateAccessibleControl: to be overwritten!" ); + return nullptr; + } + + + OUString TableControl::GetAccessibleObjectName( vcl::table::AccessibleTableControlObjType eObjType, sal_Int32 _nRow, sal_Int32 _nCol) const + { + OUString aRetText; + //Window* pWin; + switch( eObjType ) + { + case vcl::table::TCTYPE_GRIDCONTROL: + aRetText = "Grid control"; + break; + case vcl::table::TCTYPE_TABLE: + aRetText = "Grid control"; + break; + case vcl::table::TCTYPE_ROWHEADERBAR: + aRetText = "RowHeaderBar"; + break; + case vcl::table::TCTYPE_COLUMNHEADERBAR: + aRetText = "ColumnHeaderBar"; + break; + case vcl::table::TCTYPE_TABLECELL: + //the name of the cell consists of column name and row name if defined + //if the name is equal to cell content, it'll be read twice + if(GetModel()->hasColumnHeaders()) + { + aRetText = GetColumnName(_nCol) + " , "; + } + if(GetModel()->hasRowHeaders()) + { + aRetText += GetRowName(_nRow) + " , "; + } + //aRetText = GetAccessibleCellText(_nRow, _nCol); + break; + case vcl::table::TCTYPE_ROWHEADERCELL: + aRetText = GetRowName(_nRow); + break; + case vcl::table::TCTYPE_COLUMNHEADERCELL: + aRetText = GetColumnName(_nCol); + break; + default: + OSL_FAIL("GridControl::GetAccessibleName: invalid enum!"); + } + return aRetText; + } + + + OUString TableControl::GetAccessibleObjectDescription( vcl::table::AccessibleTableControlObjType eObjType ) const + { + OUString aRetText; + switch( eObjType ) + { + case vcl::table::TCTYPE_GRIDCONTROL: + aRetText = "Grid control description"; + break; + case vcl::table::TCTYPE_TABLE: + aRetText = "TABLE description"; + break; + case vcl::table::TCTYPE_ROWHEADERBAR: + aRetText = "ROWHEADERBAR description"; + break; + case vcl::table::TCTYPE_COLUMNHEADERBAR: + aRetText = "COLUMNHEADERBAR description"; + break; + case vcl::table::TCTYPE_TABLECELL: + // the description of the cell consists of column name and row name if defined + // if the name is equal to cell content, it'll be read twice + if ( GetModel()->hasColumnHeaders() ) + { + aRetText = GetColumnName( GetCurrentColumn() ) + " , "; + } + if ( GetModel()->hasRowHeaders() ) + { + aRetText += GetRowName( GetCurrentRow() ); + } + break; + case vcl::table::TCTYPE_ROWHEADERCELL: + aRetText = "ROWHEADERCELL description"; + break; + case vcl::table::TCTYPE_COLUMNHEADERCELL: + aRetText = "COLUMNHEADERCELL description"; + break; + } + return aRetText; + } + + + OUString TableControl::GetRowName( sal_Int32 _nIndex) const + { + OUString sRowName; + GetModel()->getRowHeading( _nIndex ) >>= sRowName; + return sRowName; + } + + + OUString TableControl::GetColumnName( sal_Int32 _nIndex) const + { + return GetModel()->getColumnModel(_nIndex)->getName(); + } + + + OUString TableControl::GetAccessibleCellText( sal_Int32 _nRowPos, sal_Int32 _nColPos) const + { + return m_pImpl->getCellContentAsString( _nRowPos, _nColPos ); + } + + + void TableControl::FillAccessibleStateSet( + ::utl::AccessibleStateSetHelper& rStateSet, + vcl::table::AccessibleTableControlObjType eObjType ) const + { + switch( eObjType ) + { + case vcl::table::TCTYPE_GRIDCONTROL: + case vcl::table::TCTYPE_TABLE: + + rStateSet.AddState( AccessibleStateType::FOCUSABLE ); + + if ( m_pImpl->getSelEngine()->GetSelectionMode() == SelectionMode::Multiple ) + rStateSet.AddState( AccessibleStateType::MULTI_SELECTABLE); + + if ( HasChildPathFocus() ) + rStateSet.AddState( AccessibleStateType::FOCUSED ); + + if ( IsActive() ) + rStateSet.AddState( AccessibleStateType::ACTIVE ); + + if ( m_pImpl->getDataWindow().IsEnabled() ) + { + rStateSet.AddState( AccessibleStateType::ENABLED ); + rStateSet.AddState( AccessibleStateType::SENSITIVE ); + } + + if ( IsReallyVisible() ) + rStateSet.AddState( AccessibleStateType::VISIBLE ); + + if ( eObjType == vcl::table::TCTYPE_TABLE ) + rStateSet.AddState( AccessibleStateType::MANAGES_DESCENDANTS ); + break; + + case vcl::table::TCTYPE_ROWHEADERBAR: + rStateSet.AddState( AccessibleStateType::VISIBLE ); + rStateSet.AddState( AccessibleStateType::MANAGES_DESCENDANTS ); + break; + + case vcl::table::TCTYPE_COLUMNHEADERBAR: + rStateSet.AddState( AccessibleStateType::VISIBLE ); + rStateSet.AddState( AccessibleStateType::MANAGES_DESCENDANTS ); + break; + + case vcl::table::TCTYPE_TABLECELL: + { + rStateSet.AddState( AccessibleStateType::FOCUSABLE ); + if ( HasChildPathFocus() ) + rStateSet.AddState( AccessibleStateType::FOCUSED ); + rStateSet.AddState( AccessibleStateType::ACTIVE ); + rStateSet.AddState( AccessibleStateType::TRANSIENT ); + rStateSet.AddState( AccessibleStateType::SELECTABLE); + rStateSet.AddState( AccessibleStateType::VISIBLE ); + rStateSet.AddState( AccessibleStateType::SHOWING ); + if ( IsRowSelected( GetCurrentRow() ) ) + // Hmm? Wouldn't we expect the affected row to be a parameter to this function? + rStateSet.AddState( AccessibleStateType::SELECTED ); + } + break; + + case vcl::table::TCTYPE_ROWHEADERCELL: + rStateSet.AddState( AccessibleStateType::VISIBLE ); + rStateSet.AddState( AccessibleStateType::TRANSIENT ); + break; + + case vcl::table::TCTYPE_COLUMNHEADERCELL: + rStateSet.AddState( AccessibleStateType::VISIBLE ); + break; + } + } + + void TableControl::commitCellEventIfAccessibleAlive( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue ) + { + if ( m_pImpl->isAccessibleAlive() ) + m_pImpl->commitCellEvent( i_eventID, i_newValue, i_oldValue ); + } + + void TableControl::commitTableEventIfAccessibleAlive( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue ) + { + if ( m_pImpl->isAccessibleAlive() ) + m_pImpl->commitTableEvent( i_eventID, i_newValue, i_oldValue ); + } + + tools::Rectangle TableControl::GetWindowExtentsRelative(const vcl::Window *pRelativeWindow) const + { + return Control::GetWindowExtentsRelative( pRelativeWindow ); + } + + void TableControl::GrabFocus() + { + Control::GrabFocus(); + } + + Reference< XAccessible > TableControl::GetAccessible() + { + return Control::GetAccessible(); + } + + vcl::Window* TableControl::GetAccessibleParentWindow() const + { + return Control::GetAccessibleParentWindow(); + } + + vcl::Window* TableControl::GetWindowInstance() + { + return this; + } + + + bool TableControl::HasRowHeader() + { + return GetModel()->hasRowHeaders(); + } + + + bool TableControl::HasColHeader() + { + return GetModel()->hasColumnHeaders(); + } + + + sal_Int32 TableControl::GetAccessibleControlCount() const + { + // TC_TABLE is always defined, no matter whether empty or not + sal_Int32 count = 1; + if ( GetModel()->hasRowHeaders() ) + ++count; + if ( GetModel()->hasColumnHeaders() ) + ++count; + return count; + } + + + bool TableControl::ConvertPointToControlIndex( sal_Int32& _rnIndex, const Point& _rPoint ) + { + sal_Int32 nRow = m_pImpl->getRowAtPoint( _rPoint ); + sal_Int32 nCol = m_pImpl->getColAtPoint( _rPoint ); + _rnIndex = nRow * GetColumnCount() + nCol; + return nRow >= 0; + } + + + sal_Int32 TableControl::GetRowCount() const + { + return GetModel()->getRowCount(); + } + + + sal_Int32 TableControl::GetColumnCount() const + { + return GetModel()->getColumnCount(); + } + + + bool TableControl::ConvertPointToCellAddress( sal_Int32& _rnRow, sal_Int32& _rnColPos, const Point& _rPoint ) + { + _rnRow = m_pImpl->getRowAtPoint( _rPoint ); + _rnColPos = m_pImpl->getColAtPoint( _rPoint ); + return _rnRow >= 0; + } + + + void TableControl::FillAccessibleStateSetForCell( ::utl::AccessibleStateSetHelper& _rStateSet, sal_Int32 _nRow, sal_uInt16 ) const + { + if ( IsRowSelected( _nRow ) ) + _rStateSet.AddState( AccessibleStateType::SELECTED ); + if ( HasChildPathFocus() ) + _rStateSet.AddState( AccessibleStateType::FOCUSED ); + else // only transient when column is not focused + _rStateSet.AddState( AccessibleStateType::TRANSIENT ); + + _rStateSet.AddState( AccessibleStateType::VISIBLE ); + _rStateSet.AddState( AccessibleStateType::SHOWING ); + _rStateSet.AddState( AccessibleStateType::ENABLED ); + _rStateSet.AddState( AccessibleStateType::SENSITIVE ); + _rStateSet.AddState( AccessibleStateType::ACTIVE ); + } + + + tools::Rectangle TableControl::GetFieldCharacterBounds(sal_Int32,sal_Int32,sal_Int32 nIndex) + { + return GetCharacterBounds(nIndex); + } + + + sal_Int32 TableControl::GetFieldIndexAtPoint(sal_Int32,sal_Int32,const Point& _rPoint) + { + return GetIndexForPoint(_rPoint); + } + + + tools::Rectangle TableControl::calcHeaderRect(bool _bIsColumnBar ) + { + return m_pImpl->calcHeaderRect( !_bIsColumnBar ); + } + + + tools::Rectangle TableControl::calcHeaderCellRect( bool _bIsColumnBar, sal_Int32 nPos ) + { + return m_pImpl->calcHeaderCellRect( _bIsColumnBar, nPos ); + } + + + tools::Rectangle TableControl::calcTableRect() + { + return m_pImpl->calcTableRect(); + } + + + tools::Rectangle TableControl::calcCellRect( sal_Int32 _nRowPos, sal_Int32 _nColPos ) + { + return m_pImpl->calcCellRect( _nRowPos, _nColPos ); + } + + + IMPL_LINK_NOARG(TableControl, ImplSelectHdl, LinkParamNone*, void) + { + Select(); + } + + + void TableControl::Select() + { + ImplCallEventListenersAndHandler( VclEventId::TableRowSelect, nullptr ); + + if ( m_pImpl->isAccessibleAlive() ) + { + m_pImpl->commitAccessibleEvent( AccessibleEventId::SELECTION_CHANGED ); + + m_pImpl->commitTableEvent( AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, Any(), Any() ); + // TODO: why do we notify this when the *selection* changed? Shouldn't we find a better place for this, + // actually, when the active descendant, i.e. the current cell, *really* changed? + } + } + +} // namespace svt::table + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/table/tablecontrol_impl.cxx b/svtools/source/table/tablecontrol_impl.cxx new file mode 100644 index 000000000..c82a27276 --- /dev/null +++ b/svtools/source/table/tablecontrol_impl.cxx @@ -0,0 +1,2552 @@ +/* -*- 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 <table/tablecontrol.hxx> +#include <table/defaultinputhandler.hxx> +#include <table/tablemodel.hxx> + +#include "tabledatawindow.hxx" +#include "tablecontrol_impl.hxx" +#include "tablegeometry.hxx" + +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp> + +#include <comphelper/flagguard.hxx> +#include <vcl/accessiblefactory.hxx> +#include <vcl/scrbar.hxx> +#include <vcl/seleng.hxx> +#include <vcl/settings.hxx> +#include <vcl/image.hxx> +#include <tools/diagnose_ex.h> +#include <tools/debug.hxx> + +#include <cstdlib> +#include <numeric> + +#define MIN_COLUMN_WIDTH_PIXEL 4 + + +namespace svt::table +{ + + + using ::com::sun::star::accessibility::AccessibleTableModelChange; + using ::com::sun::star::uno::Any; + using ::com::sun::star::accessibility::XAccessible; + using ::com::sun::star::uno::Reference; + + namespace AccessibleEventId = ::com::sun::star::accessibility::AccessibleEventId; + namespace AccessibleTableModelChangeType = ::com::sun::star::accessibility::AccessibleTableModelChangeType; + + + //= SuppressCursor + + namespace { + + class SuppressCursor + { + private: + ITableControl& m_rTable; + + public: + explicit SuppressCursor( ITableControl& _rTable ) + :m_rTable( _rTable ) + { + m_rTable.hideCursor(); + } + ~SuppressCursor() + { + m_rTable.showCursor(); + } + }; + + + //= EmptyTableModel + + /** default implementation of an ->ITableModel, used as fallback when no + real model is present + + Instances of this class are static in any way, and provide the least + necessary default functionality for a table model. + */ + class EmptyTableModel : public ITableModel + { + public: + EmptyTableModel() + { + } + + // ITableModel overridables + virtual TableSize getColumnCount() const override + { + return 0; + } + virtual TableSize getRowCount() const override + { + return 0; + } + virtual bool hasColumnHeaders() const override + { + return false; + } + virtual bool hasRowHeaders() const override + { + return false; + } + virtual PColumnModel getColumnModel( ColPos ) override + { + OSL_FAIL( "EmptyTableModel::getColumnModel: invalid call!" ); + return PColumnModel(); + } + virtual PTableRenderer getRenderer() const override + { + return PTableRenderer(); + } + virtual PTableInputHandler getInputHandler() const override + { + return PTableInputHandler(); + } + virtual TableMetrics getRowHeight() const override + { + return 5 * 100; + } + virtual TableMetrics getColumnHeaderHeight() const override + { + return 0; + } + virtual TableMetrics getRowHeaderWidth() const override + { + return 0; + } + virtual ScrollbarVisibility getVerticalScrollbarVisibility() const override + { + return ScrollbarShowNever; + } + virtual ScrollbarVisibility getHorizontalScrollbarVisibility() const override + { + return ScrollbarShowNever; + } + virtual void addTableModelListener( const PTableModelListener& ) override {} + virtual void removeTableModelListener( const PTableModelListener& ) override {} + virtual ::std::optional< ::Color > getLineColor() const override + { + return ::std::optional< ::Color >(); + } + virtual ::std::optional< ::Color > getHeaderBackgroundColor() const override + { + return ::std::optional< ::Color >(); + } + virtual ::std::optional< ::Color > getHeaderTextColor() const override + { + return ::std::optional< ::Color >(); + } + virtual ::std::optional< ::Color > getActiveSelectionBackColor() const override + { + return ::std::optional< ::Color >(); + } + virtual ::std::optional< ::Color > getInactiveSelectionBackColor() const override + { + return ::std::optional< ::Color >(); + } + virtual ::std::optional< ::Color > getActiveSelectionTextColor() const override + { + return ::std::optional< ::Color >(); + } + virtual ::std::optional< ::Color > getInactiveSelectionTextColor() const override + { + return ::std::optional< ::Color >(); + } + virtual ::std::optional< ::Color > getTextColor() const override + { + return ::std::optional< ::Color >(); + } + virtual ::std::optional< ::Color > getTextLineColor() const override + { + return ::std::optional< ::Color >(); + } + virtual ::std::optional< ::std::vector< ::Color > > getRowBackgroundColors() const override + { + return ::std::optional< ::std::vector< ::Color > >(); + } + virtual css::style::VerticalAlignment getVerticalAlign() const override + { + return css::style::VerticalAlignment(0); + } + virtual ITableDataSort* getSortAdapter() override + { + return nullptr; + } + virtual bool isEnabled() const override + { + return true; + } + virtual void getCellContent( ColPos const, RowPos const, css::uno::Any& o_cellContent ) override + { + o_cellContent.clear(); + } + virtual void getCellToolTip( ColPos const, RowPos const, css::uno::Any& ) override + { + } + virtual Any getRowHeading( RowPos const ) const override + { + return Any(); + } + }; + + } + + TableControl_Impl::TableControl_Impl( TableControl& _rAntiImpl ) + :m_rAntiImpl ( _rAntiImpl ) + ,m_pModel ( std::make_shared<EmptyTableModel>() ) + ,m_pInputHandler ( ) + ,m_nRowHeightPixel ( 15 ) + ,m_nColHeaderHeightPixel( 0 ) + ,m_nRowHeaderWidthPixel ( 0 ) + ,m_nColumnCount ( 0 ) + ,m_nRowCount ( 0 ) + ,m_nCurColumn ( COL_INVALID ) + ,m_nCurRow ( ROW_INVALID ) + ,m_nLeftColumn ( 0 ) + ,m_nTopRow ( 0 ) + ,m_nCursorHidden ( 1 ) + ,m_pDataWindow ( VclPtr<TableDataWindow>::Create( *this ) ) + ,m_pVScroll ( nullptr ) + ,m_pHScroll ( nullptr ) + ,m_pScrollCorner ( nullptr ) + ,m_aSelectedRows ( ) + ,m_pTableFunctionSet ( new TableFunctionSet( this ) ) + ,m_nAnchor ( -1 ) + ,m_bUpdatingColWidths ( false ) + ,m_pAccessibleTable ( nullptr ) + { + m_pSelEngine.reset( new SelectionEngine( m_pDataWindow.get(), m_pTableFunctionSet.get() ) ); + m_pSelEngine->SetSelectionMode(SelectionMode::Single); + m_pDataWindow->SetPosPixel( Point( 0, 0 ) ); + m_pDataWindow->Show(); + } + + TableControl_Impl::~TableControl_Impl() + { + m_pVScroll.disposeAndClear(); + m_pHScroll.disposeAndClear(); + m_pScrollCorner.disposeAndClear(); + m_pDataWindow.disposeAndClear(); + m_pTableFunctionSet.reset(); + m_pSelEngine.reset(); + } + + void TableControl_Impl::setModel( const PTableModel& _pModel ) + { + SuppressCursor aHideCursor( *this ); + + if ( m_pModel ) + m_pModel->removeTableModelListener( shared_from_this() ); + + m_pModel = _pModel; + if ( !m_pModel) + m_pModel = std::make_shared<EmptyTableModel>(); + + m_pModel->addTableModelListener( shared_from_this() ); + + m_nCurRow = ROW_INVALID; + m_nCurColumn = COL_INVALID; + + // recalc some model-dependent cached info + impl_ni_updateCachedModelValues(); + impl_ni_relayout(); + + // completely invalidate + m_rAntiImpl.Invalidate(); + + // reset cursor to (0,0) + if ( m_nRowCount ) m_nCurRow = 0; + if ( m_nColumnCount ) m_nCurColumn = 0; + } + + + namespace + { + bool lcl_adjustSelectedRows( ::std::vector< RowPos >& io_selectionIndexes, RowPos const i_firstAffectedRowIndex, TableSize const i_offset ) + { + bool didChanges = false; + for (auto & selectionIndex : io_selectionIndexes) + { + if ( selectionIndex < i_firstAffectedRowIndex ) + continue; + selectionIndex += i_offset; + didChanges = true; + } + return didChanges; + } + } + + + void TableControl_Impl::rowsInserted( RowPos i_first, RowPos i_last ) + { + OSL_PRECOND( i_last >= i_first, "TableControl_Impl::rowsInserted: invalid row indexes!" ); + + TableSize const insertedRows = i_last - i_first + 1; + + // adjust selection, if necessary + bool const selectionChanged = lcl_adjustSelectedRows( m_aSelectedRows, i_first, insertedRows ); + + // adjust our cached row count + m_nRowCount = m_pModel->getRowCount(); + + // if the rows have been inserted before the current row, adjust this + if ( i_first <= m_nCurRow ) + goTo( m_nCurColumn, m_nCurRow + insertedRows ); + + // relayout, since the scrollbar need might have changed + impl_ni_relayout(); + + // notify A1YY events + if ( impl_isAccessibleAlive() ) + { + impl_commitAccessibleEvent( AccessibleEventId::TABLE_MODEL_CHANGED, + Any( AccessibleTableModelChange( AccessibleTableModelChangeType::ROWS_INSERTED, i_first, i_last, -1, -1 ) ) + ); + } + + // schedule repaint + invalidateRowRange( i_first, ROW_INVALID ); + + // call selection handlers, if necessary + if ( selectionChanged ) + m_rAntiImpl.Select(); + } + + + void TableControl_Impl::rowsRemoved( RowPos i_first, RowPos i_last ) + { + sal_Int32 firstRemovedRow = i_first; + sal_Int32 lastRemovedRow = i_last; + + // adjust selection, if necessary + bool selectionChanged = false; + if ( i_first == -1 ) + { + selectionChanged = markAllRowsAsDeselected(); + + firstRemovedRow = 0; + lastRemovedRow = m_nRowCount - 1; + } + else + { + ENSURE_OR_RETURN_VOID( i_last >= i_first, "TableControl_Impl::rowsRemoved: illegal indexes!" ); + + for ( sal_Int32 row = i_first; row <= i_last; ++row ) + { + if ( markRowAsDeselected( row ) ) + selectionChanged = true; + } + + if ( lcl_adjustSelectedRows( m_aSelectedRows, i_last + 1, i_first - i_last - 1 ) ) + selectionChanged = true; + } + + // adjust cached row count + m_nRowCount = m_pModel->getRowCount(); + + // adjust the current row, if it is larger than the row count now + if ( m_nCurRow >= m_nRowCount ) + { + if ( m_nRowCount > 0 ) + goTo( m_nCurColumn, m_nRowCount - 1 ); + else + { + m_nCurRow = ROW_INVALID; + m_nTopRow = 0; + } + } + else if ( m_nRowCount == 0 ) + { + m_nTopRow = 0; + } + + + // relayout, since the scrollbar need might have changed + impl_ni_relayout(); + + // notify A11Y events + if ( impl_isAccessibleAlive() ) + { + commitTableEvent( + AccessibleEventId::TABLE_MODEL_CHANGED, + Any( AccessibleTableModelChange( + AccessibleTableModelChangeType::ROWS_REMOVED, + firstRemovedRow, + lastRemovedRow, + -1, + -1 + ) ), + Any() + ); + } + + // schedule a repaint + invalidateRowRange( firstRemovedRow, ROW_INVALID ); + + // call selection handlers, if necessary + if ( selectionChanged ) + m_rAntiImpl.Select(); + } + + + void TableControl_Impl::columnInserted() + { + m_nColumnCount = m_pModel->getColumnCount(); + impl_ni_relayout(); + + m_rAntiImpl.Invalidate(); + } + + + void TableControl_Impl::columnRemoved() + { + m_nColumnCount = m_pModel->getColumnCount(); + + // adjust the current column, if it is larger than the column count now + if ( m_nCurColumn >= m_nColumnCount ) + { + if ( m_nColumnCount > 0 ) + goTo( m_nCurColumn - 1, m_nCurRow ); + else + m_nCurColumn = COL_INVALID; + } + + impl_ni_relayout(); + + m_rAntiImpl.Invalidate(); + } + + + void TableControl_Impl::allColumnsRemoved() + { + m_nColumnCount = m_pModel->getColumnCount(); + impl_ni_relayout(); + + m_rAntiImpl.Invalidate(); + } + + + void TableControl_Impl::cellsUpdated( RowPos const i_firstRow, RowPos const i_lastRow ) + { + invalidateRowRange( i_firstRow, i_lastRow ); + } + + + void TableControl_Impl::tableMetricsChanged() + { + impl_ni_updateCachedTableMetrics(); + impl_ni_relayout(); + m_rAntiImpl.Invalidate(); + } + + + void TableControl_Impl::impl_invalidateColumn( ColPos const i_column ) + { + tools::Rectangle const aAllCellsArea( impl_getAllVisibleCellsArea() ); + + const TableColumnGeometry aColumn( *this, aAllCellsArea, i_column ); + if ( aColumn.isValid() ) + m_rAntiImpl.Invalidate( aColumn.getRect() ); + } + + + void TableControl_Impl::columnChanged( ColPos const i_column, ColumnAttributeGroup const i_attributeGroup ) + { + ColumnAttributeGroup nGroup( i_attributeGroup ); + if ( nGroup & ColumnAttributeGroup::APPEARANCE ) + { + impl_invalidateColumn( i_column ); + nGroup &= ~ColumnAttributeGroup::APPEARANCE; + } + + if ( nGroup & ColumnAttributeGroup::WIDTH ) + { + if ( !m_bUpdatingColWidths ) + { + impl_ni_relayout( i_column ); + invalidate( TableArea::All ); + } + + nGroup &= ~ColumnAttributeGroup::WIDTH; + } + + OSL_ENSURE( ( nGroup == ColumnAttributeGroup::NONE ) || ( i_attributeGroup == ColumnAttributeGroup::ALL ), + "TableControl_Impl::columnChanged: don't know how to handle this change!" ); + } + + + tools::Rectangle TableControl_Impl::impl_getAllVisibleCellsArea() const + { + tools::Rectangle aArea( Point( 0, 0 ), Size( 0, 0 ) ); + + // determine the right-most border of the last column which is + // at least partially visible + aArea.SetRight( m_nRowHeaderWidthPixel ); + if ( !m_aColumnWidths.empty() ) + { + // the number of pixels which are scrolled out of the left hand + // side of the window + const tools::Long nScrolledOutLeft = m_nLeftColumn == 0 ? 0 : m_aColumnWidths[ m_nLeftColumn - 1 ].getEnd(); + + ColumnPositions::const_reverse_iterator loop = m_aColumnWidths.rbegin(); + do + { + aArea.SetRight(loop->getEnd() - nScrolledOutLeft); + ++loop; + } + while ( ( loop != m_aColumnWidths.rend() ) + && ( loop->getEnd() - nScrolledOutLeft >= aArea.Right() ) + ); + } + // so far, aArea.Right() denotes the first pixel *after* the cell area + aArea.AdjustRight( -1 ); + + // determine the last row which is at least partially visible + aArea.SetBottom( + m_nColHeaderHeightPixel + + impl_getVisibleRows( true ) * m_nRowHeightPixel + - 1 ); + + return aArea; + } + + + tools::Rectangle TableControl_Impl::impl_getAllVisibleDataCellArea() const + { + tools::Rectangle aArea( impl_getAllVisibleCellsArea() ); + aArea.SetLeft( m_nRowHeaderWidthPixel ); + aArea.SetTop( m_nColHeaderHeightPixel ); + return aArea; + } + + + void TableControl_Impl::impl_ni_updateCachedTableMetrics() + { + m_nRowHeightPixel = m_rAntiImpl.LogicToPixel(Size(0, m_pModel->getRowHeight()), MapMode(MapUnit::MapAppFont)).Height(); + + m_nColHeaderHeightPixel = 0; + if ( m_pModel->hasColumnHeaders() ) + m_nColHeaderHeightPixel = m_rAntiImpl.LogicToPixel(Size(0, m_pModel->getColumnHeaderHeight()), MapMode(MapUnit::MapAppFont)).Height(); + + m_nRowHeaderWidthPixel = 0; + if ( m_pModel->hasRowHeaders() ) + m_nRowHeaderWidthPixel = m_rAntiImpl.LogicToPixel(Size(m_pModel->getRowHeaderWidth(), 0), MapMode(MapUnit::MapAppFont)).Width(); + } + + + void TableControl_Impl::impl_ni_updateCachedModelValues() + { + m_pInputHandler = m_pModel->getInputHandler(); + if ( !m_pInputHandler ) + m_pInputHandler = std::make_shared<DefaultInputHandler>(); + + m_nColumnCount = m_pModel->getColumnCount(); + if ( m_nLeftColumn >= m_nColumnCount ) + m_nLeftColumn = ( m_nColumnCount > 0 ) ? m_nColumnCount - 1 : 0; + + m_nRowCount = m_pModel->getRowCount(); + if ( m_nTopRow >= m_nRowCount ) + m_nTopRow = ( m_nRowCount > 0 ) ? m_nRowCount - 1 : 0; + + impl_ni_updateCachedTableMetrics(); + } + + + namespace + { + + /// determines whether a scrollbar is needed for the given values + bool lcl_determineScrollbarNeed( tools::Long const i_position, ScrollbarVisibility const i_visibility, + tools::Long const i_availableSpace, tools::Long const i_neededSpace ) + { + if ( i_visibility == ScrollbarShowNever ) + return false; + if ( i_visibility == ScrollbarShowAlways ) + return true; + if ( i_position > 0 ) + return true; + if ( i_availableSpace >= i_neededSpace ) + return false; + return true; + } + + + void lcl_setButtonRepeat( vcl::Window& _rWindow ) + { + AllSettings aSettings = _rWindow.GetSettings(); + MouseSettings aMouseSettings = aSettings.GetMouseSettings(); + + aMouseSettings.SetButtonRepeat( 0 ); + aSettings.SetMouseSettings( aMouseSettings ); + + _rWindow.SetSettings( aSettings, true ); + } + + + bool lcl_updateScrollbar( vcl::Window& _rParent, VclPtr<ScrollBar>& _rpBar, + bool const i_needBar, tools::Long _nVisibleUnits, + tools::Long _nPosition, tools::Long _nRange, + bool _bHorizontal, const Link<ScrollBar*,void>& _rScrollHandler ) + { + // do we currently have the scrollbar? + bool bHaveBar = _rpBar != nullptr; + + // do we need to correct the scrollbar visibility? + if ( bHaveBar && !i_needBar ) + { + if ( _rpBar->IsTracking() ) + _rpBar->EndTracking(); + _rpBar.disposeAndClear(); + } + else if ( !bHaveBar && i_needBar ) + { + _rpBar = VclPtr<ScrollBar>::Create( + + &_rParent, + WB_DRAG | ( _bHorizontal ? WB_HSCROLL : WB_VSCROLL ) + ); + _rpBar->SetScrollHdl( _rScrollHandler ); + // get some speed into the scrolling... + lcl_setButtonRepeat( *_rpBar ); + } + + if ( _rpBar ) + { + _rpBar->SetRange( Range( 0, _nRange ) ); + _rpBar->SetVisibleSize( _nVisibleUnits ); + _rpBar->SetPageSize( _nVisibleUnits ); + _rpBar->SetLineSize( 1 ); + _rpBar->SetThumbPos( _nPosition ); + _rpBar->Show(); + } + + return ( bHaveBar != i_needBar ); + } + + + /** returns the number of rows fitting into the given range, + for the given row height. Partially fitting rows are counted, too, if the + respective parameter says so. + */ + TableSize lcl_getRowsFittingInto( tools::Long _nOverallHeight, tools::Long _nRowHeightPixel, bool _bAcceptPartialRow ) + { + return _bAcceptPartialRow + ? ( _nOverallHeight + ( _nRowHeightPixel - 1 ) ) / _nRowHeightPixel + : _nOverallHeight / _nRowHeightPixel; + } + + + /** returns the number of columns fitting into the given area, + with the first visible column as given. Partially fitting columns are counted, too, + if the respective parameter says so. + */ + TableSize lcl_getColumnsVisibleWithin( const tools::Rectangle& _rArea, ColPos _nFirstVisibleColumn, + const TableControl_Impl& _rControl, bool _bAcceptPartialRow ) + { + TableSize visibleColumns = 0; + TableColumnGeometry aColumn( _rControl, _rArea, _nFirstVisibleColumn ); + while ( aColumn.isValid() ) + { + if ( !_bAcceptPartialRow ) + if ( aColumn.getRect().Right() > _rArea.Right() ) + // this column is only partially visible, and this is not allowed + break; + + aColumn.moveRight(); + ++visibleColumns; + } + return visibleColumns; + } + + } + + + tools::Long TableControl_Impl::impl_ni_calculateColumnWidths( ColPos const i_assumeInflexibleColumnsUpToIncluding, + bool const i_assumeVerticalScrollbar, ::std::vector< tools::Long >& o_newColWidthsPixel ) const + { + // the available horizontal space + tools::Long gridWidthPixel = m_rAntiImpl.GetOutputSizePixel().Width(); + ENSURE_OR_RETURN( !!m_pModel, "TableControl_Impl::impl_ni_calculateColumnWidths: not allowed without a model!", gridWidthPixel ); + if ( m_pModel->hasRowHeaders() && ( gridWidthPixel != 0 ) ) + { + gridWidthPixel -= m_nRowHeaderWidthPixel; + } + + if ( i_assumeVerticalScrollbar && ( m_pModel->getVerticalScrollbarVisibility() != ScrollbarShowNever ) ) + { + tools::Long nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize(); + gridWidthPixel -= nScrollbarMetrics; + } + + // no need to do anything without columns + TableSize const colCount = m_pModel->getColumnCount(); + if ( colCount == 0 ) + return gridWidthPixel; + + // collect some meta data for our columns: + // - their current (pixel) metrics + tools::Long accumulatedCurrentWidth = 0; + ::std::vector< tools::Long > currentColWidths; + currentColWidths.reserve( colCount ); + typedef ::std::vector< ::std::pair< tools::Long, long > > ColumnLimits; + ColumnLimits effectiveColumnLimits; + effectiveColumnLimits.reserve( colCount ); + tools::Long accumulatedMinWidth = 0; + tools::Long accumulatedMaxWidth = 0; + // - their relative flexibility + ::std::vector< ::sal_Int32 > columnFlexibilities; + columnFlexibilities.reserve( colCount ); + tools::Long flexibilityDenominator = 0; + size_t flexibleColumnCount = 0; + for ( ColPos col = 0; col < colCount; ++col ) + { + PColumnModel const pColumn = m_pModel->getColumnModel( col ); + ENSURE_OR_THROW( !!pColumn, "invalid column returned by the model!" ); + + // current width + tools::Long const currentWidth = appFontWidthToPixel( pColumn->getWidth() ); + currentColWidths.push_back( currentWidth ); + + // accumulated width + accumulatedCurrentWidth += currentWidth; + + // flexibility + ::sal_Int32 flexibility = pColumn->getFlexibility(); + OSL_ENSURE( flexibility >= 0, "TableControl_Impl::impl_ni_calculateColumnWidths: a column's flexibility should be non-negative." ); + if ( ( flexibility < 0 ) // normalization + || ( !pColumn->isResizable() ) // column not resizable => no auto-resize + || ( col <= i_assumeInflexibleColumnsUpToIncluding ) // column shall be treated as inflexible => respect this + ) + flexibility = 0; + + // min/max width + tools::Long effectiveMin = currentWidth, effectiveMax = currentWidth; + // if the column is not flexible, it will not be asked for min/max, but we assume the current width as limit then + if ( flexibility > 0 ) + { + tools::Long const minWidth = appFontWidthToPixel( pColumn->getMinWidth() ); + if ( minWidth > 0 ) + effectiveMin = minWidth; + else + effectiveMin = MIN_COLUMN_WIDTH_PIXEL; + + tools::Long const maxWidth = appFontWidthToPixel( pColumn->getMaxWidth() ); + OSL_ENSURE( minWidth <= maxWidth, "TableControl_Impl::impl_ni_calculateColumnWidths: pretty undecided 'bout its width limits, this column!" ); + if ( ( maxWidth > 0 ) && ( maxWidth >= minWidth ) ) + effectiveMax = maxWidth; + else + effectiveMax = gridWidthPixel; // TODO: any better guess here? + + if ( effectiveMin == effectiveMax ) + // if the min and the max are identical, this implies no flexibility at all + flexibility = 0; + } + + columnFlexibilities.push_back( flexibility ); + flexibilityDenominator += flexibility; + if ( flexibility > 0 ) + ++flexibleColumnCount; + + effectiveColumnLimits.emplace_back( effectiveMin, effectiveMax ); + accumulatedMinWidth += effectiveMin; + accumulatedMaxWidth += effectiveMax; + } + + o_newColWidthsPixel = currentColWidths; + if ( flexibilityDenominator == 0 ) + { + // no column is flexible => don't adjust anything + } + else if ( gridWidthPixel > accumulatedCurrentWidth ) + { // we have space to give away ... + tools::Long distributePixel = gridWidthPixel - accumulatedCurrentWidth; + if ( gridWidthPixel > accumulatedMaxWidth ) + { + // ... but the column's maximal widths are still less than we have + // => set them all to max + for ( svt::table::TableSize i = 0; i < colCount; ++i ) + { + o_newColWidthsPixel[i] = effectiveColumnLimits[i].second; + } + } + else + { + bool startOver = false; + do + { + startOver = false; + // distribute the remaining space amongst all columns with a positive flexibility + for ( size_t i=0; i<o_newColWidthsPixel.size() && !startOver; ++i ) + { + tools::Long const columnFlexibility = columnFlexibilities[i]; + if ( columnFlexibility == 0 ) + continue; + + tools::Long newColWidth = currentColWidths[i] + columnFlexibility * distributePixel / flexibilityDenominator; + + if ( newColWidth > effectiveColumnLimits[i].second ) + { // that was too much, we hit the col's maximum + // set the new width to exactly this maximum + newColWidth = effectiveColumnLimits[i].second; + // adjust the flexibility denominator ... + flexibilityDenominator -= columnFlexibility; + columnFlexibilities[i] = 0; + --flexibleColumnCount; + // ... and the remaining width ... + tools::Long const difference = newColWidth - currentColWidths[i]; + distributePixel -= difference; + // ... this way, we ensure that the width not taken up by this column is consumed by the other + // flexible ones (if there are some) + + // and start over with the first column, since there might be earlier columns which need + // to be recalculated now + startOver = true; + } + + o_newColWidthsPixel[i] = newColWidth; + } + } + while ( startOver ); + + // are there pixels left (might be caused by rounding errors)? + distributePixel = gridWidthPixel - ::std::accumulate( o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0 ); + while ( ( distributePixel > 0 ) && ( flexibleColumnCount > 0 ) ) + { + // yes => ignore relative flexibilities, and subsequently distribute single pixels to all flexible + // columns which did not yet reach their maximum. + for ( size_t i=0; ( i < o_newColWidthsPixel.size() ) && ( distributePixel > 0 ); ++i ) + { + if ( columnFlexibilities[i] == 0 ) + continue; + + OSL_ENSURE( o_newColWidthsPixel[i] <= effectiveColumnLimits[i].second, + "TableControl_Impl::impl_ni_calculateColumnWidths: inconsistency!" ); + if ( o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first ) + { + columnFlexibilities[i] = 0; + --flexibleColumnCount; + continue; + } + + ++o_newColWidthsPixel[i]; + --distributePixel; + } + } + } + } + else if ( gridWidthPixel < accumulatedCurrentWidth ) + { // we need to take away some space from the columns which allow it ... + tools::Long takeAwayPixel = accumulatedCurrentWidth - gridWidthPixel; + if ( gridWidthPixel < accumulatedMinWidth ) + { + // ... but the column's minimal widths are still more than we have + // => set them all to min + for ( svt::table::TableSize i = 0; i < colCount; ++i ) + { + o_newColWidthsPixel[i] = effectiveColumnLimits[i].first; + } + } + else + { + bool startOver = false; + do + { + startOver = false; + // take away the space we need from the columns with a positive flexibility + for ( size_t i=0; i<o_newColWidthsPixel.size() && !startOver; ++i ) + { + tools::Long const columnFlexibility = columnFlexibilities[i]; + if ( columnFlexibility == 0 ) + continue; + + tools::Long newColWidth = currentColWidths[i] - columnFlexibility * takeAwayPixel / flexibilityDenominator; + + if ( newColWidth < effectiveColumnLimits[i].first ) + { // that was too much, we hit the col's minimum + // set the new width to exactly this minimum + newColWidth = effectiveColumnLimits[i].first; + // adjust the flexibility denominator ... + flexibilityDenominator -= columnFlexibility; + columnFlexibilities[i] = 0; + --flexibleColumnCount; + // ... and the remaining width ... + tools::Long const difference = currentColWidths[i] - newColWidth; + takeAwayPixel -= difference; + + // and start over with the first column, since there might be earlier columns which need + // to be recalculated now + startOver = true; + } + + o_newColWidthsPixel[i] = newColWidth; + } + } + while ( startOver ); + + // are there pixels left (might be caused by rounding errors)? + takeAwayPixel = ::std::accumulate( o_newColWidthsPixel.begin(), o_newColWidthsPixel.end(), 0 ) - gridWidthPixel; + while ( ( takeAwayPixel > 0 ) && ( flexibleColumnCount > 0 ) ) + { + // yes => ignore relative flexibilities, and subsequently take away pixels from all flexible + // columns which did not yet reach their minimum. + for ( size_t i=0; ( i < o_newColWidthsPixel.size() ) && ( takeAwayPixel > 0 ); ++i ) + { + if ( columnFlexibilities[i] == 0 ) + continue; + + OSL_ENSURE( o_newColWidthsPixel[i] >= effectiveColumnLimits[i].first, + "TableControl_Impl::impl_ni_calculateColumnWidths: inconsistency!" ); + if ( o_newColWidthsPixel[i] <= effectiveColumnLimits[i].first ) + { + columnFlexibilities[i] = 0; + --flexibleColumnCount; + continue; + } + + --o_newColWidthsPixel[i]; + --takeAwayPixel; + } + } + } + } + + return gridWidthPixel; + } + + + void TableControl_Impl::impl_ni_relayout( ColPos const i_assumeInflexibleColumnsUpToIncluding ) + { + ENSURE_OR_RETURN_VOID( !m_bUpdatingColWidths, "TableControl_Impl::impl_ni_relayout: recursive call detected!" ); + + m_aColumnWidths.resize( 0 ); + if ( !m_pModel ) + return; + + ::comphelper::FlagRestorationGuard const aWidthUpdateFlag( m_bUpdatingColWidths, true ); + SuppressCursor aHideCursor( *this ); + + // layouting steps: + + // 1. adjust column widths, leaving space for a vertical scrollbar + // 2. determine need for a vertical scrollbar + // - V-YES: all fine, result from 1. is still valid + // - V-NO: result from 1. is still under consideration + + // 3. determine need for a horizontal scrollbar + // - H-NO: all fine, result from 2. is still valid + // - H-YES: reconsider need for a vertical scrollbar, if result of 2. was V-NO + // - V-YES: all fine, result from 1. is still valid + // - V-NO: redistribute the remaining space (if any) amongst all columns which allow it + + ::std::vector< tools::Long > newWidthsPixel; + tools::Long gridWidthPixel = impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding, true, newWidthsPixel ); + + // the width/height of a scrollbar, needed several times below + tools::Long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize(); + + // determine the playground for the data cells (excluding headers) + // TODO: what if the control is smaller than needed for the headers/scrollbars? + tools::Rectangle aDataCellPlayground( Point( 0, 0 ), m_rAntiImpl.GetOutputSizePixel() ); + aDataCellPlayground.SetLeft( m_nRowHeaderWidthPixel ); + aDataCellPlayground.SetTop( m_nColHeaderHeightPixel ); + + OSL_ENSURE( ( m_nRowCount == m_pModel->getRowCount() ) && ( m_nColumnCount == m_pModel->getColumnCount() ), + "TableControl_Impl::impl_ni_relayout: how is this expected to work with invalid data?" ); + tools::Long const nAllColumnsWidth = ::std::accumulate( newWidthsPixel.begin(), newWidthsPixel.end(), 0 ); + + ScrollbarVisibility const eVertScrollbar = m_pModel->getVerticalScrollbarVisibility(); + ScrollbarVisibility const eHorzScrollbar = m_pModel->getHorizontalScrollbarVisibility(); + + // do we need a vertical scrollbar? + bool bNeedVerticalScrollbar = lcl_determineScrollbarNeed( + m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(), m_nRowHeightPixel * m_nRowCount ); + bool bFirstRoundVScrollNeed = false; + if ( bNeedVerticalScrollbar ) + { + aDataCellPlayground.AdjustRight( -nScrollbarMetrics ); + bFirstRoundVScrollNeed = true; + } + + // do we need a horizontal scrollbar? + bool const bNeedHorizontalScrollbar = lcl_determineScrollbarNeed( + m_nLeftColumn, eHorzScrollbar, aDataCellPlayground.GetWidth(), nAllColumnsWidth ); + if ( bNeedHorizontalScrollbar ) + { + aDataCellPlayground.AdjustBottom( -nScrollbarMetrics ); + + // now that we just found that we need a horizontal scrollbar, + // the need for a vertical one may have changed, since the horizontal + // SB might just occupy enough space so that not all rows do fit + // anymore + if ( !bFirstRoundVScrollNeed ) + { + bNeedVerticalScrollbar = lcl_determineScrollbarNeed( + m_nTopRow, eVertScrollbar, aDataCellPlayground.GetHeight(), m_nRowHeightPixel * m_nRowCount ); + if ( bNeedVerticalScrollbar ) + { + aDataCellPlayground.AdjustRight( -nScrollbarMetrics ); + } + } + } + + // the initial call to impl_ni_calculateColumnWidths assumed that we need a vertical scrollbar. If, by now, + // we know that this is not the case, re-calculate the column widths. + if ( !bNeedVerticalScrollbar ) + gridWidthPixel = impl_ni_calculateColumnWidths( i_assumeInflexibleColumnsUpToIncluding, false, newWidthsPixel ); + + // update the column objects with the new widths we finally calculated + TableSize const colCount = m_pModel->getColumnCount(); + m_aColumnWidths.reserve( colCount ); + tools::Long accumulatedWidthPixel = m_nRowHeaderWidthPixel; + bool anyColumnWidthChanged = false; + for ( ColPos col = 0; col < colCount; ++col ) + { + const tools::Long columnStart = accumulatedWidthPixel; + const tools::Long columnEnd = columnStart + newWidthsPixel[col]; + m_aColumnWidths.emplace_back( columnStart, columnEnd ); + accumulatedWidthPixel = columnEnd; + + // and don't forget to forward this to the column models + PColumnModel const pColumn = m_pModel->getColumnModel( col ); + ENSURE_OR_THROW( !!pColumn, "invalid column returned by the model!" ); + + tools::Long const oldColumnWidthAppFont = pColumn->getWidth(); + tools::Long const newColumnWidthAppFont = pixelWidthToAppFont( newWidthsPixel[col] ); + pColumn->setWidth( newColumnWidthAppFont ); + + anyColumnWidthChanged |= ( oldColumnWidthAppFont != newColumnWidthAppFont ); + } + + // if the column widths changed, ensure everything is repainted + if ( anyColumnWidthChanged ) + invalidate( TableArea::All ); + + // if the column resizing happened to leave some space at the right, but there are columns + // scrolled out to the left, scroll them in + while ( ( m_nLeftColumn > 0 ) + && ( accumulatedWidthPixel - m_aColumnWidths[ m_nLeftColumn - 1 ].getStart() <= gridWidthPixel ) + ) + { + --m_nLeftColumn; + } + + // now adjust the column metrics, since they currently ignore the horizontal scroll position + if ( m_nLeftColumn > 0 ) + { + const tools::Long offsetPixel = m_aColumnWidths[ 0 ].getStart() - m_aColumnWidths[ m_nLeftColumn ].getStart(); + for (auto & columnWidth : m_aColumnWidths) + { + columnWidth.move( offsetPixel ); + } + } + + // show or hide the scrollbars as needed, and position the data window + impl_ni_positionChildWindows( aDataCellPlayground, bNeedVerticalScrollbar, bNeedHorizontalScrollbar ); + } + + + void TableControl_Impl::impl_ni_positionChildWindows( tools::Rectangle const & i_dataCellPlayground, + bool const i_verticalScrollbar, bool const i_horizontalScrollbar ) + { + tools::Long const nScrollbarMetrics = m_rAntiImpl.GetSettings().GetStyleSettings().GetScrollBarSize(); + + // create or destroy the vertical scrollbar, as needed + lcl_updateScrollbar( + m_rAntiImpl, + m_pVScroll, + i_verticalScrollbar, + lcl_getRowsFittingInto( i_dataCellPlayground.GetHeight(), m_nRowHeightPixel, false ), + // visible units + m_nTopRow, // current position + m_nRowCount, // range + false, // vertical + LINK( this, TableControl_Impl, OnScroll ) // scroll handler + ); + + // position it + if ( m_pVScroll ) + { + tools::Rectangle aScrollbarArea( + Point( i_dataCellPlayground.Right() + 1, 0 ), + Size( nScrollbarMetrics, i_dataCellPlayground.Bottom() + 1 ) + ); + m_pVScroll->SetPosSizePixel( + aScrollbarArea.TopLeft(), aScrollbarArea.GetSize() ); + } + + // create or destroy the horizontal scrollbar, as needed + lcl_updateScrollbar( + m_rAntiImpl, + m_pHScroll, + i_horizontalScrollbar, + lcl_getColumnsVisibleWithin( i_dataCellPlayground, m_nLeftColumn, *this, false ), + // visible units + m_nLeftColumn, // current position + m_nColumnCount, // range + true, // horizontal + LINK( this, TableControl_Impl, OnScroll ) // scroll handler + ); + + // position it + if ( m_pHScroll ) + { + TableSize const nVisibleUnits = lcl_getColumnsVisibleWithin( i_dataCellPlayground, m_nLeftColumn, *this, false ); + TableMetrics const nRange = m_nColumnCount; + if( m_nLeftColumn + nVisibleUnits == nRange - 1 ) + { + if ( m_aColumnWidths[ nRange - 1 ].getStart() - m_aColumnWidths[ m_nLeftColumn ].getEnd() + m_aColumnWidths[ nRange-1 ].getWidth() > i_dataCellPlayground.GetWidth() ) + { + m_pHScroll->SetVisibleSize( nVisibleUnits -1 ); + m_pHScroll->SetPageSize( nVisibleUnits - 1 ); + } + } + tools::Rectangle aScrollbarArea( + Point( 0, i_dataCellPlayground.Bottom() + 1 ), + Size( i_dataCellPlayground.Right() + 1, nScrollbarMetrics ) + ); + m_pHScroll->SetPosSizePixel( + aScrollbarArea.TopLeft(), aScrollbarArea.GetSize() ); + } + + // the corner window connecting the two scrollbars in the lower right corner + bool bHaveScrollCorner = nullptr != m_pScrollCorner; + bool bNeedScrollCorner = ( nullptr != m_pHScroll ) && ( nullptr != m_pVScroll ); + if ( bHaveScrollCorner && !bNeedScrollCorner ) + { + m_pScrollCorner.disposeAndClear(); + } + else if ( !bHaveScrollCorner && bNeedScrollCorner ) + { + m_pScrollCorner = VclPtr<ScrollBarBox>::Create( &m_rAntiImpl ); + m_pScrollCorner->SetSizePixel( Size( nScrollbarMetrics, nScrollbarMetrics ) ); + m_pScrollCorner->SetPosPixel( Point( i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1 ) ); + m_pScrollCorner->Show(); + } + else if(bHaveScrollCorner && bNeedScrollCorner) + { + m_pScrollCorner->SetPosPixel( Point( i_dataCellPlayground.Right() + 1, i_dataCellPlayground.Bottom() + 1 ) ); + m_pScrollCorner->Show(); + } + + // resize the data window + m_pDataWindow->SetSizePixel( Size( + i_dataCellPlayground.GetWidth() + m_nRowHeaderWidthPixel, + i_dataCellPlayground.GetHeight() + m_nColHeaderHeightPixel + ) ); + } + + + void TableControl_Impl::onResize() + { + impl_ni_relayout(); + checkCursorPosition(); + } + + + void TableControl_Impl::doPaintContent(vcl::RenderContext& rRenderContext, const tools::Rectangle& _rUpdateRect) + { + if (!getModel()) + return; + PTableRenderer pRenderer = getModel()->getRenderer(); + DBG_ASSERT(!!pRenderer, "TableDataWindow::doPaintContent: invalid renderer!"); + if (!pRenderer) + return; + + // our current style settings, to be passed to the renderer + const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings(); + m_nRowCount = m_pModel->getRowCount(); + // the area occupied by all (at least partially) visible cells, including + // headers + tools::Rectangle const aAllCellsWithHeaders( impl_getAllVisibleCellsArea() ); + + // draw the header column area + if (m_pModel->hasColumnHeaders()) + { + TableRowGeometry const aHeaderRow(*this, tools::Rectangle(Point(0, 0), aAllCellsWithHeaders.BottomRight()), ROW_COL_HEADERS); + tools::Rectangle const aColRect(aHeaderRow.getRect()); + pRenderer->PaintHeaderArea(rRenderContext, aColRect, true, false, rStyle); + // Note that strictly, aHeaderRow.getRect() also contains the intersection between column + // and row header area. However, below we go to paint this intersection, again, + // so this hopefully doesn't hurt if we already paint it here. + + for (TableCellGeometry aCell(aHeaderRow, m_nLeftColumn); aCell.isValid(); aCell.moveRight()) + { + if (_rUpdateRect.GetIntersection(aCell.getRect()).IsEmpty()) + continue; + + pRenderer->PaintColumnHeader(aCell.getColumn(), rRenderContext, aCell.getRect(), rStyle); + } + } + // the area occupied by the row header, if any + tools::Rectangle aRowHeaderArea; + if (m_pModel->hasRowHeaders()) + { + aRowHeaderArea = aAllCellsWithHeaders; + aRowHeaderArea.SetRight( m_nRowHeaderWidthPixel - 1 ); + + TableSize const nVisibleRows = impl_getVisibleRows(true); + TableSize nActualRows = nVisibleRows; + if (m_nTopRow + nActualRows > m_nRowCount) + nActualRows = m_nRowCount - m_nTopRow; + aRowHeaderArea.SetBottom( m_nColHeaderHeightPixel + m_nRowHeightPixel * nActualRows - 1 ); + + pRenderer->PaintHeaderArea(rRenderContext, aRowHeaderArea, false, true, rStyle); + // Note that strictly, aRowHeaderArea also contains the intersection between column + // and row header area. However, below we go to paint this intersection, again, + // so this hopefully doesn't hurt if we already paint it here. + + if (m_pModel->hasColumnHeaders()) + { + TableCellGeometry const aIntersection(*this, tools::Rectangle(Point(0, 0), aAllCellsWithHeaders.BottomRight()), + COL_ROW_HEADERS, ROW_COL_HEADERS); + tools::Rectangle const aInters(aIntersection.getRect()); + pRenderer->PaintHeaderArea(rRenderContext, aInters, true, true, rStyle); + } + } + + // draw the table content row by row + TableSize colCount = getModel()->getColumnCount(); + + // paint all rows + tools::Rectangle const aAllDataCellsArea(impl_getAllVisibleDataCellArea()); + for (TableRowGeometry aRowIterator(*this, aAllCellsWithHeaders, getTopRow()); aRowIterator.isValid(); aRowIterator.moveDown()) + { + if (_rUpdateRect.GetIntersection(aRowIterator.getRect() ).IsEmpty()) + continue; + + bool const isControlFocused = m_rAntiImpl.HasControlFocus(); + bool const isSelectedRow = isRowSelected(aRowIterator.getRow()); + + tools::Rectangle const aRect = aRowIterator.getRect().GetIntersection(aAllDataCellsArea); + + // give the renderer a chance to prepare the row + pRenderer->PrepareRow(aRowIterator.getRow(), isControlFocused, isSelectedRow, rRenderContext, aRect, rStyle); + + // paint the row header + if (m_pModel->hasRowHeaders()) + { + const tools::Rectangle aCurrentRowHeader(aRowHeaderArea.GetIntersection(aRowIterator.getRect())); + pRenderer->PaintRowHeader(rRenderContext, aCurrentRowHeader, rStyle); + } + + if (!colCount) + continue; + + // paint all cells in this row + for (TableCellGeometry aCell(aRowIterator, m_nLeftColumn); aCell.isValid(); aCell.moveRight()) + { + pRenderer->PaintCell(aCell.getColumn(), isSelectedRow, isControlFocused, + rRenderContext, aCell.getRect(), rStyle); + } + } + } + + void TableControl_Impl::hideCursor() + { + if ( ++m_nCursorHidden == 1 ) + impl_ni_doSwitchCursor( false ); + } + + + void TableControl_Impl::showCursor() + { + DBG_ASSERT( m_nCursorHidden > 0, "TableControl_Impl::showCursor: cursor not hidden!" ); + if ( --m_nCursorHidden == 0 ) + impl_ni_doSwitchCursor( true ); + } + + + bool TableControl_Impl::dispatchAction( TableControlAction _eAction ) + { + bool bSuccess = false; + bool selectionChanged = false; + + switch ( _eAction ) + { + case cursorDown: + if ( m_pSelEngine->GetSelectionMode() == SelectionMode::Single ) + { + //if other rows already selected, deselect them + if(!m_aSelectedRows.empty()) + { + invalidateSelectedRows(); + m_aSelectedRows.clear(); + } + if ( m_nCurRow < m_nRowCount-1 ) + { + ++m_nCurRow; + m_aSelectedRows.push_back(m_nCurRow); + } + else + m_aSelectedRows.push_back(m_nCurRow); + invalidateRow( m_nCurRow ); + ensureVisible(m_nCurColumn,m_nCurRow); + selectionChanged = true; + bSuccess = true; + } + else + { + if ( m_nCurRow < m_nRowCount - 1 ) + bSuccess = goTo( m_nCurColumn, m_nCurRow + 1 ); + } + break; + + case cursorUp: + if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single) + { + if(!m_aSelectedRows.empty()) + { + invalidateSelectedRows(); + m_aSelectedRows.clear(); + } + if(m_nCurRow>0) + { + --m_nCurRow; + m_aSelectedRows.push_back(m_nCurRow); + invalidateRow( m_nCurRow ); + } + else + { + m_aSelectedRows.push_back(m_nCurRow); + invalidateRow( m_nCurRow ); + } + ensureVisible(m_nCurColumn,m_nCurRow); + selectionChanged = true; + bSuccess = true; + } + else + { + if ( m_nCurRow > 0 ) + bSuccess = goTo( m_nCurColumn, m_nCurRow - 1 ); + } + break; + case cursorLeft: + if ( m_nCurColumn > 0 ) + bSuccess = goTo( m_nCurColumn - 1, m_nCurRow ); + else + if ( ( m_nCurColumn == 0) && ( m_nCurRow > 0 ) ) + bSuccess = goTo( m_nColumnCount - 1, m_nCurRow - 1 ); + break; + + case cursorRight: + if ( m_nCurColumn < m_nColumnCount - 1 ) + bSuccess = goTo( m_nCurColumn + 1, m_nCurRow ); + else + if ( ( m_nCurColumn == m_nColumnCount - 1 ) && ( m_nCurRow < m_nRowCount - 1 ) ) + bSuccess = goTo( 0, m_nCurRow + 1 ); + break; + + case cursorToLineStart: + bSuccess = goTo( 0, m_nCurRow ); + break; + + case cursorToLineEnd: + bSuccess = goTo( m_nColumnCount - 1, m_nCurRow ); + break; + + case cursorToFirstLine: + bSuccess = goTo( m_nCurColumn, 0 ); + break; + + case cursorToLastLine: + bSuccess = goTo( m_nCurColumn, m_nRowCount - 1 ); + break; + + case cursorPageUp: + { + RowPos nNewRow = ::std::max( RowPos(0), m_nCurRow - impl_getVisibleRows( false ) ); + bSuccess = goTo( m_nCurColumn, nNewRow ); + } + break; + + case cursorPageDown: + { + RowPos nNewRow = ::std::min( m_nRowCount - 1, m_nCurRow + impl_getVisibleRows( false ) ); + bSuccess = goTo( m_nCurColumn, nNewRow ); + } + break; + + case cursorTopLeft: + bSuccess = goTo( 0, 0 ); + break; + + case cursorBottomRight: + bSuccess = goTo( m_nColumnCount - 1, m_nRowCount - 1 ); + break; + + case cursorSelectRow: + { + if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE) + return false; + //pos is the position of the current row in the vector of selected rows, if current row is selected + int pos = getRowSelectedNumber(m_aSelectedRows, m_nCurRow); + //if current row is selected, it should be deselected, when ALT+SPACE are pressed + if(pos>-1) + { + m_aSelectedRows.erase(m_aSelectedRows.begin()+pos); + if(m_aSelectedRows.empty() && m_nAnchor != -1) + m_nAnchor = -1; + } + //else select the row->put it in the vector + else + m_aSelectedRows.push_back(m_nCurRow); + invalidateRow( m_nCurRow ); + selectionChanged = true; + bSuccess = true; + } + break; + case cursorSelectRowUp: + { + if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE) + return false; + else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single) + { + //if there are other selected rows, deselect them + return false; + } + else + { + //there are other selected rows + if(!m_aSelectedRows.empty()) + { + //the anchor wasn't set -> a region is not selected, that's why clear all selection + //and select the current row + if(m_nAnchor==-1) + { + invalidateSelectedRows(); + m_aSelectedRows.clear(); + m_aSelectedRows.push_back(m_nCurRow); + invalidateRow( m_nCurRow ); + } + else + { + //a region is already selected, prevRow is last selected row and the row above - nextRow - should be selected + int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow); + int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow-1); + if(prevRow>-1) + { + //if m_nCurRow isn't the upper one, can move up, otherwise not + if(m_nCurRow>0) + m_nCurRow--; + else + return true; + //if nextRow already selected, deselect it, otherwise select it + if(nextRow>-1 && m_aSelectedRows[nextRow] == m_nCurRow) + { + m_aSelectedRows.erase(m_aSelectedRows.begin()+prevRow); + invalidateRow( m_nCurRow + 1 ); + } + else + { + m_aSelectedRows.push_back(m_nCurRow); + invalidateRow( m_nCurRow ); + } + } + else + { + if(m_nCurRow>0) + { + m_aSelectedRows.push_back(m_nCurRow); + m_nCurRow--; + m_aSelectedRows.push_back(m_nCurRow); + invalidateSelectedRegion( m_nCurRow+1, m_nCurRow ); + } + } + } + } + else + { + //if nothing is selected and the current row isn't the upper one + //select the current and one row above + //otherwise select only the upper row + if(m_nCurRow>0) + { + m_aSelectedRows.push_back(m_nCurRow); + m_nCurRow--; + m_aSelectedRows.push_back(m_nCurRow); + invalidateSelectedRegion( m_nCurRow+1, m_nCurRow ); + } + else + { + m_aSelectedRows.push_back(m_nCurRow); + invalidateRow( m_nCurRow ); + } + } + m_pSelEngine->SetAnchor(true); + m_nAnchor = m_nCurRow; + ensureVisible(m_nCurColumn, m_nCurRow); + selectionChanged = true; + bSuccess = true; + } + } + break; + case cursorSelectRowDown: + { + if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE) + bSuccess = false; + else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single) + { + bSuccess = false; + } + else + { + if(!m_aSelectedRows.empty()) + { + //the anchor wasn't set -> a region is not selected, that's why clear all selection + //and select the current row + if(m_nAnchor==-1) + { + invalidateSelectedRows(); + m_aSelectedRows.clear(); + m_aSelectedRows.push_back(m_nCurRow); + invalidateRow( m_nCurRow ); + } + else + { + //a region is already selected, prevRow is last selected row and the row beneath - nextRow - should be selected + int prevRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow); + int nextRow = getRowSelectedNumber(m_aSelectedRows, m_nCurRow+1); + if(prevRow>-1) + { + //if m_nCurRow isn't the last one, can move down, otherwise not + if(m_nCurRow<m_nRowCount-1) + m_nCurRow++; + else + return true; + //if next row already selected, deselect it, otherwise select it + if(nextRow>-1 && m_aSelectedRows[nextRow] == m_nCurRow) + { + m_aSelectedRows.erase(m_aSelectedRows.begin()+prevRow); + invalidateRow( m_nCurRow - 1 ); + } + else + { + m_aSelectedRows.push_back(m_nCurRow); + invalidateRow( m_nCurRow ); + } + } + else + { + if(m_nCurRow<m_nRowCount-1) + { + m_aSelectedRows.push_back(m_nCurRow); + m_nCurRow++; + m_aSelectedRows.push_back(m_nCurRow); + invalidateSelectedRegion( m_nCurRow-1, m_nCurRow ); + } + } + } + } + else + { + //there wasn't any selection, select current and row beneath, otherwise only row beneath + if(m_nCurRow<m_nRowCount-1) + { + m_aSelectedRows.push_back(m_nCurRow); + m_nCurRow++; + m_aSelectedRows.push_back(m_nCurRow); + invalidateSelectedRegion( m_nCurRow-1, m_nCurRow ); + } + else + { + m_aSelectedRows.push_back(m_nCurRow); + invalidateRow( m_nCurRow ); + } + } + m_pSelEngine->SetAnchor(true); + m_nAnchor = m_nCurRow; + ensureVisible(m_nCurColumn, m_nCurRow); + selectionChanged = true; + bSuccess = true; + } + } + break; + + case cursorSelectRowAreaTop: + { + if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE) + bSuccess = false; + else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single) + bSuccess = false; + else + { + //select the region between the current and the upper row + RowPos iter = m_nCurRow; + invalidateSelectedRegion( m_nCurRow, 0 ); + //put the rows in vector + while(iter>=0) + { + if ( !isRowSelected( iter ) ) + m_aSelectedRows.push_back(iter); + --iter; + } + m_nCurRow = 0; + m_nAnchor = m_nCurRow; + m_pSelEngine->SetAnchor(true); + ensureVisible(m_nCurColumn, 0); + selectionChanged = true; + bSuccess = true; + } + } + break; + + case cursorSelectRowAreaBottom: + { + if(m_pSelEngine->GetSelectionMode() == SelectionMode::NONE) + return false; + else if(m_pSelEngine->GetSelectionMode() == SelectionMode::Single) + return false; + //select the region between the current and the last row + RowPos iter = m_nCurRow; + invalidateSelectedRegion( m_nCurRow, m_nRowCount-1 ); + //put the rows in the vector + while(iter<=m_nRowCount) + { + if ( !isRowSelected( iter ) ) + m_aSelectedRows.push_back(iter); + ++iter; + } + m_nCurRow = m_nRowCount-1; + m_nAnchor = m_nCurRow; + m_pSelEngine->SetAnchor(true); + ensureVisible(m_nCurColumn, m_nRowCount-1); + selectionChanged = true; + bSuccess = true; + } + break; + default: + OSL_FAIL( "TableControl_Impl::dispatchAction: unsupported action!" ); + break; + } + + if ( bSuccess && selectionChanged ) + { + m_rAntiImpl.Select(); + } + + return bSuccess; + } + + + void TableControl_Impl::impl_ni_doSwitchCursor( bool _bShow ) + { + PTableRenderer pRenderer = m_pModel ? m_pModel->getRenderer() : PTableRenderer(); + if ( pRenderer ) + { + tools::Rectangle aCellRect; + impl_getCellRect( m_nCurColumn, m_nCurRow, aCellRect ); + if ( _bShow ) + pRenderer->ShowCellCursor( *m_pDataWindow, aCellRect ); + else + pRenderer->HideCellCursor( *m_pDataWindow ); + } + } + + + void TableControl_Impl::impl_getCellRect( ColPos _nColumn, RowPos _nRow, tools::Rectangle& _rCellRect ) const + { + if ( !m_pModel + || ( COL_INVALID == _nColumn ) + || ( ROW_INVALID == _nRow ) + ) + { + _rCellRect.SetEmpty(); + return; + } + + TableCellGeometry aCell( *this, impl_getAllVisibleCellsArea(), _nColumn, _nRow ); + _rCellRect = aCell.getRect(); + } + + + RowPos TableControl_Impl::getRowAtPoint( const Point& rPoint ) const + { + return impl_getRowForAbscissa( rPoint.Y() ); + } + + + ColPos TableControl_Impl::getColAtPoint( const Point& rPoint ) const + { + return impl_getColumnForOrdinate( rPoint.X() ); + } + + + TableCell TableControl_Impl::hitTest( Point const & i_point ) const + { + TableCell aCell( getColAtPoint( i_point ), getRowAtPoint( i_point ) ); + if ( aCell.nColumn > COL_ROW_HEADERS ) + { + PColumnModel const pColumn = m_pModel->getColumnModel( aCell.nColumn ); + MutableColumnMetrics const & rColInfo( m_aColumnWidths[ aCell.nColumn ] ); + if ( ( rColInfo.getEnd() - 3 <= i_point.X() ) + && ( rColInfo.getEnd() >= i_point.X() ) + && pColumn->isResizable() + ) + { + aCell.eArea = ColumnDivider; + } + } + return aCell; + } + + + ColumnMetrics TableControl_Impl::getColumnMetrics( ColPos const i_column ) const + { + ENSURE_OR_RETURN( ( i_column >= 0 ) && ( i_column < m_pModel->getColumnCount() ), + "TableControl_Impl::getColumnMetrics: illegal column index!", ColumnMetrics() ); + return m_aColumnWidths[ i_column ]; + } + + + PTableModel TableControl_Impl::getModel() const + { + return m_pModel; + } + + + ColPos TableControl_Impl::getCurrentColumn() const + { + return m_nCurColumn; + } + + + RowPos TableControl_Impl::getCurrentRow() const + { + return m_nCurRow; + } + + + ::Size TableControl_Impl::getTableSizePixel() const + { + return m_pDataWindow->GetOutputSizePixel(); + } + + + void TableControl_Impl::setPointer( PointerStyle i_pointer ) + { + m_pDataWindow->SetPointer( i_pointer ); + } + + + void TableControl_Impl::captureMouse() + { + m_pDataWindow->CaptureMouse(); + } + + + void TableControl_Impl::releaseMouse() + { + m_pDataWindow->ReleaseMouse(); + } + + + void TableControl_Impl::invalidate( TableArea const i_what ) + { + switch ( i_what ) + { + case TableArea::ColumnHeaders: + m_pDataWindow->Invalidate( calcHeaderRect( true ) ); + break; + + case TableArea::RowHeaders: + m_pDataWindow->Invalidate( calcHeaderRect( false ) ); + break; + + case TableArea::All: + m_pDataWindow->Invalidate(); + m_pDataWindow->GetParent()->Invalidate( InvalidateFlags::Transparent ); + break; + } + } + + + tools::Long TableControl_Impl::pixelWidthToAppFont( tools::Long const i_pixels ) const + { + return m_pDataWindow->PixelToLogic(Size(i_pixels, 0), MapMode(MapUnit::MapAppFont)).Width(); + } + + + tools::Long TableControl_Impl::appFontWidthToPixel( tools::Long const i_appFontUnits ) const + { + return m_pDataWindow->LogicToPixel(Size(i_appFontUnits, 0), MapMode(MapUnit::MapAppFont)).Width(); + } + + + void TableControl_Impl::hideTracking() + { + m_pDataWindow->HideTracking(); + } + + + void TableControl_Impl::showTracking( tools::Rectangle const & i_location, ShowTrackFlags const i_flags ) + { + m_pDataWindow->ShowTracking( i_location, i_flags ); + } + + + void TableControl_Impl::activateCell( ColPos const i_col, RowPos const i_row ) + { + goTo( i_col, i_row ); + } + + + void TableControl_Impl::invalidateSelectedRegion( RowPos _nPrevRow, RowPos _nCurRow ) + { + // get the visible area of the table control and set the Left and right border of the region to be repainted + tools::Rectangle const aAllCells( impl_getAllVisibleCellsArea() ); + + tools::Rectangle aInvalidateRect; + aInvalidateRect.SetLeft( aAllCells.Left() ); + aInvalidateRect.SetRight( aAllCells.Right() ); + // if only one row is selected + if ( _nPrevRow == _nCurRow ) + { + tools::Rectangle aCellRect; + impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect ); + aInvalidateRect.SetTop( aCellRect.Top() ); + aInvalidateRect.SetBottom( aCellRect.Bottom() ); + } + //if the region is above the current row + else if(_nPrevRow < _nCurRow ) + { + tools::Rectangle aCellRect; + impl_getCellRect( m_nCurColumn, _nPrevRow, aCellRect ); + aInvalidateRect.SetTop( aCellRect.Top() ); + impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect ); + aInvalidateRect.SetBottom( aCellRect.Bottom() ); + } + //if the region is beneath the current row + else + { + tools::Rectangle aCellRect; + impl_getCellRect( m_nCurColumn, _nCurRow, aCellRect ); + aInvalidateRect.SetTop( aCellRect.Top() ); + impl_getCellRect( m_nCurColumn, _nPrevRow, aCellRect ); + aInvalidateRect.SetBottom( aCellRect.Bottom() ); + } + + invalidateRect(aInvalidateRect); + } + + void TableControl_Impl::invalidateRect(const tools::Rectangle &rInvalidateRect) + { + m_pDataWindow->Invalidate( rInvalidateRect, + m_pDataWindow->GetControlBackground().IsTransparent() ? InvalidateFlags::Transparent : InvalidateFlags::NONE ); + } + + + void TableControl_Impl::invalidateSelectedRows() + { + for (auto const& selectedRow : m_aSelectedRows) + { + invalidateRow(selectedRow); + } + } + + + void TableControl_Impl::invalidateRowRange( RowPos const i_firstRow, RowPos const i_lastRow ) + { + RowPos const firstRow = i_firstRow < m_nTopRow ? m_nTopRow : i_firstRow; + RowPos const lastVisibleRow = m_nTopRow + impl_getVisibleRows( true ) - 1; + RowPos const lastRow = ( ( i_lastRow == ROW_INVALID ) || ( i_lastRow > lastVisibleRow ) ) ? lastVisibleRow : i_lastRow; + + tools::Rectangle aInvalidateRect; + + tools::Rectangle const aVisibleCellsArea( impl_getAllVisibleCellsArea() ); + TableRowGeometry aRow( *this, aVisibleCellsArea, firstRow, true ); + while ( aRow.isValid() && ( aRow.getRow() <= lastRow ) ) + { + aInvalidateRect.Union( aRow.getRect() ); + aRow.moveDown(); + } + + if ( i_lastRow == ROW_INVALID ) + aInvalidateRect.SetBottom( m_pDataWindow->GetOutputSizePixel().Height() ); + + invalidateRect(aInvalidateRect); + } + + + void TableControl_Impl::checkCursorPosition() + { + + TableSize nVisibleRows = impl_getVisibleRows(true); + TableSize nVisibleCols = impl_getVisibleColumns(true); + if ( ( m_nTopRow + nVisibleRows > m_nRowCount ) + && ( m_nRowCount >= nVisibleRows ) + ) + { + --m_nTopRow; + } + else + { + m_nTopRow = 0; + } + + if ( ( m_nLeftColumn + nVisibleCols > m_nColumnCount ) + && ( m_nColumnCount >= nVisibleCols ) + ) + { + --m_nLeftColumn; + } + else + { + m_nLeftColumn = 0; + } + + m_pDataWindow->Invalidate(); + } + + + TableSize TableControl_Impl::impl_getVisibleRows( bool _bAcceptPartialRow ) const + { + DBG_ASSERT( m_pDataWindow, "TableControl_Impl::impl_getVisibleRows: no data window!" ); + + return lcl_getRowsFittingInto( + m_pDataWindow->GetOutputSizePixel().Height() - m_nColHeaderHeightPixel, + m_nRowHeightPixel, + _bAcceptPartialRow + ); + } + + + TableSize TableControl_Impl::impl_getVisibleColumns( bool _bAcceptPartialCol ) const + { + DBG_ASSERT( m_pDataWindow, "TableControl_Impl::impl_getVisibleColumns: no data window!" ); + + return lcl_getColumnsVisibleWithin( + tools::Rectangle( Point( 0, 0 ), m_pDataWindow->GetOutputSizePixel() ), + m_nLeftColumn, + *this, + _bAcceptPartialCol + ); + } + + + bool TableControl_Impl::goTo( ColPos _nColumn, RowPos _nRow ) + { + // TODO: give veto listeners a chance + + if ( ( _nColumn < 0 ) || ( _nColumn >= m_nColumnCount ) + || ( _nRow < 0 ) || ( _nRow >= m_nRowCount ) + ) + { + OSL_ENSURE( false, "TableControl_Impl::goTo: invalid row or column index!" ); + return false; + } + + SuppressCursor aHideCursor( *this ); + m_nCurColumn = _nColumn; + m_nCurRow = _nRow; + + // ensure that the new cell is visible + ensureVisible( m_nCurColumn, m_nCurRow ); + return true; + } + + + void TableControl_Impl::ensureVisible( ColPos _nColumn, RowPos _nRow ) + { + DBG_ASSERT( ( _nColumn >= 0 ) && ( _nColumn < m_nColumnCount ) + && ( _nRow >= 0 ) && ( _nRow < m_nRowCount ), + "TableControl_Impl::ensureVisible: invalid coordinates!" ); + + SuppressCursor aHideCursor( *this ); + + if ( _nColumn < m_nLeftColumn ) + impl_scrollColumns( _nColumn - m_nLeftColumn ); + else + { + TableSize nVisibleColumns = impl_getVisibleColumns( false/*bAcceptPartialVisibility*/ ); + if ( _nColumn > m_nLeftColumn + nVisibleColumns - 1 ) + { + impl_scrollColumns( _nColumn - ( m_nLeftColumn + nVisibleColumns - 1 ) ); + // TODO: since not all columns have the same width, this might in theory result + // in the column still not being visible. + } + } + + if ( _nRow < m_nTopRow ) + impl_scrollRows( _nRow - m_nTopRow ); + else + { + TableSize nVisibleRows = impl_getVisibleRows( false/*_bAcceptPartialVisibility*/ ); + if ( _nRow > m_nTopRow + nVisibleRows - 1 ) + impl_scrollRows( _nRow - ( m_nTopRow + nVisibleRows - 1 ) ); + } + } + + + OUString TableControl_Impl::getCellContentAsString( RowPos const i_row, ColPos const i_col ) + { + Any aCellValue; + m_pModel->getCellContent( i_col, i_row, aCellValue ); + + OUString sCellStringContent; + m_pModel->getRenderer()->GetFormattedCellString( aCellValue, sCellStringContent ); + + return sCellStringContent; + } + + + TableSize TableControl_Impl::impl_ni_ScrollRows( TableSize _nRowDelta ) + { + // compute new top row + RowPos nNewTopRow = + ::std::max( + ::std::min( static_cast<RowPos>( m_nTopRow + _nRowDelta ), static_cast<RowPos>( m_nRowCount - 1 ) ), + RowPos(0) + ); + + RowPos nOldTopRow = m_nTopRow; + m_nTopRow = nNewTopRow; + + // if updates are enabled currently, scroll the viewport + if ( m_nTopRow != nOldTopRow ) + { + SuppressCursor aHideCursor( *this ); + // TODO: call an onStartScroll at our listener (or better an own onStartScroll, + // which hides the cursor and then calls the listener) + // Same for onEndScroll + + // scroll the view port, if possible + tools::Long nPixelDelta = m_nRowHeightPixel * ( m_nTopRow - nOldTopRow ); + + tools::Rectangle aDataArea( Point( 0, m_nColHeaderHeightPixel ), m_pDataWindow->GetOutputSizePixel() ); + + if ( m_pDataWindow->GetBackground().IsScrollable() + && std::abs( nPixelDelta ) < aDataArea.GetHeight() + ) + { + m_pDataWindow->Scroll( 0, static_cast<tools::Long>(-nPixelDelta), aDataArea, ScrollFlags::Clip | ScrollFlags::Update | ScrollFlags::Children); + } + else + { + m_pDataWindow->Invalidate( InvalidateFlags::Update ); + m_pDataWindow->GetParent()->Invalidate( InvalidateFlags::Transparent ); + } + + // update the position at the vertical scrollbar + if ( m_pVScroll != nullptr ) + m_pVScroll->SetThumbPos( m_nTopRow ); + } + + // The scroll bar availability might change when we scrolled. + // For instance, imagine a view with 10 rows, if which 5 fit into the window, numbered 1 to 10. + // Now let + // - the user scroll to row number 6, so the last 5 rows are visible + // - somebody remove the last 4 rows + // - the user scroll to row number 5 being the top row, so the last two rows are visible + // - somebody remove row number 6 + // - the user scroll to row number 1 + // => in this case, the need for the scrollbar vanishes immediately. + if ( m_nTopRow == 0 ) + m_rAntiImpl.PostUserEvent( LINK( this, TableControl_Impl, OnUpdateScrollbars ) ); + + return static_cast<TableSize>( m_nTopRow - nOldTopRow ); + } + + + TableSize TableControl_Impl::impl_scrollRows( TableSize const i_rowDelta ) + { + return impl_ni_ScrollRows( i_rowDelta ); + } + + + TableSize TableControl_Impl::impl_ni_ScrollColumns( TableSize _nColumnDelta ) + { + // compute new left column + const ColPos nNewLeftColumn = + ::std::max( + ::std::min( static_cast<ColPos>( m_nLeftColumn + _nColumnDelta ), static_cast<ColPos>( m_nColumnCount - 1 ) ), + ColPos(0) + ); + + const ColPos nOldLeftColumn = m_nLeftColumn; + m_nLeftColumn = nNewLeftColumn; + + // if updates are enabled currently, scroll the viewport + if ( m_nLeftColumn != nOldLeftColumn ) + { + SuppressCursor aHideCursor( *this ); + // TODO: call an onStartScroll at our listener (or better an own onStartScroll, + // which hides the cursor and then calls the listener) + // Same for onEndScroll + + // scroll the view port, if possible + const tools::Rectangle aDataArea( Point( m_nRowHeaderWidthPixel, 0 ), m_pDataWindow->GetOutputSizePixel() ); + + tools::Long nPixelDelta = + m_aColumnWidths[ nOldLeftColumn ].getStart() + - m_aColumnWidths[ m_nLeftColumn ].getStart(); + + // update our column positions + // Do this *before* scrolling, as ScrollFlags::Update will trigger a paint, which already needs the correct + // information in m_aColumnWidths + for (auto & columnWidth : m_aColumnWidths) + { + columnWidth.move(nPixelDelta); + } + + // scroll the window content (if supported and possible), or invalidate the complete window + if ( m_pDataWindow->GetBackground().IsScrollable() + && std::abs( nPixelDelta ) < aDataArea.GetWidth() + ) + { + m_pDataWindow->Scroll( nPixelDelta, 0, aDataArea, ScrollFlags::Clip | ScrollFlags::Update ); + } + else + { + m_pDataWindow->Invalidate( InvalidateFlags::Update ); + m_pDataWindow->GetParent()->Invalidate( InvalidateFlags::Transparent ); + } + + // update the position at the horizontal scrollbar + if ( m_pHScroll != nullptr ) + m_pHScroll->SetThumbPos( m_nLeftColumn ); + } + + // The scroll bar availability might change when we scrolled. This is because we do not hide + // the scrollbar when it is, in theory, unnecessary, but currently at a position > 0. In this case, it will + // be auto-hidden when it's scrolled back to pos 0. + if ( m_nLeftColumn == 0 ) + m_rAntiImpl.PostUserEvent( LINK( this, TableControl_Impl, OnUpdateScrollbars ) ); + + return static_cast<TableSize>( m_nLeftColumn - nOldLeftColumn ); + } + + + TableSize TableControl_Impl::impl_scrollColumns( TableSize const i_columnDelta ) + { + return impl_ni_ScrollColumns( i_columnDelta ); + } + + + SelectionEngine* TableControl_Impl::getSelEngine() + { + return m_pSelEngine.get(); + } + + bool TableControl_Impl::isRowSelected( RowPos i_row ) const + { + return ::std::find( m_aSelectedRows.begin(), m_aSelectedRows.end(), i_row ) != m_aSelectedRows.end(); + } + + + RowPos TableControl_Impl::getSelectedRowIndex( size_t const i_selectionIndex ) const + { + if ( i_selectionIndex < m_aSelectedRows.size() ) + return m_aSelectedRows[ i_selectionIndex ]; + return ROW_INVALID; + } + + + int TableControl_Impl::getRowSelectedNumber(const ::std::vector<RowPos>& selectedRows, RowPos current) + { + std::vector<RowPos>::const_iterator it = ::std::find(selectedRows.begin(),selectedRows.end(),current); + if ( it != selectedRows.end() ) + { + return it - selectedRows.begin(); + } + return -1; + } + + + ColPos TableControl_Impl::impl_getColumnForOrdinate( tools::Long const i_ordinate ) const + { + if ( ( m_aColumnWidths.empty() ) || ( i_ordinate < 0 ) ) + return COL_INVALID; + + if ( i_ordinate < m_nRowHeaderWidthPixel ) + return COL_ROW_HEADERS; + + ColumnPositions::const_iterator lowerBound = ::std::lower_bound( + m_aColumnWidths.begin(), + m_aColumnWidths.end(), + MutableColumnMetrics(i_ordinate+1, i_ordinate+1), + ColumnInfoPositionLess() + ); + if ( lowerBound == m_aColumnWidths.end() ) + { + // point is *behind* the start of the last column ... + if ( i_ordinate < m_aColumnWidths.rbegin()->getEnd() ) + // ... but still before its end + return m_nColumnCount - 1; + return COL_INVALID; + } + return lowerBound - m_aColumnWidths.begin(); + } + + + RowPos TableControl_Impl::impl_getRowForAbscissa( tools::Long const i_abscissa ) const + { + if ( i_abscissa < 0 ) + return ROW_INVALID; + + if ( i_abscissa < m_nColHeaderHeightPixel ) + return ROW_COL_HEADERS; + + tools::Long const abscissa = i_abscissa - m_nColHeaderHeightPixel; + tools::Long const row = m_nTopRow + abscissa / m_nRowHeightPixel; + return row < m_pModel->getRowCount() ? row : ROW_INVALID; + } + + + bool TableControl_Impl::markRowAsDeselected( RowPos const i_rowIndex ) + { + ::std::vector< RowPos >::iterator selPos = ::std::find( m_aSelectedRows.begin(), m_aSelectedRows.end(), i_rowIndex ); + if ( selPos == m_aSelectedRows.end() ) + return false; + + m_aSelectedRows.erase( selPos ); + return true; + } + + + bool TableControl_Impl::markRowAsSelected( RowPos const i_rowIndex ) + { + if ( isRowSelected( i_rowIndex ) ) + return false; + + SelectionMode const eSelMode = getSelEngine()->GetSelectionMode(); + switch ( eSelMode ) + { + case SelectionMode::Single: + if ( !m_aSelectedRows.empty() ) + { + OSL_ENSURE( m_aSelectedRows.size() == 1, "TableControl::markRowAsSelected: SingleSelection with more than one selected element?" ); + m_aSelectedRows[0] = i_rowIndex; + break; + } + [[fallthrough]]; + + case SelectionMode::Multiple: + m_aSelectedRows.push_back( i_rowIndex ); + break; + + default: + OSL_ENSURE( false, "TableControl_Impl::markRowAsSelected: unsupported selection mode!" ); + return false; + } + + return true; + } + + + bool TableControl_Impl::markAllRowsAsDeselected() + { + if ( m_aSelectedRows.empty() ) + return false; + + m_aSelectedRows.clear(); + return true; + } + + + bool TableControl_Impl::markAllRowsAsSelected() + { + SelectionMode const eSelMode = getSelEngine()->GetSelectionMode(); + ENSURE_OR_RETURN_FALSE( eSelMode == SelectionMode::Multiple, "TableControl_Impl::markAllRowsAsSelected: unsupported selection mode!" ); + + if ( m_aSelectedRows.size() == size_t( m_pModel->getRowCount() ) ) + { + #if OSL_DEBUG_LEVEL > 0 + for ( TableSize row = 0; row < m_pModel->getRowCount(); ++row ) + { + OSL_ENSURE( isRowSelected( row ), "TableControl_Impl::markAllRowsAsSelected: inconsistency in the selected rows!" ); + } + #endif + // already all rows marked as selected + return false; + } + + m_aSelectedRows.clear(); + for ( RowPos i=0; i < m_pModel->getRowCount(); ++i ) + m_aSelectedRows.push_back(i); + + return true; + } + + + void TableControl_Impl::commitAccessibleEvent( sal_Int16 const i_eventID ) + { + impl_commitAccessibleEvent( i_eventID, Any() ); + } + + + void TableControl_Impl::commitCellEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue ) + { + if ( impl_isAccessibleAlive() ) + m_pAccessibleTable->commitCellEvent( i_eventID, i_newValue, i_oldValue ); + } + + + void TableControl_Impl::commitTableEvent( sal_Int16 const i_eventID, const Any& i_newValue, const Any& i_oldValue ) + { + if ( impl_isAccessibleAlive() ) + m_pAccessibleTable->commitTableEvent( i_eventID, i_newValue, i_oldValue ); + } + + + tools::Rectangle TableControl_Impl::calcHeaderRect(bool bColHeader) + { + tools::Rectangle const aRectTableWithHeaders( impl_getAllVisibleCellsArea() ); + Size const aSizeTableWithHeaders( aRectTableWithHeaders.GetSize() ); + if ( bColHeader ) + return tools::Rectangle( aRectTableWithHeaders.TopLeft(), Size( aSizeTableWithHeaders.Width(), m_nColHeaderHeightPixel ) ); + else + return tools::Rectangle( aRectTableWithHeaders.TopLeft(), Size( m_nRowHeaderWidthPixel, aSizeTableWithHeaders.Height() ) ); + } + + + tools::Rectangle TableControl_Impl::calcHeaderCellRect( bool bColHeader, sal_Int32 nPos ) + { + tools::Rectangle const aHeaderRect = calcHeaderRect( bColHeader ); + TableCellGeometry const aGeometry( + *this, aHeaderRect, + bColHeader ? nPos : COL_ROW_HEADERS, + bColHeader ? ROW_COL_HEADERS : nPos + ); + return aGeometry.getRect(); + } + + + tools::Rectangle TableControl_Impl::calcTableRect() const + { + return impl_getAllVisibleDataCellArea(); + } + + + tools::Rectangle TableControl_Impl::calcCellRect( sal_Int32 nRow, sal_Int32 nCol ) const + { + tools::Rectangle aCellRect; + impl_getCellRect( nRow, nCol, aCellRect ); + return aCellRect; + } + + + IMPL_LINK_NOARG( TableControl_Impl, OnUpdateScrollbars, void*, void ) + { + // TODO: can't we simply use lcl_updateScrollbar here, so the scrollbars ranges are updated, instead of + // doing a complete re-layout? + impl_ni_relayout(); + } + + + IMPL_LINK( TableControl_Impl, OnScroll, ScrollBar*, _pScrollbar, void ) + { + DBG_ASSERT( ( _pScrollbar == m_pVScroll ) || ( _pScrollbar == m_pHScroll ), + "TableControl_Impl::OnScroll: where did this come from?" ); + + if ( _pScrollbar == m_pVScroll ) + impl_ni_ScrollRows( _pScrollbar->GetDelta() ); + else + impl_ni_ScrollColumns( _pScrollbar->GetDelta() ); + } + + + Reference< XAccessible > TableControl_Impl::getAccessible( vcl::Window& i_parentWindow ) + { + DBG_TESTSOLARMUTEX(); + if ( m_pAccessibleTable == nullptr ) + { + Reference< XAccessible > const xAccParent = i_parentWindow.GetAccessible(); + if ( xAccParent.is() ) + { + m_pAccessibleTable = m_aFactoryAccess.getFactory().createAccessibleTableControl( + xAccParent, m_rAntiImpl + ); + } + } + + Reference< XAccessible > xAccessible; + if ( m_pAccessibleTable ) + xAccessible = m_pAccessibleTable->getMyself(); + return xAccessible; + } + + + void TableControl_Impl::disposeAccessible() + { + if ( m_pAccessibleTable ) + m_pAccessibleTable->DisposeAccessImpl(); + m_pAccessibleTable = nullptr; + } + + + bool TableControl_Impl::impl_isAccessibleAlive() const + { + return ( nullptr != m_pAccessibleTable ) && m_pAccessibleTable->isAlive(); + } + + + void TableControl_Impl::impl_commitAccessibleEvent( sal_Int16 const i_eventID, Any const & i_newValue ) + { + if ( impl_isAccessibleAlive() ) + m_pAccessibleTable->commitEvent( i_eventID, i_newValue ); + } + + + //= TableFunctionSet + + + TableFunctionSet::TableFunctionSet(TableControl_Impl* _pTableControl) + :m_pTableControl( _pTableControl) + ,m_nCurrentRow( ROW_INVALID ) + { + } + + TableFunctionSet::~TableFunctionSet() + { + } + + void TableFunctionSet::BeginDrag() + { + } + + void TableFunctionSet::CreateAnchor() + { + m_pTableControl->setAnchor( m_pTableControl->getCurRow() ); + } + + + void TableFunctionSet::DestroyAnchor() + { + m_pTableControl->setAnchor( ROW_INVALID ); + } + + + void TableFunctionSet::SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor) + { + // newRow is the row which includes the point, getCurRow() is the last selected row, before the mouse click + RowPos newRow = m_pTableControl->getRowAtPoint( rPoint ); + if ( newRow == ROW_COL_HEADERS ) + newRow = m_pTableControl->getTopRow(); + + ColPos newCol = m_pTableControl->getColAtPoint( rPoint ); + if ( newCol == COL_ROW_HEADERS ) + newCol = m_pTableControl->getLeftColumn(); + + if ( ( newRow == ROW_INVALID ) || ( newCol == COL_INVALID ) ) + return; + + if ( bDontSelectAtCursor ) + { + if ( m_pTableControl->getSelectedRowCount() > 1 ) + m_pTableControl->getSelEngine()->AddAlways(true); + } + else if ( m_pTableControl->getAnchor() == m_pTableControl->getCurRow() ) + { + //selected region lies above the last selection + if( m_pTableControl->getCurRow() >= newRow) + { + //put selected rows in vector + while ( m_pTableControl->getAnchor() >= newRow ) + { + m_pTableControl->markRowAsSelected( m_pTableControl->getAnchor() ); + m_pTableControl->setAnchor( m_pTableControl->getAnchor() - 1 ); + } + m_pTableControl->setAnchor( m_pTableControl->getAnchor() + 1 ); + } + //selected region lies beneath the last selected row + else + { + while ( m_pTableControl->getAnchor() <= newRow ) + { + m_pTableControl->markRowAsSelected( m_pTableControl->getAnchor() ); + m_pTableControl->setAnchor( m_pTableControl->getAnchor() + 1 ); + } + m_pTableControl->setAnchor( m_pTableControl->getAnchor() - 1 ); + } + m_pTableControl->invalidateSelectedRegion( m_pTableControl->getCurRow(), newRow ); + } + //no region selected + else + { + if ( !m_pTableControl->hasRowSelection() ) + m_pTableControl->markRowAsSelected( newRow ); + else + { + if ( m_pTableControl->getSelEngine()->GetSelectionMode() == SelectionMode::Single ) + { + DeselectAll(); + m_pTableControl->markRowAsSelected( newRow ); + } + else + { + m_pTableControl->markRowAsSelected( newRow ); + } + } + if ( m_pTableControl->getSelectedRowCount() > 1 && m_pTableControl->getSelEngine()->GetSelectionMode() != SelectionMode::Single ) + m_pTableControl->getSelEngine()->AddAlways(true); + + m_pTableControl->invalidateRow( newRow ); + } + m_pTableControl->goTo( newCol, newRow ); + } + + bool TableFunctionSet::IsSelectionAtPoint( const Point& rPoint ) + { + m_pTableControl->getSelEngine()->AddAlways(false); + if ( !m_pTableControl->hasRowSelection() ) + return false; + else + { + RowPos curRow = m_pTableControl->getRowAtPoint( rPoint ); + m_pTableControl->setAnchor( ROW_INVALID ); + bool selected = m_pTableControl->isRowSelected( curRow ); + m_nCurrentRow = curRow; + return selected; + } + } + + void TableFunctionSet::DeselectAtPoint( const Point& ) + { + m_pTableControl->invalidateRow( m_nCurrentRow ); + m_pTableControl->markRowAsDeselected( m_nCurrentRow ); + } + + + void TableFunctionSet::DeselectAll() + { + if ( m_pTableControl->hasRowSelection() ) + { + for ( size_t i=0; i<m_pTableControl->getSelectedRowCount(); ++i ) + { + RowPos const rowIndex = m_pTableControl->getSelectedRowIndex(i); + m_pTableControl->invalidateRow( rowIndex ); + } + + m_pTableControl->markAllRowsAsDeselected(); + } + } + + +} // namespace svt::table + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/table/tablecontrol_impl.hxx b/svtools/source/table/tablecontrol_impl.hxx new file mode 100644 index 000000000..727dea92b --- /dev/null +++ b/svtools/source/table/tablecontrol_impl.hxx @@ -0,0 +1,479 @@ +/* -*- 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 <table/tablemodel.hxx> +#include <table/tablecontrolinterface.hxx> + +#include <vcl/svtaccessiblefactory.hxx> +#include <vcl/accessibletable.hxx> + +#include <vcl/seleng.hxx> + +#include <vector> + +class ScrollBar; +class ScrollBarBox; + +namespace svt::table +{ + struct MutableColumnMetrics : public ColumnMetrics + { + MutableColumnMetrics() + :ColumnMetrics() + { + } + + MutableColumnMetrics( tools::Long const i_startPixel, tools::Long const i_endPixel ) + :ColumnMetrics( i_startPixel, i_endPixel ) + { + } + + tools::Long getStart() const { return nStartPixel; } + tools::Long getEnd() const { return nEndPixel; } + + void move( tools::Long const i_offset ) { nStartPixel += i_offset; nEndPixel += i_offset; } + + tools::Long getWidth() const { return nEndPixel - nStartPixel; } + }; + + struct ColumnInfoPositionLess + { + bool operator()( MutableColumnMetrics const& i_lhs, MutableColumnMetrics const& i_rhs ) + { + return i_lhs.getEnd() < i_rhs.getStart(); + } + }; + + typedef ::std::vector< MutableColumnMetrics > ColumnPositions; + + class TableControl; + class TableDataWindow; + class TableFunctionSet; + + + //= TableControl_Impl + + class TableControl_Impl :public ITableControl + ,public ITableModelListener + { + friend class TableGeometry; + friend class TableRowGeometry; + friend class TableColumnGeometry; + friend class SuspendInvariants; + + private: + /// the control whose impl-instance we implement + TableControl& m_rAntiImpl; + /// the model of the table control + PTableModel m_pModel; + /// the input handler to use, usually the input handler as provided by ->m_pModel + PTableInputHandler m_pInputHandler; + /// info about the widths of our columns + ColumnPositions m_aColumnWidths; + + /// the height of a single row in the table, measured in pixels + tools::Long m_nRowHeightPixel; + /// the height of the column header row in the table, measured in pixels + tools::Long m_nColHeaderHeightPixel; + /// the width of the row header column in the table, measured in pixels + tools::Long m_nRowHeaderWidthPixel; + + /// the number of columns in the table control. Cached model value. + TableSize m_nColumnCount; + + /// the number of rows in the table control. Cached model value. + TableSize m_nRowCount; + + ColPos m_nCurColumn; + RowPos m_nCurRow; + ColPos m_nLeftColumn; + RowPos m_nTopRow; + + sal_Int32 m_nCursorHidden; + + /** the window to contain all data content, including header bars + + The window's upper left corner is at position (0,0), relative to the + table control, which is the direct parent of the data window. + */ + VclPtr<TableDataWindow> m_pDataWindow; + /// the vertical scrollbar, if any + VclPtr<ScrollBar> m_pVScroll; + /// the horizontal scrollbar, if any + VclPtr<ScrollBar> m_pHScroll; + VclPtr<ScrollBarBox> m_pScrollCorner; + //selection engine - for determining selection range, e.g. single, multiple + std::unique_ptr<SelectionEngine> m_pSelEngine; + //vector which contains the selected rows + std::vector<RowPos> m_aSelectedRows; + //part of selection engine + std::unique_ptr<TableFunctionSet> m_pTableFunctionSet; + //part of selection engine + RowPos m_nAnchor; + bool m_bUpdatingColWidths; + + vcl::AccessibleFactoryAccess m_aFactoryAccess; + vcl::table::IAccessibleTableControl* m_pAccessibleTable; + + public: + void setModel( const PTableModel& _pModel ); + + const PTableInputHandler& getInputHandler() const { return m_pInputHandler; } + + RowPos getCurRow() const { return m_nCurRow; } + + RowPos getAnchor() const { return m_nAnchor; } + void setAnchor( RowPos const i_anchor ) { m_nAnchor = i_anchor; } + + RowPos getTopRow() const { return m_nTopRow; } + ColPos getLeftColumn() const { return m_nLeftColumn; } + + const TableControl& getAntiImpl() const { return m_rAntiImpl; } + TableControl& getAntiImpl() { return m_rAntiImpl; } + + public: + explicit TableControl_Impl( TableControl& _rAntiImpl ); + virtual ~TableControl_Impl() override; + + /** to be called when the anti-impl instance has been resized + */ + void onResize(); + + /** paints the table control content which intersects with the given rectangle + */ + void doPaintContent(vcl::RenderContext& rRenderContext, const tools::Rectangle& _rUpdateRect); + + /** moves the cursor to the cell with the given coordinates + + To ease the caller's code, the coordinates must not necessarily denote a + valid position. If they don't, <FALSE/> will be returned. + */ + bool goTo( ColPos _nColumn, RowPos _nRow ); + + /** ensures that the given coordinate is visible + @param _nColumn + the column position which should be visible. Must be non-negative, and smaller + than the column count. + @param _nRow + the row position which should be visibleMust be non-negative, and smaller + than the row count. + */ + void ensureVisible( ColPos _nColumn, RowPos _nRow ); + + /** retrieves the content of the given cell, converted to a string + */ + OUString getCellContentAsString( RowPos const i_row, ColPos const i_col ); + + /** returns the position of the current row in the selection vector */ + static int getRowSelectedNumber(const ::std::vector<RowPos>& selectedRows, RowPos current); + + void invalidateRect(const tools::Rectangle &rInvalidateRect); + + /** ??? */ + void invalidateSelectedRegion( RowPos _nPrevRow, RowPos _nCurRow ); + + /** invalidates the part of the data window which is covered by the given rows + @param i_firstRow + the index of the first row to include in the invalidation + @param i_lastRow + the index of the last row to include in the invalidation, or ROW_INVALID if the invalidation + should happen down to the bottom of the data window. + */ + void invalidateRowRange( RowPos const i_firstRow, RowPos const i_lastRow ); + + /** invalidates the part of the data window which is covered by the given row + */ + void invalidateRow( RowPos const i_row ) { invalidateRowRange( i_row, i_row ); } + + /** invalidates all selected rows + */ + void invalidateSelectedRows(); + + void checkCursorPosition(); + + bool hasRowSelection() const { return !m_aSelectedRows.empty(); } + size_t getSelectedRowCount() const { return m_aSelectedRows.size(); } + RowPos getSelectedRowIndex( size_t const i_selectionIndex ) const; + + /** removes the given row index from m_aSelectedRows + + @return + <TRUE/> if and only if the row was previously marked as selected + */ + bool markRowAsDeselected( RowPos const i_rowIndex ); + + /** marks the given row as selected, by putting it into m_aSelectedRows + @return + <TRUE/> if and only if the row was previously <em>not</em> marked as selected + */ + bool markRowAsSelected( RowPos const i_rowIndex ); + + /** marks all rows as deselected + @return + <TRUE/> if and only if the selection actually changed by this operation + */ + bool markAllRowsAsDeselected(); + + /** marks all rows as selected + @return + <FALSE/> if and only if all rows were selected already. + */ + bool markAllRowsAsSelected(); + + void commitAccessibleEvent( sal_Int16 const i_eventID ); + void commitCellEvent( sal_Int16 const i_eventID, const css::uno::Any& i_newValue, const css::uno::Any& i_oldValue ); + void commitTableEvent( sal_Int16 const i_eventID, const css::uno::Any& i_newValue, const css::uno::Any& i_oldValue ); + + // ITableControl + virtual void hideCursor() override; + virtual void showCursor() override; + virtual bool dispatchAction( TableControlAction _eAction ) override; + virtual SelectionEngine* getSelEngine() override; + virtual PTableModel getModel() const override; + virtual ColPos getCurrentColumn() const override; + virtual RowPos getCurrentRow() const override; + virtual void activateCell( ColPos const i_col, RowPos const i_row ) override; + virtual ::Size getTableSizePixel() const override; + virtual void setPointer( PointerStyle i_pointer ) override; + virtual void captureMouse() override; + virtual void releaseMouse() override; + virtual void invalidate( TableArea const i_what ) override; + virtual tools::Long pixelWidthToAppFont( tools::Long const i_pixels ) const override; + virtual void hideTracking() override; + virtual void showTracking( tools::Rectangle const & i_location, ShowTrackFlags const i_flags ) override; + RowPos getRowAtPoint( const Point& rPoint ) const; + ColPos getColAtPoint( const Point& rPoint ) const; + virtual TableCell hitTest( const Point& rPoint ) const override; + virtual ColumnMetrics getColumnMetrics( ColPos const i_column ) const override; + virtual bool isRowSelected( RowPos i_row ) const override; + + + tools::Long appFontWidthToPixel( tools::Long const i_appFontUnits ) const; + + TableDataWindow& getDataWindow() { return *m_pDataWindow; } + const TableDataWindow& getDataWindow() const { return *m_pDataWindow; } + ScrollBar* getHorzScrollbar() { return m_pHScroll; } + ScrollBar* getVertScrollbar() { return m_pVScroll; } + + tools::Rectangle calcHeaderRect( bool bColHeader ); + tools::Rectangle calcHeaderCellRect( bool bColHeader, sal_Int32 nPos ); + tools::Rectangle calcTableRect() const; + tools::Rectangle calcCellRect( sal_Int32 nRow, sal_Int32 nCol ) const; + + // A11Y + css::uno::Reference< css::accessibility::XAccessible > + getAccessible( vcl::Window& i_parentWindow ); + void disposeAccessible(); + + bool isAccessibleAlive() const { return impl_isAccessibleAlive(); } + + // ITableModelListener + virtual void rowsInserted( RowPos first, RowPos last ) override; + virtual void rowsRemoved( RowPos first, RowPos last ) override; + virtual void columnInserted() override; + virtual void columnRemoved() override; + virtual void allColumnsRemoved() override; + virtual void cellsUpdated( RowPos const i_firstRow, RowPos const i_lastRow ) override; + virtual void columnChanged( ColPos const i_column, ColumnAttributeGroup const i_attributeGroup ) override; + virtual void tableMetricsChanged() override; + + private: + bool impl_isAccessibleAlive() const; + void impl_commitAccessibleEvent( + sal_Int16 const i_eventID, + css::uno::Any const & i_newValue + ); + + /** toggles the cursor visibility + + The method is not bound to the classes public invariants, as it's used in + situations where the they must not necessarily be fulfilled. + */ + void impl_ni_doSwitchCursor( bool _bOn ); + + /** returns the number of visible rows. + + @param _bAcceptPartialRow + specifies whether a possible only partially visible last row is + counted, too. + */ + TableSize impl_getVisibleRows( bool _bAcceptPartialRow ) const; + + /** returns the number of visible columns + + The value may change with different horizontal scroll positions, as + different columns have different widths. For instance, if your control is + 100 pixels wide, and has three columns of width 50, 50, 100, respectively, + then this method will return either "2" or "1", depending on which column + is the first visible one. + + @param _bAcceptPartialRow + specifies whether a possible only partially visible last row is + counted, too. + */ + TableSize impl_getVisibleColumns( bool _bAcceptPartialCol ) const; + + /** determines the rectangle occupied by the given cell + */ + void impl_getCellRect( ColPos _nColumn, RowPos _nRow, tools::Rectangle& _rCellRect ) const; + + /** updates all cached model values + + The method is not bound to the classes public invariants, as it's used in + situations where the they must not necessarily be fulfilled. + */ + void impl_ni_updateCachedModelValues(); + + /** updates the cached table metrics (row height etc.) + */ + void impl_ni_updateCachedTableMetrics(); + + /** does a relayout of the table control + + Column widths, and consequently the availability of the vertical and horizontal scrollbar, are updated + with a call to this method. + + @param i_assumeInflexibleColumnsUpToIncluding + the index of a column up to which all columns should be considered as inflexible, or + <code>COL_INVALID</code>. + */ + void impl_ni_relayout( ColPos const i_assumeInflexibleColumnsUpToIncluding = COL_INVALID ); + + /** calculates the new width of our columns, taking into account their min and max widths, and their relative + flexibility. + + @param i_assumeInflexibleColumnsUpToIncluding + the index of a column up to which all columns should be considered as inflexible, or + <code>COL_INVALID</code>. + + @param i_assumeVerticalScrollbar + controls whether or not we should assume the presence of a vertical scrollbar. If <true/>, and + if the model has a VerticalScrollbarVisibility != ScrollbarShowNever, the method will leave + space for a vertical scrollbar. + + @return + the overall width of the grid, which is available for columns + */ + tools::Long impl_ni_calculateColumnWidths( + ColPos const i_assumeInflexibleColumnsUpToIncluding, + bool const i_assumeVerticalScrollbar, + ::std::vector< tools::Long >& o_newColWidthsPixel + ) const; + + /** positions all child windows, e.g. the both scrollbars, the corner window, and the data window + */ + void impl_ni_positionChildWindows( + tools::Rectangle const & i_dataCellPlayground, + bool const i_verticalScrollbar, + bool const i_horizontalScrollbar + ); + + /** scrolls the view by the given number of rows + + The method is not bound to the classes public invariants, as it's used in + situations where the they must not necessarily be fulfilled. + + @return + the number of rows by which the viewport was scrolled. This may differ + from the given numbers to scroll in case the begin or the end of the + row range were reached. + */ + TableSize impl_ni_ScrollRows( TableSize _nRowDelta ); + + /** equivalent to impl_ni_ScrollRows, but checks the instances invariants beforehand (in a non-product build only) + */ + TableSize impl_scrollRows( TableSize const i_rowDelta ); + + /** scrolls the view by the given number of columns + + The method is not bound to the classes public invariants, as it's used in + situations where the they must not necessarily be fulfilled. + + @return + the number of columns by which the viewport was scrolled. This may differ + from the given numbers to scroll in case the begin or the end of the + column range were reached. + */ + TableSize impl_ni_ScrollColumns( TableSize _nColumnDelta ); + + /** equivalent to impl_ni_ScrollColumns, but checks the instances invariants beforehand (in a non-product build only) + */ + TableSize impl_scrollColumns( TableSize const i_columnDelta ); + + /** retrieves the area occupied by the totality of (at least partially) visible cells + + The returned area includes row and column headers. Also, it takes into + account the fact that there might be less columns than would normally + find room in the control. + + As a result of respecting the partial visibility of rows and columns, + the returned area might be larger than the data window's output size. + */ + tools::Rectangle impl_getAllVisibleCellsArea() const; + + /** retrieves the area occupied by all (at least partially) visible data cells. + + Effectively, the returned area is the same as returned by ->impl_getAllVisibleCellsArea, + minus the row and column header areas. + */ + tools::Rectangle impl_getAllVisibleDataCellArea() const; + + /** retrieves the column which covers the given ordinate + */ + ColPos impl_getColumnForOrdinate( tools::Long const i_ordinate ) const; + + /** retrieves the row which covers the given abscissa + */ + RowPos impl_getRowForAbscissa( tools::Long const i_abscissa ) const; + + /// invalidates the window area occupied by the given column + void impl_invalidateColumn( ColPos const i_column ); + + DECL_LINK( OnScroll, ScrollBar*, void ); + DECL_LINK( OnUpdateScrollbars, void*, void ); + }; + + //see seleng.hxx, seleng.cxx, FunctionSet overridables, part of selection engine + class TableFunctionSet : public FunctionSet + { + private: + TableControl_Impl* m_pTableControl; + RowPos m_nCurrentRow; + + public: + explicit TableFunctionSet(TableControl_Impl* _pTableControl); + virtual ~TableFunctionSet() override; + + virtual void BeginDrag() override; + virtual void CreateAnchor() override; + virtual void DestroyAnchor() override; + virtual void SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor = false) override; + virtual bool IsSelectionAtPoint( const Point& rPoint ) override; + virtual void DeselectAtPoint( const Point& rPoint ) override; + virtual void DeselectAll() override; + }; + + +} // namespace svt::table + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/table/tabledatawindow.cxx b/svtools/source/table/tabledatawindow.cxx new file mode 100644 index 000000000..06e6902b0 --- /dev/null +++ b/svtools/source/table/tabledatawindow.cxx @@ -0,0 +1,200 @@ +/* -*- 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 <table/tablecontrol.hxx> + +#include "tabledatawindow.hxx" +#include "tablecontrol_impl.hxx" +#include "tablegeometry.hxx" + +#include <vcl/help.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandevent.hxx> + +namespace svt::table +{ + using css::uno::Any; + + TableDataWindow::TableDataWindow( TableControl_Impl& _rTableControl ) + :Window( &_rTableControl.getAntiImpl() ) + ,m_rTableControl( _rTableControl ) + { + // by default, use the background as determined by the style settings + const Color aWindowColor( GetSettings().GetStyleSettings().GetFieldColor() ); + SetBackground( Wallpaper( aWindowColor ) ); + GetOutDev()->SetFillColor( aWindowColor ); + } + + TableDataWindow::~TableDataWindow() + { + disposeOnce(); + } + + void TableDataWindow::dispose() + { + impl_hideTipWindow(); + Window::dispose(); + } + + void TableDataWindow::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rUpdateRect ) + { + m_rTableControl.doPaintContent(rRenderContext, rUpdateRect); + } + + void TableDataWindow::RequestHelp( const HelpEvent& rHEvt ) + { + HelpEventMode const nHelpMode = rHEvt.GetMode(); + if ( IsMouseCaptured() + || !( nHelpMode & HelpEventMode::QUICK ) + ) + { + Window::RequestHelp( rHEvt ); + return; + } + + OUString sHelpText; + QuickHelpFlags nHelpStyle = QuickHelpFlags::NONE; + + Point const aMousePos( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) ); + RowPos const hitRow = m_rTableControl.getRowAtPoint( aMousePos ); + ColPos const hitCol = m_rTableControl.getColAtPoint( aMousePos ); + + PTableModel const pTableModel( m_rTableControl.getModel() ); + if ( ( hitCol >= 0 ) && ( hitCol < pTableModel->getColumnCount() ) ) + { + if ( hitRow == ROW_COL_HEADERS ) + { + sHelpText = pTableModel->getColumnModel( hitCol )->getHelpText(); + } + else if ( ( hitRow >= 0 ) && ( hitRow < pTableModel->getRowCount() ) ) + { + Any aCellToolTip; + pTableModel->getCellToolTip( hitCol, hitRow, aCellToolTip ); + if ( !aCellToolTip.hasValue() ) + { + // use the cell content + pTableModel->getCellContent( hitCol, hitRow, aCellToolTip ); + + // use the cell content as tool tip only if it doesn't fit into the cell. + tools::Rectangle const aWindowRect( Point( 0, 0 ), GetOutputSizePixel() ); + TableCellGeometry const aCell( m_rTableControl, aWindowRect, hitCol, hitRow ); + tools::Rectangle const aCellRect( aCell.getRect() ); + + PTableRenderer const pRenderer = pTableModel->getRenderer(); + if ( pRenderer->FitsIntoCell( aCellToolTip, *GetOutDev(), aCellRect ) ) + aCellToolTip.clear(); + } + + pTableModel->getRenderer()->GetFormattedCellString( aCellToolTip, sHelpText ); + + if ( sHelpText.indexOf( '\n' ) >= 0 ) + nHelpStyle = QuickHelpFlags::TipStyleBalloon; + } + } + + if ( !sHelpText.isEmpty() ) + { + // hide the standard (singleton) help window, so we do not have two help windows open at the same time + Help::HideBalloonAndQuickHelp(); + + tools::Rectangle const aControlScreenRect( + OutputToScreenPixel( Point( 0, 0 ) ), + GetOutputSizePixel() + ); + + Help::ShowQuickHelp(this, aControlScreenRect, sHelpText, nHelpStyle); + } + else + { + impl_hideTipWindow(); + Window::RequestHelp( rHEvt ); + } + } + + void TableDataWindow::impl_hideTipWindow() + { + Help::HideBalloonAndQuickHelp(); + } + + void TableDataWindow::MouseMove( const MouseEvent& rMEvt ) + { + if ( rMEvt.IsLeaveWindow() ) + impl_hideTipWindow(); + + if ( !m_rTableControl.getInputHandler()->MouseMove( m_rTableControl, rMEvt ) ) + { + Window::MouseMove( rMEvt ); + } + } + + void TableDataWindow::MouseButtonDown( const MouseEvent& rMEvt ) + { + impl_hideTipWindow(); + + Point const aPoint = rMEvt.GetPosPixel(); + RowPos const hitRow = m_rTableControl.getRowAtPoint( aPoint ); + bool const wasRowSelected = m_rTableControl.isRowSelected( hitRow ); + size_t const nPrevSelRowCount = m_rTableControl.getSelectedRowCount(); + + if ( !m_rTableControl.getInputHandler()->MouseButtonDown( m_rTableControl, rMEvt ) ) + { + Window::MouseButtonDown( rMEvt ); + return; + } + + bool const isRowSelected = m_rTableControl.isRowSelected( hitRow ); + size_t const nCurSelRowCount = m_rTableControl.getSelectedRowCount(); + if ( isRowSelected != wasRowSelected || nCurSelRowCount != nPrevSelRowCount ) + { + m_aSelectHdl.Call( nullptr ); + } + } + + + void TableDataWindow::MouseButtonUp( const MouseEvent& rMEvt ) + { + if ( !m_rTableControl.getInputHandler()->MouseButtonUp( m_rTableControl, rMEvt ) ) + Window::MouseButtonUp( rMEvt ); + + m_rTableControl.getAntiImpl().GrabFocus(); + } + + + bool TableDataWindow::EventNotify(NotifyEvent& rNEvt ) + { + bool bDone = false; + if ( rNEvt.GetType() == MouseNotifyEvent::COMMAND ) + { + const CommandEvent& rCEvt = *rNEvt.GetCommandEvent(); + if ( rCEvt.GetCommand() == CommandEventId::Wheel ) + { + const CommandWheelData* pData = rCEvt.GetWheelData(); + if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) ) + { + bDone = HandleScrollCommand( rCEvt, m_rTableControl.getHorzScrollbar(), m_rTableControl.getVertScrollbar() ); + } + } + } + return bDone || Window::EventNotify( rNEvt ); + } + +} // namespace svt::table + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/table/tabledatawindow.hxx b/svtools/source/table/tabledatawindow.hxx new file mode 100644 index 000000000..e42a05493 --- /dev/null +++ b/svtools/source/table/tabledatawindow.hxx @@ -0,0 +1,66 @@ +/* -*- 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 <vcl/window.hxx> + + +namespace svt::table +{ + class TableControl_Impl; + class TableFunctionSet; + + /** the window containing the content area (including headers) of + a table control + */ + class TableDataWindow : public vcl::Window + { + friend class TableFunctionSet; + private: + TableControl_Impl& m_rTableControl; + Link<LinkParamNone*,void> m_aSelectHdl; + + public: + explicit TableDataWindow( TableControl_Impl& _rTableControl ); + virtual ~TableDataWindow() override; + virtual void dispose() override; + + void SetSelectHdl(const Link<LinkParamNone*,void>& rLink) + { + m_aSelectHdl = rLink; + } + + // Window overridables + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override; + virtual void MouseMove( const MouseEvent& rMEvt) override; + virtual void MouseButtonDown( const MouseEvent& rMEvt) override; + virtual void MouseButtonUp( const MouseEvent& rMEvt) override; + virtual bool EventNotify(NotifyEvent& rNEvt) override; + virtual void RequestHelp( const HelpEvent& rHEvt ) override; + + private: + static void impl_hideTipWindow(); + }; + +} // namespace svt::table + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/table/tablegeometry.cxx b/svtools/source/table/tablegeometry.cxx new file mode 100644 index 000000000..5b18826c5 --- /dev/null +++ b/svtools/source/table/tablegeometry.cxx @@ -0,0 +1,154 @@ +/* -*- 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 "tablegeometry.hxx" +#include "tablecontrol_impl.hxx" + + +namespace svt::table +{ + + + //= TableRowGeometry + + + TableRowGeometry::TableRowGeometry( TableControl_Impl const & _rControl, tools::Rectangle const & _rBoundaries, + RowPos const _nRow, bool const i_allowVirtualRows ) + :TableGeometry( _rControl, _rBoundaries ) + ,m_nRowPos( _nRow ) + ,m_bAllowVirtualRows( i_allowVirtualRows ) + { + if ( m_nRowPos == ROW_COL_HEADERS ) + { + m_aRect.SetTop( 0 ); + m_aRect.SetBottom( m_rControl.m_nColHeaderHeightPixel - 1 ); + } + else + { + impl_initRect(); + } + } + + + void TableRowGeometry::impl_initRect() + { + if ( ( m_nRowPos >= m_rControl.m_nTopRow ) && impl_isValidRow( m_nRowPos ) ) + { + m_aRect.SetTop( m_rControl.m_nColHeaderHeightPixel + ( m_nRowPos - m_rControl.m_nTopRow ) * m_rControl.m_nRowHeightPixel ); + m_aRect.SetBottom( m_aRect.Top() + m_rControl.m_nRowHeightPixel - 1 ); + } + else + m_aRect.SetEmpty(); + } + + + bool TableRowGeometry::impl_isValidRow( RowPos const i_row ) const + { + return m_bAllowVirtualRows || ( i_row < m_rControl.m_pModel->getRowCount() ); + } + + + bool TableRowGeometry::moveDown() + { + if ( m_nRowPos == ROW_COL_HEADERS ) + { + m_nRowPos = m_rControl.m_nTopRow; + impl_initRect(); + } + else + { + if ( impl_isValidRow( ++m_nRowPos ) ) + m_aRect.Move( 0, m_rControl.m_nRowHeightPixel ); + else + m_aRect.SetEmpty(); + } + return isValid(); + } + + + //= TableColumnGeometry + + + TableColumnGeometry::TableColumnGeometry( TableControl_Impl const & _rControl, tools::Rectangle const & _rBoundaries, + ColPos const _nCol ) + :TableGeometry( _rControl, _rBoundaries ) + ,m_nColPos( _nCol ) + { + if ( m_nColPos == COL_ROW_HEADERS ) + { + m_aRect.SetLeft( 0 ); + m_aRect.SetRight( m_rControl.m_nRowHeaderWidthPixel - 1 ); + } + else + { + impl_initRect(); + } + } + + + void TableColumnGeometry::impl_initRect() + { + ColPos nLeftColumn = m_rControl.m_nLeftColumn; + if ( ( m_nColPos >= nLeftColumn ) && impl_isValidColumn( m_nColPos ) ) + { + m_aRect.SetLeft( m_rControl.m_nRowHeaderWidthPixel ); + // TODO: take into account any possibly frozen columns + + for ( ColPos col = nLeftColumn; col < m_nColPos; ++col ) + m_aRect.AdjustLeft(m_rControl.m_aColumnWidths[ col ].getWidth() ); + m_aRect.SetRight( m_aRect.Left() + m_rControl.m_aColumnWidths[ m_nColPos ].getWidth() - 1 ); + } + else + m_aRect.SetEmpty(); + } + + + bool TableColumnGeometry::impl_isValidColumn( ColPos const i_column ) const + { + return i_column < ColPos( m_rControl.m_aColumnWidths.size() ); + } + + + bool TableColumnGeometry::moveRight() + { + if ( m_nColPos == COL_ROW_HEADERS ) + { + m_nColPos = m_rControl.m_nLeftColumn; + impl_initRect(); + } + else + { + if ( impl_isValidColumn( ++m_nColPos ) ) + { + m_aRect.SetLeft( m_aRect.Right() + 1 ); + m_aRect.AdjustRight(m_rControl.m_aColumnWidths[ m_nColPos ].getWidth() ); + } + else + m_aRect.SetEmpty(); + } + + return isValid(); + } + + +} // namespace svt::table + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/table/tablegeometry.hxx b/svtools/source/table/tablegeometry.hxx new file mode 100644 index 000000000..9fb6d03f0 --- /dev/null +++ b/svtools/source/table/tablegeometry.hxx @@ -0,0 +1,158 @@ +/* -*- 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 <table/tabletypes.hxx> + +#include <tools/gen.hxx> + + +namespace svt::table +{ + + + class TableControl_Impl; + + + //= TableGeometry + + class TableGeometry + { + protected: + const TableControl_Impl& m_rControl; + const tools::Rectangle& m_rBoundaries; + tools::Rectangle m_aRect; + + protected: + TableGeometry( + const TableControl_Impl& _rControl, + const tools::Rectangle& _rBoundaries + ) + :m_rControl( _rControl ) + ,m_rBoundaries( _rBoundaries ) + ,m_aRect( _rBoundaries ) + { + } + + public: + // attribute access + const TableControl_Impl& getControl() const { return m_rControl; } + + // status + const tools::Rectangle& getRect() const { return m_aRect; } + bool isValid() const { return !m_aRect.GetIntersection( m_rBoundaries ).IsEmpty(); } + }; + + + //= TableRowGeometry + + class TableRowGeometry final : public TableGeometry + { + public: + TableRowGeometry( + TableControl_Impl const & _rControl, + tools::Rectangle const & _rBoundaries, + RowPos const _nRow, + bool const i_allowVirtualRows = false + // allow rows >= getRowCount()? + ); + + // status + RowPos getRow() const { return m_nRowPos; } + // operations + bool moveDown(); + + private: + void impl_initRect(); + bool impl_isValidRow( RowPos const i_row ) const; + + RowPos m_nRowPos; + bool m_bAllowVirtualRows; + }; + + + //= TableColumnGeometry + + class TableColumnGeometry final : public TableGeometry + { + public: + TableColumnGeometry( + TableControl_Impl const & _rControl, + tools::Rectangle const & _rBoundaries, + ColPos const _nCol + ); + + // status + ColPos getCol() const { return m_nColPos; } + // operations + bool moveRight(); + + private: + void impl_initRect(); + bool impl_isValidColumn( ColPos const i_column ) const; + + ColPos m_nColPos; + }; + + + //= TableCellGeometry + + /** a helper representing geometry information of a cell + */ + class TableCellGeometry + { + private: + TableRowGeometry m_aRow; + TableColumnGeometry m_aCol; + + public: + TableCellGeometry( + TableControl_Impl const & _rControl, + tools::Rectangle const & _rBoundaries, + ColPos const _nCol, + RowPos const _nRow + ) + :m_aRow( _rControl, _rBoundaries, _nRow, false/*allowVirtualCells*/ ) + ,m_aCol( _rControl, _rBoundaries, _nCol ) + { + } + + TableCellGeometry( + const TableRowGeometry& _rRow, + ColPos _nCol + ) + :m_aRow( _rRow ) + ,m_aCol( _rRow.getControl(), _rRow.getRect(), _nCol ) + { + } + + tools::Rectangle getRect() const { return m_aRow.getRect().GetIntersection( m_aCol.getRect() ); } + ColPos getColumn() const { return m_aCol.getCol(); } + bool isValid() const { return !getRect().IsEmpty(); } + + bool moveRight() {return m_aCol.moveRight(); } + }; + + +} // namespace svt::table + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uitest/uiobject.cxx b/svtools/source/uitest/uiobject.cxx new file mode 100644 index 000000000..2de31226f --- /dev/null +++ b/svtools/source/uitest/uiobject.cxx @@ -0,0 +1,54 @@ +/* -*- 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 <uiobject.hxx> +#include <svtools/valueset.hxx> +#include <vcl/window.hxx> + +ValueSetUIObject::ValueSetUIObject(const VclPtr<vcl::Window>& rSetWin) + : DrawingAreaUIObject(rSetWin) + , mpSet(static_cast<ValueSet*>(mpController)) +{ +} + +void ValueSetUIObject::execute(const OUString& rAction, const StringMap& rParameters) +{ + if (rAction == "CHOOSE") + { + if (rParameters.find("POS") != rParameters.end()) + { + OUString aIndexStr = rParameters.find("POS")->second; + + sal_Int32 nIndex = aIndexStr.toInt32(); + mpSet->SelectItem(nIndex); + mpSet->Select(); + } + } + else + DrawingAreaUIObject::execute(rAction, rParameters); +} + +std::unique_ptr<UIObject> ValueSetUIObject::create(vcl::Window* pWindow) +{ + return std::unique_ptr<UIObject>(new ValueSetUIObject(pWindow)); +} + +OUString ValueSetUIObject::get_name() const { return "ValueSetUIObject"; } + +StringMap ValueSetUIObject::get_state() +{ + StringMap aMap = DrawingAreaUIObject::get_state(); + aMap["SelectedItemId"] = OUString::number(mpSet->GetSelectedItemId()); + aMap["SelectedItemPos"] = OUString::number(mpSet->GetSelectItemPos()); + aMap["ItemsCount"] = OUString::number(mpSet->GetItemCount()); + return aMap; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/addrtempuno.cxx b/svtools/source/uno/addrtempuno.cxx new file mode 100644 index 000000000..54cb28afc --- /dev/null +++ b/svtools/source/uno/addrtempuno.cxx @@ -0,0 +1,213 @@ +/* -*- 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 <svtools/addresstemplate.hxx> +#include <svtools/genericunodialog.hxx> +#include <comphelper/proparrhlp.hxx> +#include <comphelper/propertysequence.hxx> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/util/AliasProgrammaticPair.hpp> +#include <com/sun/star/sdbc/XDataSource.hpp> +#include <vcl/svapp.hxx> + +using namespace svt; + +namespace { + +#define UNODIALOG_PROPERTY_ID_ALIASES 100 +constexpr OUStringLiteral UNODIALOG_PROPERTY_ALIASES = u"FieldMapping"; + + using namespace css::uno; + using namespace css::lang; + using namespace css::util; + using namespace css::beans; + using namespace css::sdbc; + + class OAddressBookSourceDialogUno + :public OGenericUnoDialog + ,public ::comphelper::OPropertyArrayUsageHelper< OAddressBookSourceDialogUno > + { + private: + Sequence< AliasProgrammaticPair > m_aAliases; + Reference< XDataSource > m_xDataSource; + OUString m_sDataSourceName; + OUString m_sTable; + + public: + explicit OAddressBookSourceDialogUno(const Reference< XComponentContext >& _rxORB); + + // XTypeProvider + virtual Sequence<sal_Int8> SAL_CALL getImplementationId( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // XPropertySet + virtual Reference< XPropertySetInfo> SAL_CALL getPropertySetInfo() override; + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + + // OPropertyArrayUsageHelper + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override; + + virtual void SAL_CALL initialize(const Sequence< Any >& aArguments) override; + + protected: + // OGenericUnoDialog overridables + virtual std::unique_ptr<weld::DialogController> createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) override; + + virtual void implInitialize(const css::uno::Any& _rValue) override; + + virtual void executedDialog(sal_Int16 _nExecutionResult) override; + }; + + + OAddressBookSourceDialogUno::OAddressBookSourceDialogUno(const Reference< XComponentContext >& _rxORB) + :OGenericUnoDialog(_rxORB) + { + registerProperty(UNODIALOG_PROPERTY_ALIASES, UNODIALOG_PROPERTY_ID_ALIASES, PropertyAttribute::READONLY, + &m_aAliases, cppu::UnoType<decltype(m_aAliases)>::get()); + } + + + Sequence<sal_Int8> SAL_CALL OAddressBookSourceDialogUno::getImplementationId( ) + { + return css::uno::Sequence<sal_Int8>(); + } + + + OUString SAL_CALL OAddressBookSourceDialogUno::getImplementationName() + { + return "com.sun.star.comp.svtools.OAddressBookSourceDialogUno"; + } + + + css::uno::Sequence<OUString> SAL_CALL OAddressBookSourceDialogUno::getSupportedServiceNames() + { + return { "com.sun.star.ui.AddressBookSourceDialog" }; + } + + + Reference<XPropertySetInfo> SAL_CALL OAddressBookSourceDialogUno::getPropertySetInfo() + { + Reference<XPropertySetInfo> xInfo( createPropertySetInfo( getInfoHelper() ) ); + return xInfo; + } + + ::cppu::IPropertyArrayHelper& OAddressBookSourceDialogUno::getInfoHelper() + { + return *getArrayHelper(); + } + + ::cppu::IPropertyArrayHelper* OAddressBookSourceDialogUno::createArrayHelper( ) const + { + Sequence< Property > aProps; + describeProperties(aProps); + return new ::cppu::OPropertyArrayHelper(aProps); + } + + void OAddressBookSourceDialogUno::executedDialog(sal_Int16 _nExecutionResult) + { + OGenericUnoDialog::executedDialog(_nExecutionResult); + + if ( _nExecutionResult && m_xDialog ) + static_cast<AddressBookSourceDialog*>(m_xDialog.get())->getFieldMapping(m_aAliases); + } + + void SAL_CALL OAddressBookSourceDialogUno::initialize(const Sequence< Any >& rArguments) + { + if( rArguments.getLength() == 5 ) + { + Reference<css::awt::XWindow> xParentWindow; + Reference<css::beans::XPropertySet> xDataSource; + OUString sDataSourceName; + OUString sCommand; + OUString sTitle; + if ( (rArguments[0] >>= xParentWindow) + && (rArguments[1] >>= xDataSource) + && (rArguments[2] >>= sDataSourceName) + && (rArguments[3] >>= sCommand) + && (rArguments[4] >>= sTitle) ) + { + + // convert the parameters for creating the dialog to PropertyValues + Sequence<Any> aArguments(comphelper::InitAnyPropertySequence( + { + {"ParentWindow", Any(xParentWindow)}, + {"DataSource", Any(xDataSource)}, + {"DataSourceName", Any(sDataSourceName)}, + {"Command", Any(sCommand)}, // the table to use + {"Title", Any(sTitle)} + })); + OGenericUnoDialog::initialize(aArguments); + return; + } + } + OGenericUnoDialog::initialize(rArguments); + } + + void OAddressBookSourceDialogUno::implInitialize(const css::uno::Any& _rValue) + { + PropertyValue aVal; + if (_rValue >>= aVal) + { + if (aVal.Name == "DataSource") + { + bool bSuccess = aVal.Value >>= m_xDataSource; + OSL_ENSURE( bSuccess, "OAddressBookSourceDialogUno::implInitialize: invalid type for DataSource!" ); + return; + } + + if (aVal.Name == "DataSourceName") + { + bool bSuccess = aVal.Value >>= m_sDataSourceName; + OSL_ENSURE( bSuccess, "OAddressBookSourceDialogUno::implInitialize: invalid type for DataSourceName!" ); + return; + } + + if (aVal.Name == "Command") + { + bool bSuccess = aVal.Value >>= m_sTable; + OSL_ENSURE( bSuccess, "OAddressBookSourceDialogUno::implInitialize: invalid type for Command!" ); + return; + } + } + + OGenericUnoDialog::implInitialize( _rValue ); + } + + std::unique_ptr<weld::DialogController> OAddressBookSourceDialogUno::createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) + { + weld::Window* pParent = Application::GetFrameWeld(rParent); + if ( m_xDataSource.is() && !m_sTable.isEmpty() ) + return std::make_unique<AddressBookSourceDialog>(pParent, m_aContext, m_xDataSource, m_sDataSourceName, m_sTable, m_aAliases); + return std::make_unique<AddressBookSourceDialog>(pParent, m_aContext); + } +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_svtools_OAddressBookSourceDialogUno_get_implementation( + css::uno::XComponentContext * context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new OAddressBookSourceDialogUno(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/fpicker.cxx b/svtools/source/uno/fpicker.cxx new file mode 100644 index 000000000..dbb69d601 --- /dev/null +++ b/svtools/source/uno/fpicker.cxx @@ -0,0 +1,171 @@ +/* -*- 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 <rtl/ustring.hxx> + +#include <com/sun/star/lang/XMultiComponentFactory.hpp> + +#include <svl/pickerhistoryaccess.hxx> +#include <officecfg/Office/Common.hxx> + +#include <vcl/svapp.hxx> + +#include "fpicker.hxx" + +using css::uno::Reference; +using css::uno::Sequence; + +/* + * FilePicker implementation. + */ +static OUString FilePicker_getSystemPickerServiceName() +{ +#ifdef UNX + OUString aDesktopEnvironment (Application::GetDesktopEnvironment()); + if (aDesktopEnvironment.equalsIgnoreAsciiCase("macosx")) + return "com.sun.star.ui.dialogs.AquaFilePicker"; + else + return "com.sun.star.ui.dialogs.SystemFilePicker"; +#endif +#ifdef _WIN32 + return "com.sun.star.ui.dialogs.Win32FilePicker"; +#endif +} + +Reference< css::uno::XInterface > FilePicker_CreateInstance ( + Reference< css::uno::XComponentContext > const & context) +{ + Reference< css::uno::XInterface > xResult; + + if (!context.is()) + return xResult; + + Reference< css::lang::XMultiComponentFactory > xFactory (context->getServiceManager()); + if (xFactory.is() && officecfg::Office::Common::Misc::UseSystemFileDialog::get()) + { + xResult.set( Application::createFilePicker( context ) ); + + if (!xResult.is()) + { + try + { + xResult = xFactory->createInstanceWithContext ( + FilePicker_getSystemPickerServiceName(), + context); + } + catch (css::uno::Exception const &) + { + // Handled below (see @ fallback). + } + } + } + + + if (!xResult.is() && xFactory.is()) + { + // Always fall back to OfficeFilePicker. + xResult = xFactory->createInstanceWithContext ( + "com.sun.star.ui.dialogs.OfficeFilePicker", + context); + } + if (xResult.is()) + { + // Add to FilePicker history. + svt::addFilePicker (xResult); + } + return xResult; +} + +OUString FilePicker_getImplementationName() +{ + return "com.sun.star.comp.svt.FilePicker"; +} + +Sequence< OUString > FilePicker_getSupportedServiceNames() +{ + Sequence< OUString > aServiceNames { "com.sun.star.ui.dialogs.FilePicker" }; + return aServiceNames; +} + +/* + * FolderPicker implementation. + */ +static OUString FolderPicker_getSystemPickerServiceName() +{ +#ifdef UNX + OUString aDesktopEnvironment (Application::GetDesktopEnvironment()); + if (aDesktopEnvironment.equalsIgnoreAsciiCase("macosx")) + return "com.sun.star.ui.dialogs.AquaFolderPicker"; +#endif + return "com.sun.star.ui.dialogs.SystemFolderPicker"; +} + +Reference< css::uno::XInterface > FolderPicker_CreateInstance ( + Reference< css::uno::XComponentContext > const & context) +{ + Reference< css::uno::XInterface > xResult; + + if (!context.is()) + return xResult; + + Reference< css::lang::XMultiComponentFactory > xFactory (context->getServiceManager()); + if (xFactory.is() && officecfg::Office::Common::Misc::UseSystemFileDialog::get()) + { + xResult.set( Application::createFolderPicker( context ) ); + if (!xResult.is()) + { + try + { + xResult = xFactory->createInstanceWithContext ( + FolderPicker_getSystemPickerServiceName(), + context); + } + catch (css::uno::Exception const &) + { + // Handled below (see @ fallback). + } + } + } + if (!xResult.is() && xFactory.is() ) + { + // Always fall back to OfficeFolderPicker. + xResult = xFactory->createInstanceWithContext ( + "com.sun.star.ui.dialogs.OfficeFolderPicker", + context); + } + if (xResult.is()) + { + // Add to FolderPicker history. + svt::addFolderPicker (xResult); + } + return xResult; +} + +OUString FolderPicker_getImplementationName() +{ + return "com.sun.star.comp.svt.FolderPicker"; +} + +Sequence< OUString > FolderPicker_getSupportedServiceNames() +{ + Sequence< OUString > aServiceNames { "com.sun.star.ui.dialogs.FolderPicker" }; + return aServiceNames; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/fpicker.hxx b/svtools/source/uno/fpicker.hxx new file mode 100644 index 000000000..9fcbacd54 --- /dev/null +++ b/svtools/source/uno/fpicker.hxx @@ -0,0 +1,45 @@ +/* -*- 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 <sal/config.h> +#include <rtl/ustring.hxx> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> + +namespace com::sun::star { + namespace lang { class XMultiServiceFactory; } + namespace uno { class XInterface; } +} + +css::uno::Reference<css::uno::XInterface> FilePicker_CreateInstance( + css::uno::Reference< css::uno::XComponentContext > const & context); +css::uno::Sequence<OUString> FilePicker_getSupportedServiceNames(); +OUString FilePicker_getImplementationName(); + +css::uno::Reference<css::uno::XInterface> FolderPicker_CreateInstance( + css::uno::Reference< css::uno::XComponentContext > const & context); +css::uno::Sequence<OUString> FolderPicker_getSupportedServiceNames(); +OUString FolderPicker_getImplementationName(); + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/framestatuslistener.cxx b/svtools/source/uno/framestatuslistener.cxx new file mode 100644 index 000000000..0ad4271a1 --- /dev/null +++ b/svtools/source/uno/framestatuslistener.cxx @@ -0,0 +1,269 @@ +/* -*- 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 <framestatuslistener.hxx> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <cppuhelper/queryinterface.hxx> +#include <vcl/svapp.hxx> + +using namespace ::cppu; +using namespace css::awt; +using namespace css::uno; +using namespace css::util; +using namespace css::beans; +using namespace css::lang; +using namespace css::frame; + +namespace svt +{ + +FrameStatusListener::FrameStatusListener( + const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& xFrame ) : + OWeakObject() + , m_bDisposed( false ) + , m_xFrame( xFrame ) + , m_xContext( rxContext ) +{ +} + +FrameStatusListener::~FrameStatusListener() +{ +} + +// XInterface +Any SAL_CALL FrameStatusListener::queryInterface( const Type& rType ) +{ + Any a = ::cppu::queryInterface( + rType , + static_cast< XComponent* >( this ), + static_cast< XFrameActionListener* >( this ), + static_cast< XStatusListener* >( this ), + static_cast< XEventListener* >( static_cast< XStatusListener* >( this )), + static_cast< XEventListener* >( static_cast< XFrameActionListener* >( this ))); + + if ( a.hasValue() ) + return a; + + return OWeakObject::queryInterface( rType ); +} + +void SAL_CALL FrameStatusListener::acquire() noexcept +{ + OWeakObject::acquire(); +} + +void SAL_CALL FrameStatusListener::release() noexcept +{ + OWeakObject::release(); +} + +// XComponent +void SAL_CALL FrameStatusListener::dispose() +{ + Reference< XComponent > xThis = this; + + SolarMutexGuard aSolarMutexGuard; + if ( m_bDisposed ) + throw DisposedException(); + + for (auto const& listener : m_aListenerMap) + { + try + { + Reference< XDispatch > xDispatch( listener.second ); + Reference< XURLTransformer > xURLTransformer( css::util::URLTransformer::create( m_xContext ) ); + css::util::URL aTargetURL; + aTargetURL.Complete = listener.first; + xURLTransformer->parseStrict( aTargetURL ); + + if ( xDispatch.is() ) + xDispatch->removeStatusListener( this, aTargetURL ); + } + catch (const Exception&) + { + } + } + + m_bDisposed = true; +} + +void SAL_CALL FrameStatusListener::addEventListener( const Reference< XEventListener >& ) +{ + // helper class for status updates - no need to support listener +} + +void SAL_CALL FrameStatusListener::removeEventListener( const Reference< XEventListener >& ) +{ + // helper class for status updates - no need to support listener +} + +// XEventListener +void SAL_CALL FrameStatusListener::disposing( const EventObject& Source ) +{ + Reference< XInterface > xSource( Source.Source ); + + SolarMutexGuard aSolarMutexGuard; + + for (auto & listener : m_aListenerMap) + { + // Compare references and release dispatch references if they are equal. + Reference< XInterface > xIfac( listener.second, UNO_QUERY ); + if ( xSource == xIfac ) + listener.second.clear(); + } + + Reference< XInterface > xIfac( m_xFrame, UNO_QUERY ); + if ( xIfac == xSource ) + m_xFrame.clear(); +} + +void FrameStatusListener::frameAction( const FrameActionEvent& Action ) +{ + if ( Action.Action == FrameAction_CONTEXT_CHANGED ) + bindListener(); +} + +void FrameStatusListener::addStatusListener( const OUString& aCommandURL ) +{ + Reference< XDispatch > xDispatch; + Reference< XStatusListener > xStatusListener; + css::util::URL aTargetURL; + + { + SolarMutexGuard aSolarMutexGuard; + URLToDispatchMap::iterator pIter = m_aListenerMap.find( aCommandURL ); + + // Already in the list of status listener. Do nothing. + if ( pIter != m_aListenerMap.end() ) + return; + + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + if ( m_xContext.is() && xDispatchProvider.is() ) + { + Reference< XURLTransformer > xURLTransformer( css::util::URLTransformer::create( m_xContext ) ); + aTargetURL.Complete = aCommandURL; + xURLTransformer->parseStrict( aTargetURL ); + xDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + + xStatusListener = this; + URLToDispatchMap::iterator aIter = m_aListenerMap.find( aCommandURL ); + if ( aIter != m_aListenerMap.end() ) + { + Reference< XDispatch > xOldDispatch( aIter->second ); + aIter->second = xDispatch; + + try + { + if ( xOldDispatch.is() ) + xOldDispatch->removeStatusListener( xStatusListener, aTargetURL ); + } + catch (const Exception&) + { + } + } + else + m_aListenerMap.emplace( aCommandURL, xDispatch ); + } + } + + // Call without locked mutex as we are called back from dispatch implementation + try + { + if ( xDispatch.is() ) + xDispatch->addStatusListener( xStatusListener, aTargetURL ); + } + catch (const Exception&) + { + } +} + + +void FrameStatusListener::bindListener() +{ + std::vector< Listener > aDispatchVector; + Reference< XStatusListener > xStatusListener; + + { + SolarMutexGuard aSolarMutexGuard; + + // Collect all registered command URL's and store them temporary + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + if ( m_xContext.is() && xDispatchProvider.is() ) + { + xStatusListener = this; + for (auto & listener : m_aListenerMap) + { + Reference< XURLTransformer > xURLTransformer( css::util::URLTransformer::create( m_xContext ) ); + css::util::URL aTargetURL; + aTargetURL.Complete = listener.first; + xURLTransformer->parseStrict( aTargetURL ); + + Reference< XDispatch > xDispatch( listener.second ); + if ( xDispatch.is() ) + { + // We already have a dispatch object => we have to requery. + // Release old dispatch object and remove it as listener + try + { + xDispatch->removeStatusListener( xStatusListener, aTargetURL ); + } + catch (const Exception&) + { + } + } + + // Query for dispatch object. Old dispatch will be released with this, too. + try + { + xDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + } + catch (const Exception&) + { + } + listener.second = xDispatch; + + Listener aListener( aTargetURL, xDispatch ); + aDispatchVector.push_back( aListener ); + } + } + } + + // Call without locked mutex as we are called back from dispatch implementation + if ( !xStatusListener.is() ) + return; + + try + { + for (Listener & rListener : aDispatchVector) + { + if ( rListener.xDispatch.is() ) + rListener.xDispatch->addStatusListener( xStatusListener, rListener.aURL ); + } + } + catch (const Exception&) + { + } +} + +} // svt + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/genericunodialog.cxx b/svtools/source/uno/genericunodialog.cxx new file mode 100644 index 000000000..b6a493654 --- /dev/null +++ b/svtools/source/uno/genericunodialog.cxx @@ -0,0 +1,263 @@ +/* -*- 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 <svtools/genericunodialog.hxx> + +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/ucb/AlreadyInitializedException.hpp> + +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <osl/diagnose.h> +#include <tools/diagnose_ex.h> +#include <osl/mutex.hxx> +#include <vcl/svapp.hxx> + +using namespace css::uno; +using namespace css::lang; +using namespace css::beans; +using namespace css::ucb; + + +namespace svt +{ + + +OGenericUnoDialog::OGenericUnoDialog(const Reference< XComponentContext >& _rxContext) + :OPropertyContainer(GetBroadcastHelper()) + ,m_bExecuting(false) + ,m_bTitleAmbiguous(true) + ,m_bInitialized( false ) + ,m_aContext(_rxContext) +{ + registerProperty(UNODIALOG_PROPERTY_TITLE, UNODIALOG_PROPERTY_ID_TITLE, PropertyAttribute::TRANSIENT, + &m_sTitle, cppu::UnoType<decltype(m_sTitle)>::get()); + registerProperty(UNODIALOG_PROPERTY_PARENT, UNODIALOG_PROPERTY_ID_PARENT, PropertyAttribute::TRANSIENT, + &m_xParent, cppu::UnoType<decltype(m_xParent)>::get()); +} + + +OGenericUnoDialog::~OGenericUnoDialog() +{ + if (m_xDialog) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + if (m_xDialog) + destroyDialog(); + } +} + + +Any SAL_CALL OGenericUnoDialog::queryInterface(const Type& _rType) +{ + Any aReturn = OGenericUnoDialogBase::queryInterface(_rType); + + if (!aReturn.hasValue()) + aReturn = ::cppu::queryInterface(_rType + ,static_cast<XPropertySet*>(this) + ,static_cast<XMultiPropertySet*>(this) + ,static_cast<XFastPropertySet*>(this) + ); + + return aReturn; +} + + +Sequence<Type> SAL_CALL OGenericUnoDialog::getTypes( ) +{ + return ::comphelper::concatSequences( + OGenericUnoDialogBase::getTypes(), + getBaseTypes() + ); +} + +sal_Bool SAL_CALL OGenericUnoDialog::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + + +void OGenericUnoDialog::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, const Any& rValue ) +{ + // TODO: need some handling if we're currently executing ... + + OPropertyContainer::setFastPropertyValue_NoBroadcast(nHandle, rValue); + + if (UNODIALOG_PROPERTY_ID_TITLE == nHandle) + { + // from now on m_sTitle is valid + m_bTitleAmbiguous = false; + + if (m_xDialog) + m_xDialog->set_title(m_sTitle); + } +} + + +sal_Bool OGenericUnoDialog::convertFastPropertyValue( Any& rConvertedValue, Any& rOldValue, sal_Int32 nHandle, const Any& rValue) +{ + switch (nHandle) + { + case UNODIALOG_PROPERTY_ID_PARENT: + { + Reference<css::awt::XWindow> xNew(rValue, css::uno::UNO_QUERY); + if (xNew != m_xParent) + { + rConvertedValue <<= xNew; + rOldValue <<= m_xParent; + return true; + } + return false; + } + } + return OPropertyContainer::convertFastPropertyValue(rConvertedValue, rOldValue, nHandle, rValue); +} + + +void SAL_CALL OGenericUnoDialog::setTitle( const OUString& _rTitle ) +{ + UnoDialogEntryGuard aGuard( *this ); + + try + { + setPropertyValue(UNODIALOG_PROPERTY_TITLE, Any(_rTitle)); + } + catch(RuntimeException&) + { + // allowed to pass + throw; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + // not allowed to pass + } +} + + +bool OGenericUnoDialog::impl_ensureDialog_lck() +{ + if (m_xDialog) + return true; + + // get the parameters for the dialog from the current settings + + // the title + OUString sTitle = m_sTitle; + + auto xDialog(createDialog(m_xParent)); + OSL_ENSURE(xDialog, "OGenericUnoDialog::impl_ensureDialog_lck: createDialog returned nonsense!"); + if (!xDialog) + return false; + + // do some initialisations + if (!m_bTitleAmbiguous) + xDialog->set_title(sTitle); + + m_xDialog = std::move(xDialog); + + return true; +} + +sal_Int16 SAL_CALL OGenericUnoDialog::execute() +{ + // both creation and execution of the dialog must be guarded with the SolarMutex, so be generous here + SolarMutexGuard aSolarGuard; + + // create the dialog, if necessary + { + UnoDialogEntryGuard aGuard( *this ); + + if (m_bExecuting) + throw RuntimeException( + "already executing the dialog (recursive call)", + *this + ); + + m_bExecuting = true; + + if ( !impl_ensureDialog_lck() ) + return 0; + } + + // start execution + sal_Int16 nReturn(0); + if (m_xDialog) + nReturn = m_xDialog->run(); + + { + ::osl::MutexGuard aGuard(m_aMutex); + + // get the settings of the dialog + executedDialog( nReturn ); + + m_bExecuting = false; + } + + // outta here + return nReturn; +} + +void OGenericUnoDialog::implInitialize(const Any& _rValue) +{ + try + { + PropertyValue aProperty; + NamedValue aValue; + if ( _rValue >>= aProperty ) + { + setPropertyValue( aProperty.Name, aProperty.Value ); + } + else if ( _rValue >>= aValue ) + { + setPropertyValue( aValue.Name, aValue.Value ); + } + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } +} + +void SAL_CALL OGenericUnoDialog::initialize( const Sequence< Any >& aArguments ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if ( m_bInitialized ) + throw AlreadyInitializedException( OUString(), *this ); + + for (const Any& rArgument : aArguments) + implInitialize(rArgument); + + m_bInitialized = true; +} + +void OGenericUnoDialog::destroyDialog() +{ + SolarMutexGuard aSolarGuard; + m_xDialog.reset(); +} + +} // namespace svt + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/miscservices.cxx b/svtools/source/uno/miscservices.cxx new file mode 100644 index 000000000..625b336c5 --- /dev/null +++ b/svtools/source/uno/miscservices.cxx @@ -0,0 +1,77 @@ +/* -*- 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/types.h> + +#include <cppuhelper/factory.hxx> +#include <cppuhelper/implementationentry.hxx> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> + +#include "fpicker.hxx" + +using namespace ::com::sun::star; +using namespace css::uno; +using namespace css::lang; + +namespace +{ + const struct ::cppu::ImplementationEntry s_aServiceEntries[] = + { + { + // FilePicker should not use a constructor, it is only a + // trampoline to a real impl. + FilePicker_CreateInstance, + FilePicker_getImplementationName, + FilePicker_getSupportedServiceNames, + ::cppu::createSingleComponentFactory, nullptr, 0 + }, + { + // FolderPicker should not use a constructor, it is only a + // trampoline to a real impl. + FolderPicker_CreateInstance, + FolderPicker_getImplementationName, + FolderPicker_getSupportedServiceNames, + ::cppu::createSingleComponentFactory, nullptr, 0 + }, + { nullptr, nullptr, nullptr, nullptr, nullptr, 0 } + }; +} + +extern "C" +{ + +SAL_DLLPUBLIC_EXPORT void * svt_component_getFactory( + const char * pImplementationName, void * _pServiceManager, void * pRegistryKey) +{ + void * pResult = nullptr; + if (_pServiceManager) + { + Reference< XMultiServiceFactory > xHoldAlive(static_cast< XMultiServiceFactory * >(_pServiceManager)); + + pResult = cppu::component_getFactoryHelper(pImplementationName, + _pServiceManager, + pRegistryKey, + s_aServiceEntries); + } + return pResult; +} + +} // extern "C" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/popupmenucontrollerbase.cxx b/svtools/source/uno/popupmenucontrollerbase.cxx new file mode 100644 index 000000000..3e5d47c36 --- /dev/null +++ b/svtools/source/uno/popupmenucontrollerbase.cxx @@ -0,0 +1,363 @@ +/* -*- 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 <svtools/popupmenucontrollerbase.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/util/URLTransformer.hpp> + +#include <vcl/svapp.hxx> +#include <osl/mutex.hxx> +#include <cppuhelper/supportsservice.hxx> + +using namespace com::sun::star; +using namespace css::uno; +using namespace css::lang; +using namespace css::frame; +using namespace css::beans; +using namespace css::util; + +namespace svt +{ + +namespace { + +struct PopupMenuControllerBaseDispatchInfo +{ + Reference< XDispatch > mxDispatch; + const URL maURL; + const Sequence< PropertyValue > maArgs; + + PopupMenuControllerBaseDispatchInfo( const Reference< XDispatch >& xDispatch, const URL& rURL, const Sequence< PropertyValue >& rArgs ) + : mxDispatch( xDispatch ), maURL( rURL ), maArgs( rArgs ) {} +}; + +} + +PopupMenuControllerBase::PopupMenuControllerBase( const Reference< XComponentContext >& xContext ) : + ::cppu::BaseMutex(), + PopupMenuControllerBaseType(m_aMutex), + m_bInitialized( false ) +{ + if ( xContext.is() ) + m_xURLTransformer.set( util::URLTransformer::create( xContext ) ); +} + +PopupMenuControllerBase::~PopupMenuControllerBase() +{ +} + +// protected function +void PopupMenuControllerBase::throwIfDisposed() +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + throw css::lang::DisposedException(); +} + +// protected function +void PopupMenuControllerBase::resetPopupMenu( css::uno::Reference< css::awt::XPopupMenu > const & rPopupMenu ) +{ + if ( rPopupMenu.is() && rPopupMenu->getItemCount() > 0 ) + { + rPopupMenu->clear(); + } +} + +void SAL_CALL PopupMenuControllerBase::disposing() +{ + // Reset our members and set disposed flag + osl::MutexGuard aLock( m_aMutex ); + m_xFrame.clear(); + m_xDispatch.clear(); + m_xPopupMenu.clear(); +} + +// XServiceInfo +sal_Bool SAL_CALL PopupMenuControllerBase::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +// XEventListener +void SAL_CALL PopupMenuControllerBase::disposing( const EventObject& ) +{ + osl::MutexGuard aLock( m_aMutex ); + m_xFrame.clear(); + m_xDispatch.clear(); + m_xPopupMenu.clear(); +} + +// XMenuListener +void SAL_CALL PopupMenuControllerBase::itemHighlighted( const awt::MenuEvent& ) +{ +} + +void SAL_CALL PopupMenuControllerBase::itemSelected( const awt::MenuEvent& rEvent ) +{ + throwIfDisposed(); + + osl::MutexGuard aLock( m_aMutex ); + + if( m_xPopupMenu.is() ) + { + Sequence<PropertyValue> aArgs; + dispatchCommand( m_xPopupMenu->getCommand( rEvent.MenuId ), aArgs ); + } +} + +void PopupMenuControllerBase::dispatchCommand( const OUString& sCommandURL, + const css::uno::Sequence< css::beans::PropertyValue >& rArgs, + const OUString& sTarget ) +{ + osl::MutexGuard aLock( m_aMutex ); + + throwIfDisposed(); + + try + { + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY_THROW ); + URL aURL; + aURL.Complete = sCommandURL; + m_xURLTransformer->parseStrict( aURL ); + + Reference< XDispatch > xDispatch( xDispatchProvider->queryDispatch( aURL, sTarget, 0 ), UNO_SET_THROW ); + + Application::PostUserEvent( LINK(nullptr, PopupMenuControllerBase, ExecuteHdl_Impl), new PopupMenuControllerBaseDispatchInfo( xDispatch, aURL, rArgs ) ); + + } + catch( Exception& ) + { + } + +} + +IMPL_STATIC_LINK( PopupMenuControllerBase, ExecuteHdl_Impl, void*, p, void ) +{ + PopupMenuControllerBaseDispatchInfo* pDispatchInfo = static_cast<PopupMenuControllerBaseDispatchInfo*>(p); + pDispatchInfo->mxDispatch->dispatch( pDispatchInfo->maURL, pDispatchInfo->maArgs ); + delete pDispatchInfo; +} + +void SAL_CALL PopupMenuControllerBase::itemActivated( const awt::MenuEvent& ) +{ +} + +void SAL_CALL PopupMenuControllerBase::itemDeactivated( const awt::MenuEvent& ) +{ +} + +void SAL_CALL PopupMenuControllerBase::updatePopupMenu() +{ + { + osl::MutexGuard aLock(m_aMutex); + throwIfDisposed(); + } + + updateCommand( m_aCommandURL ); +} + +void PopupMenuControllerBase::updateCommand( const OUString& rCommandURL ) +{ + osl::ClearableMutexGuard aLock( m_aMutex ); + Reference< XStatusListener > xStatusListener(this); + Reference< XDispatch > xDispatch( m_xDispatch ); + URL aTargetURL; + aTargetURL.Complete = rCommandURL; + m_xURLTransformer->parseStrict( aTargetURL ); + aLock.clear(); + + // Add/remove status listener to get a status update once + if ( xDispatch.is() ) + { + xDispatch->addStatusListener( xStatusListener, aTargetURL ); + xDispatch->removeStatusListener( xStatusListener, aTargetURL ); + } +} + + +// XDispatchProvider +Reference< XDispatch > SAL_CALL +PopupMenuControllerBase::queryDispatch( + const URL& /*aURL*/, + const OUString& /*sTarget*/, + sal_Int32 /*nFlags*/ ) +{ + // must be implemented by subclass + osl::MutexGuard aLock( m_aMutex ); + throwIfDisposed(); + + return Reference< XDispatch >(); +} + +Sequence< Reference< XDispatch > > SAL_CALL PopupMenuControllerBase::queryDispatches( const Sequence< DispatchDescriptor >& lDescriptor ) +{ + // Create return list - which must have same size then the given descriptor + // It's not allowed to pack it! + { + osl::MutexGuard aLock(m_aMutex); + throwIfDisposed(); + } + + sal_Int32 nCount = lDescriptor.getLength(); + uno::Sequence< uno::Reference< frame::XDispatch > > lDispatcher( nCount ); + + // Step over all descriptors and try to get any dispatcher for it. + std::transform(lDescriptor.begin(), lDescriptor.end(), lDispatcher.getArray(), + [this](const DispatchDescriptor& rDesc) -> uno::Reference< frame::XDispatch > { + return queryDispatch(rDesc.FeatureURL, rDesc.FrameName, rDesc.SearchFlags); }); + + return lDispatcher; +} + +// XDispatch +void SAL_CALL +PopupMenuControllerBase::dispatch( + const URL& /*aURL*/, + const Sequence< PropertyValue >& /*seqProperties*/ ) +{ + // must be implemented by subclass + osl::MutexGuard aLock( m_aMutex ); + throwIfDisposed(); +} + +void SAL_CALL +PopupMenuControllerBase::addStatusListener( + const Reference< XStatusListener >& xControl, + const URL& aURL ) +{ + osl::ResettableMutexGuard aLock( m_aMutex ); + throwIfDisposed(); + aLock.clear(); + + bool bStatusUpdate( false ); + rBHelper.addListener( cppu::UnoType<decltype(xControl)>::get(), xControl ); + + aLock.reset(); + if ( aURL.Complete.startsWith( m_aBaseURL ) ) + bStatusUpdate = true; + aLock.clear(); + + if ( bStatusUpdate ) + { + // Dummy update for popup menu controllers + FeatureStateEvent aEvent; + aEvent.FeatureURL = aURL; + aEvent.IsEnabled = true; + aEvent.Requery = false; + aEvent.State = Any(); + xControl->statusChanged( aEvent ); + } +} + +void SAL_CALL PopupMenuControllerBase::removeStatusListener( + const Reference< XStatusListener >& xControl, + const URL& /*aURL*/ ) +{ + rBHelper.removeListener( cppu::UnoType<decltype(xControl)>::get(), xControl ); +} + +OUString PopupMenuControllerBase::determineBaseURL( const OUString& aURL ) +{ + // Just use the main part of the URL for popup menu controllers + sal_Int32 nSchemePart( 0 ); + OUString aMainURL( "vnd.sun.star.popup:" ); + + nSchemePart = aURL.indexOf( ':' ); + if (( nSchemePart > 0 ) && + ( aURL.getLength() > ( nSchemePart+1 ))) + { + sal_Int32 nQueryPart = aURL.indexOf( '?', nSchemePart ); + if ( nQueryPart > 0 ) + aMainURL += aURL.subView( nSchemePart, nQueryPart-nSchemePart ); + else if ( nQueryPart == -1 ) + aMainURL += aURL.subView( nSchemePart+1 ); + } + + return aMainURL; +} + +// XInitialization +void SAL_CALL PopupMenuControllerBase::initialize( const Sequence< Any >& aArguments ) +{ + osl::MutexGuard aLock( m_aMutex ); + + bool bInitialized( m_bInitialized ); + if ( bInitialized ) + return; + + PropertyValue aPropValue; + OUString aCommandURL; + Reference< XFrame > xFrame; + + for ( const auto& rArgument : aArguments ) + { + if ( rArgument >>= aPropValue ) + { + if ( aPropValue.Name == "Frame" ) + aPropValue.Value >>= xFrame; + else if ( aPropValue.Name == "CommandURL" ) + aPropValue.Value >>= aCommandURL; + else if ( aPropValue.Name == "ModuleIdentifier" ) + aPropValue.Value >>= m_aModuleName; + } + } + + if ( xFrame.is() && !aCommandURL.isEmpty() ) + { + m_xFrame = xFrame; + m_aCommandURL = aCommandURL; + m_aBaseURL = determineBaseURL( aCommandURL ); + m_bInitialized = true; + } +} +// XPopupMenuController +void SAL_CALL PopupMenuControllerBase::setPopupMenu( const Reference< awt::XPopupMenu >& xPopupMenu ) +{ + osl::MutexGuard aLock( m_aMutex ); + throwIfDisposed(); + + if ( !m_xFrame.is() || m_xPopupMenu.is() ) + return; + + // Create popup menu on demand + SolarMutexGuard aSolarMutexGuard; + + m_xPopupMenu = xPopupMenu; + m_xPopupMenu->addMenuListener( Reference< awt::XMenuListener >(this) ); + + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + + URL aTargetURL; + aTargetURL.Complete = m_aCommandURL; + m_xURLTransformer->parseStrict( aTargetURL ); + m_xDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + + impl_setPopupMenu(); + + updatePopupMenu(); +} +void PopupMenuControllerBase::impl_setPopupMenu() +{ +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/popupwindowcontroller.cxx b/svtools/source/uno/popupwindowcontroller.cxx new file mode 100644 index 000000000..83419b4d9 --- /dev/null +++ b/svtools/source/uno/popupwindowcontroller.cxx @@ -0,0 +1,270 @@ +/* -*- 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 <cppuhelper/supportsservice.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> + +#include <svtools/popupwindowcontroller.hxx> +#include <svtools/toolbarmenu.hxx> + +using namespace ::com::sun::star; +using namespace css::uno; +using namespace css::lang; + + +namespace svt +{ + +class PopupWindowControllerImpl +{ +public: + PopupWindowControllerImpl(); + ~PopupWindowControllerImpl() COVERITY_NOEXCEPT_FALSE; + + void SetPopupWindow( vcl::Window* pPopupWindow, ToolBox* pToolBox ); + void SetFloatingWindow(); + DECL_LINK( WindowEventListener, VclWindowEvent&, void ); + +private: + VclPtr<vcl::Window> mpPopupWindow, mpFloatingWindow; + VclPtr<ToolBox> mpToolBox; +}; + +PopupWindowControllerImpl::PopupWindowControllerImpl() +{ +} + +PopupWindowControllerImpl::~PopupWindowControllerImpl() COVERITY_NOEXCEPT_FALSE +{ + SetPopupWindow(nullptr,nullptr); + SetFloatingWindow(); +} + +void PopupWindowControllerImpl::SetPopupWindow( vcl::Window* pPopupWindow, ToolBox* pToolBox ) +{ + if( mpPopupWindow ) + { + mpPopupWindow->RemoveEventListener( LINK( this, PopupWindowControllerImpl, WindowEventListener ) ); + mpPopupWindow.disposeAndClear(); + } + mpPopupWindow = pPopupWindow; + mpToolBox = pToolBox; + + if( mpPopupWindow ) + { + mpPopupWindow->AddEventListener( LINK( this, PopupWindowControllerImpl, WindowEventListener )); + } +} + +void PopupWindowControllerImpl::SetFloatingWindow() +{ + if( mpFloatingWindow ) + { + mpFloatingWindow->RemoveEventListener( LINK( this, PopupWindowControllerImpl, WindowEventListener ) ); + mpFloatingWindow.disposeAndClear(); + } + mpFloatingWindow = mpPopupWindow; + mpPopupWindow.clear(); +} + +IMPL_LINK( PopupWindowControllerImpl, WindowEventListener, VclWindowEvent&, rWindowEvent, void ) +{ + switch( rWindowEvent.GetId() ) + { + case VclEventId::WindowEndPopupMode: + { + EndPopupModeData* pData = static_cast< EndPopupModeData* >( rWindowEvent.GetData() ); + if( pData && pData->mbTearoff ) + { + vcl::Window::GetDockingManager()->SetFloatingMode( mpPopupWindow.get(), true ); + vcl::Window::GetDockingManager()->SetPosSizePixel( mpPopupWindow.get(), + pData->maFloatingPos.X(), + pData->maFloatingPos.Y(), + 0, 0, + PosSizeFlags::Pos ); + SetFloatingWindow(); + mpFloatingWindow->Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate ); + } + SetPopupWindow(nullptr,nullptr); + break; + } + case VclEventId::WindowPrepareToggleFloating: + { + if ( mpFloatingWindow && rWindowEvent.GetWindow() == mpFloatingWindow.get() ) + { + bool* pData = static_cast< bool* >( rWindowEvent.GetData() ); + *pData = false; + } + break; + } + case VclEventId::WindowClose: + { + SetPopupWindow(nullptr,nullptr); + SetFloatingWindow(); + break; + } + case VclEventId::WindowShow: + { + if( mpPopupWindow ) + { + if( mpToolBox ) + mpToolBox->CallEventListeners( VclEventId::DropdownOpen, static_cast<void*>(mpPopupWindow) ); + mpPopupWindow->CallEventListeners( VclEventId::WindowGetFocus ); + break; + } + break; + } + case VclEventId::WindowHide: + { + if( mpPopupWindow ) + { + mpPopupWindow->CallEventListeners( VclEventId::WindowLoseFocus ); + if( mpToolBox ) + mpToolBox->CallEventListeners( VclEventId::DropdownClose, static_cast<void*>(mpPopupWindow) ); + } + break; + } + default: break; + } +} + + + + +PopupWindowController::PopupWindowController( const Reference< uno::XComponentContext >& rxContext, + const Reference< frame::XFrame >& xFrame, + const OUString& aCommandURL ) +: PopupWindowController_Base( rxContext, xFrame, aCommandURL ) +, mxImpl( new PopupWindowControllerImpl() ) +{ +} + +PopupWindowController::~PopupWindowController() +{ +} + +// XServiceInfo +sal_Bool SAL_CALL PopupWindowController::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +// XComponent +void SAL_CALL PopupWindowController::dispose() +{ + mxInterimPopover.clear(); + mxPopoverContainer.reset(); + mxImpl.reset(); + svt::ToolboxController::dispose(); +} + +// XStatusListener +void SAL_CALL PopupWindowController::statusChanged( const frame::FeatureStateEvent& rEvent ) +{ + SolarMutexGuard aSolarLock; + + bool bValue = false; + rEvent.State >>= bValue; + + if (m_pToolbar) + { + OString sId = m_aCommandURL.toUtf8(); + m_pToolbar->set_item_active(sId, bValue); + m_pToolbar->set_item_sensitive(sId, rEvent.IsEnabled); + return; + } + + ToolBox* pToolBox = nullptr; + ToolBoxItemId nItemId; + if ( getToolboxId( nItemId, &pToolBox ) ) + { + pToolBox->CheckItem( nItemId, bValue ); + pToolBox->EnableItem( nItemId, rEvent.IsEnabled ); + } +} + +VclPtr<vcl::Window> PopupWindowController::createVclPopupWindow(vcl::Window* /*pParent*/) +{ + return nullptr; +} + +Reference< awt::XWindow > SAL_CALL PopupWindowController::createPopupWindow() +{ + if (m_pToolbar) + { + mxPopoverContainer->unsetPopover(); + mxPopoverContainer->setPopover(weldPopupWindow()); + return Reference<awt::XWindow>(); + } + + VclPtr< ToolBox > pToolBox = dynamic_cast< ToolBox* >( VCLUnoHelper::GetWindow( getParent() ) ); + if( pToolBox ) + { + vcl::Window* pItemWindow = pToolBox->GetItemWindow( pToolBox->GetDownItemId() ); + VclPtr<vcl::Window> pWin = createVclPopupWindow( pItemWindow ? pItemWindow : pToolBox ); + if( pWin ) + { + FloatWinPopupFlags eFloatFlags = FloatWinPopupFlags::GrabFocus | + FloatWinPopupFlags::AllMouseButtonClose | + FloatWinPopupFlags::NoMouseUpClose; + + WinBits nWinBits; + if ( pWin->GetType() == WindowType::DOCKINGWINDOW ) + nWinBits = static_cast< DockingWindow* >( pWin.get() )->GetFloatStyle(); + else + nWinBits = pWin->GetStyle(); + + if ( nWinBits & ( WB_SIZEABLE | WB_CLOSEABLE ) ) + eFloatFlags |= FloatWinPopupFlags::AllowTearOff; + + pWin->EnableDocking(); + mxImpl->SetPopupWindow(pWin,pToolBox); + vcl::Window::GetDockingManager()->StartPopupMode( pToolBox, pWin, eFloatFlags ); + } + } + return Reference< awt::XWindow >(); +} + +void SAL_CALL PopupWindowController::click() +{ + if (m_pToolbar) + { + if (m_pToolbar->get_menu_item_active(m_aCommandURL.toUtf8())) + createPopupWindow(); + else + mxPopoverContainer->unsetPopover(); + } + + svt::ToolboxController::click(); +} + +void PopupWindowController::EndPopupMode() +{ + if (m_pToolbar) + m_pToolbar->set_menu_item_active(m_aCommandURL.toUtf8(), false); + else if (mxInterimPopover) + mxInterimPopover->EndPopupMode(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/statusbarcontroller.cxx b/svtools/source/uno/statusbarcontroller.cxx new file mode 100644 index 000000000..fae89f287 --- /dev/null +++ b/svtools/source/uno/statusbarcontroller.cxx @@ -0,0 +1,594 @@ +/* -*- 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 <svtools/statusbarcontroller.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/ui/XStatusbarItem.hpp> +#include <cppuhelper/queryinterface.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <vcl/status.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <comphelper/processfactory.hxx> + +using namespace ::cppu; +using namespace css::awt; +using namespace css::uno; +using namespace css::util; +using namespace css::beans; +using namespace css::lang; +using namespace css::frame; + +namespace svt +{ + +StatusbarController::StatusbarController( + const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& xFrame, + const OUString& aCommandURL, + unsigned short nID ) : + OWeakObject() + , m_bInitialized( false ) + , m_bDisposed( false ) + , m_nID( nID ) + , m_xFrame( xFrame ) + , m_xContext( rxContext ) + , m_aCommandURL( aCommandURL ) + , m_aListenerContainer( m_aMutex ) +{ +} + +StatusbarController::StatusbarController() : + OWeakObject() + , m_bInitialized( false ) + , m_bDisposed( false ) + , m_nID( 0 ) + , m_aListenerContainer( m_aMutex ) +{ +} + +StatusbarController::~StatusbarController() +{ +} + +Reference< XFrame > StatusbarController::getFrameInterface() const +{ + SolarMutexGuard aSolarMutexGuard; + return m_xFrame; +} + +Reference< XURLTransformer > StatusbarController::getURLTransformer() const +{ + SolarMutexGuard aSolarMutexGuard; + if ( !m_xURLTransformer.is() && m_xContext.is() ) + { + m_xURLTransformer = css::util::URLTransformer::create( m_xContext ); + } + + return m_xURLTransformer; +} + +// XInterface +Any SAL_CALL StatusbarController::queryInterface( const Type& rType ) +{ + Any a = ::cppu::queryInterface( + rType , + static_cast< XStatusbarController* >( this ), + static_cast< XStatusListener* >( this ), + static_cast< XEventListener* >( this ), + static_cast< XInitialization* >( this ), + static_cast< XComponent* >( this ), + static_cast< XUpdatable* >( this )); + + if ( a.hasValue() ) + return a; + + return OWeakObject::queryInterface( rType ); +} + +void SAL_CALL StatusbarController::acquire() noexcept +{ + OWeakObject::acquire(); +} + +void SAL_CALL StatusbarController::release() noexcept +{ + OWeakObject::release(); +} + +void SAL_CALL StatusbarController::initialize( const Sequence< Any >& aArguments ) +{ + bool bInitialized( true ); + + { + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + bInitialized = m_bInitialized; + } + + if ( bInitialized ) + return; + + SolarMutexGuard aSolarMutexGuard; + m_bInitialized = true; + + PropertyValue aPropValue; + for ( const auto& rArgument : aArguments ) + { + if ( rArgument >>= aPropValue ) + { + if ( aPropValue.Name == "Frame" ) + aPropValue.Value >>= m_xFrame; + else if ( aPropValue.Name == "CommandURL" ) + aPropValue.Value >>= m_aCommandURL; + else if ( aPropValue.Name == "ServiceManager" ) + { + Reference<XMultiServiceFactory> xMSF; + aPropValue.Value >>= xMSF; + if( xMSF.is() ) + m_xContext = comphelper::getComponentContext(xMSF); + } + else if ( aPropValue.Name == "ParentWindow" ) + aPropValue.Value >>= m_xParentWindow; + else if ( aPropValue.Name == "Identifier" ) + aPropValue.Value >>= m_nID; + else if ( aPropValue.Name == "StatusbarItem" ) + aPropValue.Value >>= m_xStatusbarItem; + } + } + + if ( !m_aCommandURL.isEmpty() ) + m_aListenerMap.emplace( m_aCommandURL, Reference< XDispatch >() ); +} + +void SAL_CALL StatusbarController::update() +{ + { + SolarMutexGuard aSolarMutexGuard; + if ( m_bDisposed ) + throw DisposedException(); + } + + // Bind all registered listeners to their dispatch objects + bindListener(); +} + +// XComponent +void SAL_CALL StatusbarController::dispose() +{ + Reference< XComponent > xThis = this; + + { + SolarMutexGuard aSolarMutexGuard; + if ( m_bDisposed ) + throw DisposedException(); + } + + css::lang::EventObject aEvent( xThis ); + m_aListenerContainer.disposeAndClear( aEvent ); + + SolarMutexGuard aSolarMutexGuard; + Reference< XStatusListener > xStatusListener = this; + Reference< XURLTransformer > xURLTransformer = getURLTransformer(); + css::util::URL aTargetURL; + for (auto const& listener : m_aListenerMap) + { + try + { + Reference< XDispatch > xDispatch(listener.second); + aTargetURL.Complete = listener.first; + xURLTransformer->parseStrict( aTargetURL ); + + if ( xDispatch.is() && xStatusListener.is() ) + xDispatch->removeStatusListener( xStatusListener, aTargetURL ); + } + catch ( Exception& ) + { + } + } + + // clear hash map + m_aListenerMap.clear(); + + // release references + m_xURLTransformer.clear(); + m_xContext.clear(); + m_xFrame.clear(); + m_xParentWindow.clear(); + m_xStatusbarItem.clear(); + + m_bDisposed = true; +} + +void SAL_CALL StatusbarController::addEventListener( const Reference< XEventListener >& xListener ) +{ + m_aListenerContainer.addInterface( cppu::UnoType<XEventListener>::get(), xListener ); +} + +void SAL_CALL StatusbarController::removeEventListener( const Reference< XEventListener >& aListener ) +{ + m_aListenerContainer.removeInterface( cppu::UnoType<XEventListener>::get(), aListener ); +} + +// XEventListener +void SAL_CALL StatusbarController::disposing( const EventObject& Source ) +{ + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + return; + + Reference< XFrame > xFrame( Source.Source, UNO_QUERY ); + if ( xFrame.is() ) + { + if ( xFrame == m_xFrame ) + m_xFrame.clear(); + return; + } + + Reference< XDispatch > xDispatch( Source.Source, UNO_QUERY ); + if ( !xDispatch.is() ) + return; + + for (auto & listener : m_aListenerMap) + { + // Compare references and release dispatch references if they are equal. + if ( xDispatch == listener.second ) + listener.second.clear(); + } +} + +// XStatusListener +void SAL_CALL StatusbarController::statusChanged( const FeatureStateEvent& Event ) +{ + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + return; + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( m_xParentWindow ); + if ( pWindow && pWindow->GetType() == WindowType::STATUSBAR && m_nID != 0 ) + { + OUString aStrValue; + StatusBar* pStatusBar = static_cast<StatusBar *>(pWindow.get()); + + if ( Event.State >>= aStrValue ) + pStatusBar->SetItemText( m_nID, aStrValue ); + else if ( !Event.State.hasValue() ) + pStatusBar->SetItemText( m_nID, "" ); + } +} + +// XStatusbarController +sal_Bool SAL_CALL StatusbarController::mouseButtonDown( + const css::awt::MouseEvent& ) +{ + return false; +} + +sal_Bool SAL_CALL StatusbarController::mouseMove( + const css::awt::MouseEvent& ) +{ + return false; +} + +sal_Bool SAL_CALL StatusbarController::mouseButtonUp( + const css::awt::MouseEvent& ) +{ + return false; +} + +void SAL_CALL StatusbarController::command( + const css::awt::Point&, + ::sal_Int32, + sal_Bool, + const css::uno::Any& ) +{ +} + +void SAL_CALL StatusbarController::paint( + const css::uno::Reference< css::awt::XGraphics >&, + const css::awt::Rectangle&, + ::sal_Int32 ) +{ +} + +void SAL_CALL StatusbarController::click( const css::awt::Point& ) +{ +} + +void SAL_CALL StatusbarController::doubleClick( const css::awt::Point& ) +{ + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + return; + + Sequence< PropertyValue > aArgs; + execute( aArgs ); +} + +void StatusbarController::addStatusListener( const OUString& aCommandURL ) +{ + Reference< XDispatch > xDispatch; + Reference< XStatusListener > xStatusListener; + css::util::URL aTargetURL; + + { + SolarMutexGuard aSolarMutexGuard; + URLToDispatchMap::iterator pIter = m_aListenerMap.find( aCommandURL ); + + // Already in the list of status listener. Do nothing. + if ( pIter != m_aListenerMap.end() ) + return; + + // Check if we are already initialized. Implementation starts adding itself as status listener when + // initialize is called. + if ( !m_bInitialized ) + { + // Put into the unordered_map of status listener. Will be activated when initialized is called + m_aListenerMap.emplace( aCommandURL, Reference< XDispatch >() ); + return; + } + else + { + // Add status listener directly as initialize has already been called. + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + if ( m_xContext.is() && xDispatchProvider.is() ) + { + Reference< XURLTransformer > xURLTransformer = getURLTransformer(); + aTargetURL.Complete = aCommandURL; + xURLTransformer->parseStrict( aTargetURL ); + xDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + + xStatusListener = this; + URLToDispatchMap::iterator aIter = m_aListenerMap.find( aCommandURL ); + if ( aIter != m_aListenerMap.end() ) + { + Reference< XDispatch > xOldDispatch( aIter->second ); + aIter->second = xDispatch; + + try + { + if ( xOldDispatch.is() ) + xOldDispatch->removeStatusListener( xStatusListener, aTargetURL ); + } + catch ( Exception& ) + { + } + } + else + m_aListenerMap.emplace( aCommandURL, xDispatch ); + } + } + } + + // Call without locked mutex as we are called back from dispatch implementation + try + { + if ( xDispatch.is() ) + xDispatch->addStatusListener( xStatusListener, aTargetURL ); + } + catch ( Exception& ) + { + } +} + +void StatusbarController::bindListener() +{ + std::vector< Listener > aDispatchVector; + Reference< XStatusListener > xStatusListener; + + { + SolarMutexGuard aSolarMutexGuard; + + if ( !m_bInitialized ) + return; + + // Collect all registered command URL's and store them temporary + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + if ( m_xContext.is() && xDispatchProvider.is() ) + { + xStatusListener = this; + for (auto & listener : m_aListenerMap) + { + Reference< XURLTransformer > xURLTransformer = getURLTransformer(); + css::util::URL aTargetURL; + aTargetURL.Complete = listener.first; + xURLTransformer->parseStrict( aTargetURL ); + + Reference< XDispatch > xDispatch(listener.second); + if ( xDispatch.is() ) + { + // We already have a dispatch object => we have to requery. + // Release old dispatch object and remove it as listener + try + { + xDispatch->removeStatusListener( xStatusListener, aTargetURL ); + } + catch ( Exception& ) + { + } + } + + listener.second.clear(); + xDispatch.clear(); + + // Query for dispatch object. Old dispatch will be released with this, too. + try + { + xDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + } + catch ( Exception& ) + { + } + listener.second = xDispatch; + + Listener aListener( aTargetURL, xDispatch ); + aDispatchVector.push_back( aListener ); + } + } + } + + // Call without locked mutex as we are called back from dispatch implementation + if ( !xStatusListener.is() ) + return; + + for (Listener & rListener : aDispatchVector) + { + try + { + if ( rListener.xDispatch.is() ) + rListener.xDispatch->addStatusListener( xStatusListener, rListener.aURL ); + else if ( rListener.aURL.Complete == m_aCommandURL ) + { + // Send status changed for the main URL, if we cannot get a valid dispatch object. + // UI disables the button. Catch exception as we release our mutex, it is possible + // that someone else already disposed this instance! + FeatureStateEvent aFeatureStateEvent; + aFeatureStateEvent.IsEnabled = false; + aFeatureStateEvent.FeatureURL = rListener.aURL; + aFeatureStateEvent.State = Any(); + xStatusListener->statusChanged( aFeatureStateEvent ); + } + } + catch ( ... ){} + } +} + +::tools::Rectangle StatusbarController::getControlRect() const +{ + ::tools::Rectangle aRect; + + { + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( m_xParentWindow.is() ) + { + VclPtr< StatusBar > pStatusBar = dynamic_cast< StatusBar* >( VCLUnoHelper::GetWindow( m_xParentWindow ) ); + if ( pStatusBar && pStatusBar->GetType() == WindowType::STATUSBAR ) + aRect = pStatusBar->GetItemRect( m_nID ); + } + } + + return aRect; +} + +void StatusbarController::execute( const css::uno::Sequence< css::beans::PropertyValue >& aArgs ) +{ + Reference< XDispatch > xDispatch; + Reference< XURLTransformer > xURLTransformer; + OUString aCommandURL; + + { + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( m_bInitialized && + m_xFrame.is() && + m_xContext.is() && + !m_aCommandURL.isEmpty() ) + { + xURLTransformer = getURLTransformer(); + aCommandURL = m_aCommandURL; + URLToDispatchMap::iterator pIter = m_aListenerMap.find( m_aCommandURL ); + if ( pIter != m_aListenerMap.end() ) + xDispatch = pIter->second; + } + } + + if ( !(xDispatch.is() && xURLTransformer.is()) ) + return; + + try + { + css::util::URL aTargetURL; + + aTargetURL.Complete = aCommandURL; + xURLTransformer->parseStrict( aTargetURL ); + xDispatch->dispatch( aTargetURL, aArgs ); + } + catch ( DisposedException& ) + { + } +} + +void StatusbarController::execute( + const OUString& aCommandURL, + const Sequence< css::beans::PropertyValue >& aArgs ) +{ + Reference< XDispatch > xDispatch; + css::util::URL aTargetURL; + + { + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( m_bInitialized && + m_xFrame.is() && + m_xContext.is() && + !m_aCommandURL.isEmpty() ) + { + Reference< XURLTransformer > xURLTransformer( getURLTransformer() ); + aTargetURL.Complete = aCommandURL; + xURLTransformer->parseStrict( aTargetURL ); + + URLToDispatchMap::iterator pIter = m_aListenerMap.find( aCommandURL ); + if ( pIter != m_aListenerMap.end() ) + xDispatch = pIter->second; + else + { + Reference< css::frame::XDispatchProvider > xDispatchProvider( + m_xFrame->getController(), UNO_QUERY ); + if ( xDispatchProvider.is() ) + xDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + } + } + } + + if ( xDispatch.is() ) + { + try + { + xDispatch->dispatch( aTargetURL, aArgs ); + } + catch ( DisposedException& ) + { + } + } +} + +} // svt + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/svtxgridcontrol.cxx b/svtools/source/uno/svtxgridcontrol.cxx new file mode 100644 index 000000000..4aee46f2c --- /dev/null +++ b/svtools/source/uno/svtxgridcontrol.cxx @@ -0,0 +1,909 @@ +/* -*- 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 "svtxgridcontrol.hxx" +#include <com/sun/star/view/SelectionType.hpp> +#include <table/tablecontrolinterface.hxx> +#include <table/gridtablerenderer.hxx> +#include <table/tablecontrol.hxx> +#include "unocontroltablemodel.hxx" +#include <sal/log.hxx> +#include <tools/diagnose_ex.h> +#include <toolkit/helper/property.hxx> +#include <com/sun/star/awt/grid/XGridColumn.hpp> +#include <com/sun/star/awt/grid/GridInvalidDataException.hpp> +#include <com/sun/star/awt/grid/GridInvalidModelException.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/util/Color.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> + +#include <vcl/svapp.hxx> + +#include <algorithm> + +using css::uno::Reference; +using css::uno::Exception; +using css::uno::UNO_QUERY; +using css::uno::UNO_QUERY_THROW; +using css::uno::Any; +using css::uno::Sequence; +using css::awt::grid::XGridSelectionListener; +using css::style::VerticalAlignment; +using css::style::VerticalAlignment_TOP; +using css::view::SelectionType; +using css::view::SelectionType_NONE; +using css::view::SelectionType_RANGE; +using css::view::SelectionType_SINGLE; +using css::view::SelectionType_MULTI; +using css::awt::grid::XGridDataModel; +using css::awt::grid::GridInvalidDataException; +using css::lang::EventObject; +using css::lang::IndexOutOfBoundsException; +using css::awt::grid::XGridColumnModel; +using css::awt::grid::GridSelectionEvent; +using css::awt::grid::XGridColumn; +using css::container::ContainerEvent; +using css::awt::grid::GridDataEvent; +using css::awt::grid::GridInvalidModelException; + +namespace AccessibleEventId = css::accessibility::AccessibleEventId; +namespace AccessibleStateType = css::accessibility::AccessibleStateType; + +using namespace ::svt::table; + + +SVTXGridControl::SVTXGridControl() + :m_xTableModel( std::make_shared<UnoControlTableModel>() ) + ,m_bTableModelInitCompleted( false ) + ,m_aSelectionListeners( *this ) +{ +} + + +SVTXGridControl::~SVTXGridControl() +{ +} + + +void SVTXGridControl::SetWindow( const VclPtr< vcl::Window > &pWindow ) +{ + SVTXGridControl_Base::SetWindow( pWindow ); + impl_checkTableModelInit(); +} + + +void SVTXGridControl::impl_checkColumnIndex_throw( ::svt::table::TableControl const & i_table, sal_Int32 const i_columnIndex ) const +{ + if ( ( i_columnIndex < 0 ) || ( i_columnIndex >= i_table.GetColumnCount() ) ) + throw IndexOutOfBoundsException( OUString(), *const_cast< SVTXGridControl* >( this ) ); +} + + +void SVTXGridControl::impl_checkRowIndex_throw( ::svt::table::TableControl const & i_table, sal_Int32 const i_rowIndex ) const +{ + if ( ( i_rowIndex < 0 ) || ( i_rowIndex >= i_table.GetRowCount() ) ) + throw IndexOutOfBoundsException( OUString(), *const_cast< SVTXGridControl* >( this ) ); +} + + +sal_Int32 SAL_CALL SVTXGridControl::getRowAtPoint(::sal_Int32 x, ::sal_Int32 y) +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN( pTable, "SVTXGridControl::getRowAtPoint: no control (anymore)!", -1 ); + + TableCell const tableCell = pTable->getTableControlInterface().hitTest( Point( x, y ) ); + return ( tableCell.nRow >= 0 ) ? tableCell.nRow : -1; +} + + +sal_Int32 SAL_CALL SVTXGridControl::getColumnAtPoint(::sal_Int32 x, ::sal_Int32 y) +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN( pTable, "SVTXGridControl::getColumnAtPoint: no control (anymore)!", -1 ); + + TableCell const tableCell = pTable->getTableControlInterface().hitTest( Point( x, y ) ); + return ( tableCell.nColumn >= 0 ) ? tableCell.nColumn : -1; +} + + +sal_Int32 SAL_CALL SVTXGridControl::getCurrentColumn( ) +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN( pTable, "SVTXGridControl::getCurrentColumn: no control (anymore)!", -1 ); + + sal_Int32 const nColumn = pTable->GetCurrentColumn(); + return ( nColumn >= 0 ) ? nColumn : -1; +} + + +sal_Int32 SAL_CALL SVTXGridControl::getCurrentRow( ) +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN( pTable, "SVTXGridControl::getCurrentRow: no control (anymore)!", -1 ); + + sal_Int32 const nRow = pTable->GetCurrentRow(); + return ( nRow >= 0 ) ? nRow : -1; +} + + +void SAL_CALL SVTXGridControl::goToCell( ::sal_Int32 i_columnIndex, ::sal_Int32 i_rowIndex ) +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::getCurrentRow: no control (anymore)!" ); + + impl_checkColumnIndex_throw( *pTable, i_columnIndex ); + impl_checkRowIndex_throw( *pTable, i_rowIndex ); + + pTable->GoTo( i_columnIndex, i_rowIndex ); +} + + +void SAL_CALL SVTXGridControl::addSelectionListener(const Reference< XGridSelectionListener > & listener) +{ + m_aSelectionListeners.addInterface(listener); +} + + +void SAL_CALL SVTXGridControl::removeSelectionListener(const Reference< XGridSelectionListener > & listener) +{ + m_aSelectionListeners.removeInterface(listener); +} + + +void SVTXGridControl::setProperty( const OUString& PropertyName, const Any& aValue) +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::setProperty: no control (anymore)!" ); + + switch( GetPropertyId( PropertyName ) ) + { + case BASEPROPERTY_ROW_HEADER_WIDTH: + { + sal_Int32 rowHeaderWidth( -1 ); + aValue >>= rowHeaderWidth; + if ( rowHeaderWidth <= 0 ) + { + SAL_WARN( "svtools.uno", "SVTXGridControl::setProperty: illegal row header width!" ); + break; + } + + m_xTableModel->setRowHeaderWidth( rowHeaderWidth ); + // TODO: the model should broadcast this change itself, and the table should invalidate itself as needed + pTable->Invalidate(); + } + break; + + case BASEPROPERTY_COLUMN_HEADER_HEIGHT: + { + sal_Int32 columnHeaderHeight = 0; + if ( !aValue.hasValue() ) + { + columnHeaderHeight = pTable->PixelToLogic(Size(0, pTable->GetTextHeight() + 3), MapMode(MapUnit::MapAppFont)).Height(); + } + else + { + aValue >>= columnHeaderHeight; + } + if ( columnHeaderHeight <= 0 ) + { + SAL_WARN( "svtools.uno", "SVTXGridControl::setProperty: illegal column header width!" ); + break; + } + + m_xTableModel->setColumnHeaderHeight( columnHeaderHeight ); + // TODO: the model should broadcast this change itself, and the table should invalidate itself as needed + pTable->Invalidate(); + } + break; + + case BASEPROPERTY_USE_GRID_LINES: + { + GridTableRenderer* pGridRenderer = dynamic_cast< GridTableRenderer* >( + m_xTableModel->getRenderer().get() ); + if ( !pGridRenderer ) + { + SAL_WARN( "svtools.uno", "SVTXGridControl::setProperty(UseGridLines): invalid renderer!" ); + break; + } + + bool bUseGridLines = false; + OSL_VERIFY( aValue >>= bUseGridLines ); + pGridRenderer->useGridLines( bUseGridLines ); + pTable->Invalidate(); + } + break; + + case BASEPROPERTY_ROW_HEIGHT: + { + sal_Int32 rowHeight = 0; + if ( !aValue.hasValue() ) + { + rowHeight = pTable->PixelToLogic(Size(0, pTable->GetTextHeight() + 3), MapMode(MapUnit::MapAppFont)).Height(); + } + else + { + aValue >>= rowHeight; + } + m_xTableModel->setRowHeight( rowHeight ); + if ( rowHeight <= 0 ) + { + SAL_WARN( "svtools.uno", "SVTXGridControl::setProperty: illegal row height!" ); + break; + } + + // TODO: the model should broadcast this change itself, and the table should invalidate itself as needed + pTable->Invalidate(); + } + break; + + case BASEPROPERTY_BACKGROUNDCOLOR: + { + // let the base class handle this for the TableControl + VCLXWindow::setProperty( PropertyName, aValue ); + // and forward to the grid control's data window + if ( pTable->IsBackground() ) + pTable->getDataWindow().SetBackground( pTable->GetBackground() ); + else + pTable->getDataWindow().SetBackground(); + } + break; + + case BASEPROPERTY_GRID_SELECTIONMODE: + { + SelectionType eSelectionType; + if( aValue >>= eSelectionType ) + { + SelectionMode eSelMode; + switch( eSelectionType ) + { + case SelectionType_SINGLE: eSelMode = SelectionMode::Single; break; + case SelectionType_RANGE: eSelMode = SelectionMode::Range; break; + case SelectionType_MULTI: eSelMode = SelectionMode::Multiple; break; + default: eSelMode = SelectionMode::NONE; break; + } + if( pTable->getSelEngine()->GetSelectionMode() != eSelMode ) + pTable->getSelEngine()->SetSelectionMode( eSelMode ); + } + break; + } + case BASEPROPERTY_HSCROLL: + { + bool bHScroll = true; + if( aValue >>= bHScroll ) + m_xTableModel->setHorizontalScrollbarVisibility( bHScroll ? ScrollbarShowAlways : ScrollbarShowSmart ); + break; + } + + case BASEPROPERTY_VSCROLL: + { + bool bVScroll = true; + if( aValue >>= bVScroll ) + { + m_xTableModel->setVerticalScrollbarVisibility( bVScroll ? ScrollbarShowAlways : ScrollbarShowSmart ); + } + break; + } + + case BASEPROPERTY_GRID_SHOWROWHEADER: + { + bool rowHeader = true; + if( aValue >>= rowHeader ) + { + m_xTableModel->setRowHeaders(rowHeader); + } + break; + } + + case BASEPROPERTY_GRID_ROW_BACKGROUND_COLORS: + m_xTableModel->setRowBackgroundColors( aValue ); + pTable->Invalidate(); + break; + + case BASEPROPERTY_GRID_LINE_COLOR: + m_xTableModel->setLineColor( aValue ); + pTable->Invalidate(); + break; + + case BASEPROPERTY_GRID_HEADER_BACKGROUND: + m_xTableModel->setHeaderBackgroundColor( aValue ); + pTable->Invalidate(); + break; + + case BASEPROPERTY_GRID_HEADER_TEXT_COLOR: + m_xTableModel->setHeaderTextColor( aValue ); + pTable->Invalidate(); + break; + + case BASEPROPERTY_ACTIVE_SEL_BACKGROUND_COLOR: + m_xTableModel->setActiveSelectionBackColor( aValue ); + pTable->Invalidate(); + break; + + case BASEPROPERTY_INACTIVE_SEL_BACKGROUND_COLOR: + m_xTableModel->setInactiveSelectionBackColor( aValue ); + pTable->Invalidate(); + break; + + case BASEPROPERTY_ACTIVE_SEL_TEXT_COLOR: + m_xTableModel->setActiveSelectionTextColor( aValue ); + pTable->Invalidate(); + break; + + case BASEPROPERTY_INACTIVE_SEL_TEXT_COLOR: + m_xTableModel->setInactiveSelectionTextColor( aValue ); + pTable->Invalidate(); + break; + + + case BASEPROPERTY_TEXTCOLOR: + m_xTableModel->setTextColor( aValue ); + pTable->Invalidate(); + break; + + case BASEPROPERTY_TEXTLINECOLOR: + m_xTableModel->setTextLineColor( aValue ); + pTable->Invalidate(); + break; + + case BASEPROPERTY_VERTICALALIGN: + { + VerticalAlignment eAlign( VerticalAlignment_TOP ); + if ( aValue >>= eAlign ) + m_xTableModel->setVerticalAlign( eAlign ); + break; + } + + case BASEPROPERTY_GRID_SHOWCOLUMNHEADER: + { + bool colHeader = true; + if( aValue >>= colHeader ) + { + m_xTableModel->setColumnHeaders(colHeader); + } + break; + } + case BASEPROPERTY_GRID_DATAMODEL: + { + Reference< XGridDataModel > const xDataModel( aValue, UNO_QUERY ); + if ( !xDataModel.is() ) + throw GridInvalidDataException("Invalid data model.", *this ); + + m_xTableModel->setDataModel( xDataModel ); + impl_checkTableModelInit(); + } + break; + + case BASEPROPERTY_GRID_COLUMNMODEL: + { + // obtain new col model + Reference< XGridColumnModel > const xColumnModel( aValue, UNO_QUERY ); + if ( !xColumnModel.is() ) + throw GridInvalidModelException("Invalid column model.", *this ); + + // remove all old columns + m_xTableModel->removeAllColumns(); + + // announce to the TableModel + m_xTableModel->setColumnModel( xColumnModel ); + impl_checkTableModelInit(); + + // add new columns + impl_updateColumnsFromModel_nothrow(); + break; + } + default: + VCLXWindow::setProperty( PropertyName, aValue ); + break; + } +} + + +void SVTXGridControl::impl_checkTableModelInit() +{ + if ( !(!m_bTableModelInitCompleted && m_xTableModel->hasColumnModel() && m_xTableModel->hasDataModel()) ) + return; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + if ( !pTable ) + return; + + pTable->SetModel( PTableModel( m_xTableModel ) ); + + m_bTableModelInitCompleted = true; + + // ensure default columns exist, if they have not previously been added + Reference< XGridDataModel > const xDataModel( m_xTableModel->getDataModel(), css::uno::UNO_SET_THROW ); + Reference< XGridColumnModel > const xColumnModel( m_xTableModel->getColumnModel(), css::uno::UNO_SET_THROW ); + + sal_Int32 const nDataColumnCount = xDataModel->getColumnCount(); + if ( ( nDataColumnCount > 0 ) && ( xColumnModel->getColumnCount() == 0 ) ) + xColumnModel->setDefaultColumns( nDataColumnCount ); + // this will trigger notifications, which in turn will let us update our m_xTableModel +} + +namespace +{ + void lcl_convertColor( ::std::optional< ::Color > const & i_color, Any & o_colorValue ) + { + if ( !i_color ) + o_colorValue.clear(); + else + o_colorValue <<= sal_Int32(*i_color); + } +} + +Any SVTXGridControl::getProperty( const OUString& PropertyName ) +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN( pTable, "SVTXGridControl::getProperty: no control (anymore)!", Any() ); + + Any aPropertyValue; + + const sal_uInt16 nPropId = GetPropertyId( PropertyName ); + switch(nPropId) + { + case BASEPROPERTY_GRID_SELECTIONMODE: + { + SelectionType eSelectionType; + + SelectionMode eSelMode = pTable->getSelEngine()->GetSelectionMode(); + switch( eSelMode ) + { + case SelectionMode::Single: eSelectionType = SelectionType_SINGLE; break; + case SelectionMode::Range: eSelectionType = SelectionType_RANGE; break; + case SelectionMode::Multiple:eSelectionType = SelectionType_MULTI; break; + default: eSelectionType = SelectionType_NONE; break; + } + aPropertyValue <<= eSelectionType; + break; + } + + case BASEPROPERTY_GRID_SHOWROWHEADER: + aPropertyValue <<= m_xTableModel->hasRowHeaders(); + break; + + case BASEPROPERTY_GRID_SHOWCOLUMNHEADER: + aPropertyValue <<= m_xTableModel->hasColumnHeaders(); + break; + + case BASEPROPERTY_GRID_DATAMODEL: + aPropertyValue <<= m_xTableModel->getDataModel(); + break; + + case BASEPROPERTY_GRID_COLUMNMODEL: + aPropertyValue <<= m_xTableModel->getColumnModel(); + break; + + case BASEPROPERTY_HSCROLL: + { + bool const bHasScrollbar = ( m_xTableModel->getHorizontalScrollbarVisibility() != ScrollbarShowNever ); + aPropertyValue <<= bHasScrollbar; + break; + } + + case BASEPROPERTY_VSCROLL: + { + bool const bHasScrollbar = ( m_xTableModel->getVerticalScrollbarVisibility() != ScrollbarShowNever ); + aPropertyValue <<= bHasScrollbar; + break; + } + + case BASEPROPERTY_USE_GRID_LINES: + { + GridTableRenderer* pGridRenderer = dynamic_cast< GridTableRenderer* >( + m_xTableModel->getRenderer().get() ); + if ( !pGridRenderer ) + { + SAL_WARN( "svtools.uno", "SVTXGridControl::getProperty(UseGridLines): invalid renderer!" ); + break; + } + + aPropertyValue <<= pGridRenderer->useGridLines(); + } + break; + + case BASEPROPERTY_GRID_ROW_BACKGROUND_COLORS: + { + ::std::optional< ::std::vector< ::Color > > aColors( m_xTableModel->getRowBackgroundColors() ); + if ( !aColors ) + aPropertyValue.clear(); + else + { + Sequence< css::util::Color > aAPIColors( aColors->size() ); + std::transform(aColors->begin(), aColors->end(), aAPIColors.getArray(), + [](const auto& color) { return sal_Int32(color); }); + aPropertyValue <<= aAPIColors; + } + } + break; + + case BASEPROPERTY_GRID_LINE_COLOR: + lcl_convertColor( m_xTableModel->getLineColor(), aPropertyValue ); + break; + + case BASEPROPERTY_GRID_HEADER_BACKGROUND: + lcl_convertColor( m_xTableModel->getHeaderBackgroundColor(), aPropertyValue ); + break; + + case BASEPROPERTY_GRID_HEADER_TEXT_COLOR: + lcl_convertColor( m_xTableModel->getHeaderTextColor(), aPropertyValue ); + break; + + case BASEPROPERTY_ACTIVE_SEL_BACKGROUND_COLOR: + lcl_convertColor( m_xTableModel->getActiveSelectionBackColor(), aPropertyValue ); + break; + + case BASEPROPERTY_INACTIVE_SEL_BACKGROUND_COLOR: + lcl_convertColor( m_xTableModel->getInactiveSelectionBackColor(), aPropertyValue ); + break; + + case BASEPROPERTY_ACTIVE_SEL_TEXT_COLOR: + lcl_convertColor( m_xTableModel->getActiveSelectionTextColor(), aPropertyValue ); + break; + + case BASEPROPERTY_INACTIVE_SEL_TEXT_COLOR: + lcl_convertColor( m_xTableModel->getInactiveSelectionTextColor(), aPropertyValue ); + break; + + case BASEPROPERTY_TEXTCOLOR: + lcl_convertColor( m_xTableModel->getTextColor(), aPropertyValue ); + break; + + case BASEPROPERTY_TEXTLINECOLOR: + lcl_convertColor( m_xTableModel->getTextLineColor(), aPropertyValue ); + break; + + default: + aPropertyValue = VCLXWindow::getProperty( PropertyName ); + break; + } + + return aPropertyValue; +} + + +void SAL_CALL SVTXGridControl::rowsInserted( const GridDataEvent& i_event ) +{ + SolarMutexGuard aGuard; + m_xTableModel->notifyRowsInserted( i_event ); +} + + +void SAL_CALL + SVTXGridControl::rowsRemoved( const GridDataEvent& i_event ) +{ + SolarMutexGuard aGuard; + m_xTableModel->notifyRowsRemoved( i_event ); +} + + +void SAL_CALL SVTXGridControl::dataChanged( const GridDataEvent& i_event ) +{ + SolarMutexGuard aGuard; + + m_xTableModel->notifyDataChanged( i_event ); + + // if the data model is sortable, a dataChanged event is also fired in case the sort order changed. + // So, just in case, invalidate the column header area, too. + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::dataChanged: no control (anymore)!" ); + pTable->getTableControlInterface().invalidate( TableArea::ColumnHeaders ); +} + + +void SAL_CALL SVTXGridControl::rowHeadingChanged( const GridDataEvent& ) +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::rowHeadingChanged: no control (anymore)!" ); + + // TODO: we could do better than this - invalidate the header area only + pTable->getTableControlInterface().invalidate( TableArea::RowHeaders ); +} + + +void SAL_CALL SVTXGridControl::elementInserted( const ContainerEvent& i_event ) +{ + SolarMutexGuard aGuard; + + Reference< XGridColumn > const xGridColumn( i_event.Element, UNO_QUERY_THROW ); + + sal_Int32 nIndex( m_xTableModel->getColumnCount() ); + OSL_VERIFY( i_event.Accessor >>= nIndex ); + m_xTableModel->insertColumn( nIndex, xGridColumn ); +} + + +void SAL_CALL SVTXGridControl::elementRemoved( const ContainerEvent& i_event ) +{ + SolarMutexGuard aGuard; + + sal_Int32 nIndex( -1 ); + OSL_VERIFY( i_event.Accessor >>= nIndex ); + m_xTableModel->removeColumn( nIndex ); +} + + +void SAL_CALL SVTXGridControl::elementReplaced( const ContainerEvent& ) +{ + OSL_ENSURE( false, "SVTXGridControl::elementReplaced: not implemented!" ); + // at the moment, the XGridColumnModel API does not allow replacing columns + // TODO: replace the respective column in our table model +} + + +void SAL_CALL SVTXGridControl::disposing( const EventObject& Source ) +{ + VCLXWindow::disposing( Source ); +} + + +void SAL_CALL SVTXGridControl::selectRow( ::sal_Int32 i_rowIndex ) +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::selectRow: no control (anymore)!" ); + + impl_checkRowIndex_throw( *pTable, i_rowIndex ); + + pTable->SelectRow( i_rowIndex, true ); +} + + +void SAL_CALL SVTXGridControl::selectAllRows() +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::selectAllRows: no control (anymore)!" ); + + pTable->SelectAllRows( true ); +} + + +void SAL_CALL SVTXGridControl::deselectRow( ::sal_Int32 i_rowIndex ) +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::deselectRow: no control (anymore)!" ); + + impl_checkRowIndex_throw( *pTable, i_rowIndex ); + + pTable->SelectRow( i_rowIndex, false ); +} + + +void SAL_CALL SVTXGridControl::deselectAllRows() +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::deselectAllRows: no control (anymore)!" ); + + pTable->SelectAllRows( false ); +} + + +Sequence< ::sal_Int32 > SAL_CALL SVTXGridControl::getSelectedRows() +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN( pTable, "SVTXGridControl::getSelectedRows: no control (anymore)!", Sequence< sal_Int32 >() ); + + sal_Int32 selectionCount = pTable->GetSelectedRowCount(); + Sequence< sal_Int32 > selectedRows( selectionCount ); + auto selectedRowsRange = asNonConstRange(selectedRows); + for ( sal_Int32 i=0; i<selectionCount; ++i ) + selectedRowsRange[i] = pTable->GetSelectedRowIndex(i); + return selectedRows; +} + + +sal_Bool SAL_CALL SVTXGridControl::hasSelectedRows() +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN( pTable, "SVTXGridControl::hasSelectedRows: no control (anymore)!", true ); + + return pTable->GetSelectedRowCount() > 0; +} + + +sal_Bool SAL_CALL SVTXGridControl::isRowSelected( ::sal_Int32 index ) +{ + SolarMutexGuard aGuard; + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN( pTable, "SVTXGridControl::isRowSelected: no control (anymore)!", false ); + + return pTable->IsRowSelected( index ); +} + + +void SVTXGridControl::dispose() +{ + EventObject aObj; + aObj.Source = static_cast<cppu::OWeakObject*>(this); + m_aSelectionListeners.disposeAndClear( aObj ); + VCLXWindow::dispose(); +} + + +void SVTXGridControl::ProcessWindowEvent( const VclWindowEvent& rVclWindowEvent ) +{ + SolarMutexGuard aGuard; + + Reference< XWindow > xKeepAlive( this ); + + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::ProcessWindowEvent: no control (anymore)!" ); + + bool handled = false; + switch ( rVclWindowEvent.GetId() ) + { + case VclEventId::TableRowSelect: + { + if ( m_aSelectionListeners.getLength() ) + ImplCallItemListeners(); + handled = true; + } + break; + + case VclEventId::ControlGetFocus: + { + // TODO: this doesn't belong here. It belongs into the TableControl/_Impl, so A11Y also + // works when the control is used outside the UNO context + if ( pTable->GetRowCount()>0 ) + { + pTable->commitCellEventIfAccessibleAlive( + AccessibleEventId::STATE_CHANGED, + Any( AccessibleStateType::FOCUSED ), + Any() + ); + pTable->commitTableEventIfAccessibleAlive( + AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, + Any(), + Any() + ); + } + else + { + pTable->commitTableEventIfAccessibleAlive( + AccessibleEventId::STATE_CHANGED, + Any( AccessibleStateType::FOCUSED ), + Any() + ); + } + } + break; + + case VclEventId::ControlLoseFocus: + { + // TODO: this doesn't belong here. It belongs into the TableControl/_Impl, so A11Y also + // works when the control is used outside the UNO context + if ( pTable->GetRowCount()>0 ) + { + pTable->commitCellEventIfAccessibleAlive( + AccessibleEventId::STATE_CHANGED, + Any(), + Any( AccessibleStateType::FOCUSED ) + ); + } + else + { + pTable->commitTableEventIfAccessibleAlive( + AccessibleEventId::STATE_CHANGED, + Any(), + Any( AccessibleStateType::FOCUSED ) + ); + } + } + break; + + default: break; + } + + if ( !handled ) + VCLXWindow::ProcessWindowEvent( rVclWindowEvent ); +} + + +void SVTXGridControl::setEnable( sal_Bool bEnable ) +{ + SolarMutexGuard aGuard; + + m_xTableModel->setEnabled( bEnable ); + VclPtr<vcl::Window> pWindow = GetWindow(); + if ( pWindow ) + { + pWindow->Enable( bEnable ); + pWindow->EnableInput( bEnable ); + pWindow->Invalidate(); + } +} + + +void SVTXGridControl::ImplCallItemListeners() +{ + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN_VOID( pTable, "SVTXGridControl::ImplCallItemListeners: no control (anymore)!" ); + + if ( m_aSelectionListeners.getLength() ) + { + GridSelectionEvent aEvent; + aEvent.Source = static_cast<cppu::OWeakObject*>(this); + + sal_Int32 const nSelectedRowCount( pTable->GetSelectedRowCount() ); + aEvent.SelectedRowIndexes.realloc( nSelectedRowCount ); + auto pSelectedRowIndexes = aEvent.SelectedRowIndexes.getArray(); + for ( sal_Int32 i=0; i<nSelectedRowCount; ++i ) + pSelectedRowIndexes[i] = pTable->GetSelectedRowIndex( i ); + m_aSelectionListeners.selectionChanged( aEvent ); + } +} + + +void SVTXGridControl::impl_updateColumnsFromModel_nothrow() +{ + Reference< XGridColumnModel > const xColumnModel( m_xTableModel->getColumnModel() ); + ENSURE_OR_RETURN_VOID( xColumnModel.is(), "no model!" ); + VclPtr< TableControl > pTable = GetAsDynamic< TableControl >(); + ENSURE_OR_RETURN_VOID( pTable, "no table!" ); + + try + { + const Sequence< Reference< XGridColumn > > columns = xColumnModel->getColumns(); + for ( auto const & colRef : columns ) + { + if ( !colRef.is() ) + { + SAL_WARN( "svtools.uno", "illegal column!" ); + continue; + } + + m_xTableModel->appendColumn( colRef ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/svtxgridcontrol.hxx b/svtools/source/uno/svtxgridcontrol.hxx new file mode 100644 index 000000000..9e4fd85fe --- /dev/null +++ b/svtools/source/uno/svtxgridcontrol.hxx @@ -0,0 +1,110 @@ +/* -*- 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 "unocontroltablemodel.hxx" +#include <table/tablecontrol.hxx> +#include <com/sun/star/awt/grid/XGridControl.hpp> +#include <com/sun/star/awt/grid/XGridRowSelection.hpp> +#include <com/sun/star/awt/grid/XGridDataListener.hpp> +#include <com/sun/star/awt/grid/GridDataEvent.hpp> +#include <com/sun/star/awt/grid/XGridSelectionListener.hpp> +#include <com/sun/star/container/XContainerListener.hpp> +#include <toolkit/awt/vclxwindow.hxx> +#include <cppuhelper/implbase.hxx> +#include <toolkit/helper/listenermultiplexer.hxx> + + +namespace svt::table { + class TableControl; +} + +typedef ::cppu::ImplInheritanceHelper < VCLXWindow + , css::awt::grid::XGridControl + , css::awt::grid::XGridRowSelection + , css::awt::grid::XGridDataListener + , css::container::XContainerListener + > SVTXGridControl_Base; +class SVTXGridControl final : public SVTXGridControl_Base +{ +public: + SVTXGridControl(); + virtual ~SVTXGridControl() override; + + // XGridDataListener + virtual void SAL_CALL rowsInserted( const css::awt::grid::GridDataEvent& Event ) override; + virtual void SAL_CALL rowsRemoved( const css::awt::grid::GridDataEvent& Event ) override; + virtual void SAL_CALL dataChanged( const css::awt::grid::GridDataEvent& Event ) override; + virtual void SAL_CALL rowHeadingChanged( const css::awt::grid::GridDataEvent& Event ) override; + + // XContainerListener + virtual void SAL_CALL elementInserted( const css::container::ContainerEvent& Event ) override; + virtual void SAL_CALL elementRemoved( const css::container::ContainerEvent& Event ) override; + virtual void SAL_CALL elementReplaced( const css::container::ContainerEvent& Event ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + // XGridControl + virtual ::sal_Int32 SAL_CALL getRowAtPoint(::sal_Int32 x, ::sal_Int32 y) override; + virtual ::sal_Int32 SAL_CALL getColumnAtPoint(::sal_Int32 x, ::sal_Int32 y) override; + virtual ::sal_Int32 SAL_CALL getCurrentColumn( ) override; + virtual ::sal_Int32 SAL_CALL getCurrentRow( ) override; + virtual void SAL_CALL goToCell( ::sal_Int32 i_columnIndex, ::sal_Int32 i_rowIndex ) override; + + // XGridRowSelection + virtual void SAL_CALL selectRow( ::sal_Int32 i_rowIndex ) override; + virtual void SAL_CALL selectAllRows() override; + virtual void SAL_CALL deselectRow( ::sal_Int32 i_rowIndex ) override; + virtual void SAL_CALL deselectAllRows() override; + virtual css::uno::Sequence< ::sal_Int32 > SAL_CALL getSelectedRows() override; + virtual sal_Bool SAL_CALL hasSelectedRows() override; + virtual sal_Bool SAL_CALL isRowSelected(::sal_Int32 index) override; + virtual void SAL_CALL addSelectionListener(const css::uno::Reference< css::awt::grid::XGridSelectionListener > & listener) override; + virtual void SAL_CALL removeSelectionListener(const css::uno::Reference< css::awt::grid::XGridSelectionListener > & listener) override; + + void SAL_CALL setProperty( const OUString& PropertyName, const css::uno::Any& Value ) override; + css::uno::Any SAL_CALL getProperty( const OUString& PropertyName ) override; + + // css::lang::XComponent + void SAL_CALL dispose( ) override; + + // XWindow + void SAL_CALL setEnable( sal_Bool bEnable ) override; + +private: + // VCLXWindow + virtual void SetWindow( const VclPtr< vcl::Window > &pWindow ) override; + + void impl_updateColumnsFromModel_nothrow(); + void impl_checkTableModelInit(); + + void impl_checkColumnIndex_throw( ::svt::table::TableControl const & i_table, sal_Int32 const i_columnIndex ) const; + void impl_checkRowIndex_throw( ::svt::table::TableControl const & i_table, sal_Int32 const i_rowIndex ) const; + + virtual void ProcessWindowEvent( const VclWindowEvent& rVclWindowEvent ) override; + void ImplCallItemListeners(); + + std::shared_ptr< ::svt::table::UnoControlTableModel > m_xTableModel; + bool m_bTableModelInitCompleted; + SelectionListenerMultiplexer m_aSelectionListeners; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/toolboxcontroller.cxx b/svtools/source/uno/toolboxcontroller.cxx new file mode 100644 index 000000000..21e186baf --- /dev/null +++ b/svtools/source/uno/toolboxcontroller.cxx @@ -0,0 +1,802 @@ +/* -*- 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 <svtools/toolboxcontroller.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/frame/XLayoutManager.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/svapp.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/weldutils.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> + +const int TOOLBARCONTROLLER_PROPHANDLE_SUPPORTSVISIBLE = 1; +constexpr OUStringLiteral TOOLBARCONTROLLER_PROPNAME_SUPPORTSVISIBLE = u"SupportsVisible"; + + +using namespace ::cppu; +using namespace css::awt; +using namespace css::uno; +using namespace css::util; +using namespace css::beans; +using namespace css::lang; +using namespace css::frame; + +namespace svt +{ + +ToolboxController::ToolboxController( + const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& xFrame, + const OUString& aCommandURL ) : + OPropertyContainer( GetBroadcastHelper() ) + , m_bSupportVisible( false ) + , m_bInitialized( false ) + , m_bDisposed( false ) + , m_bSidebar( false ) + , m_nToolBoxId( SAL_MAX_UINT16 ) + , m_xFrame( xFrame ) + , m_xContext( rxContext ) + , m_aCommandURL( aCommandURL ) + , m_aListenerContainer( m_aMutex ) + , m_pToolbar(nullptr) + , m_pBuilder(nullptr) +{ + OSL_ASSERT( m_xContext.is() ); + registerProperty( TOOLBARCONTROLLER_PROPNAME_SUPPORTSVISIBLE, + TOOLBARCONTROLLER_PROPHANDLE_SUPPORTSVISIBLE, + css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY, + &m_bSupportVisible, cppu::UnoType<decltype(m_bSupportVisible)>::get()); + + try + { + m_xUrlTransformer = URLTransformer::create( rxContext ); + } + catch(const Exception&) + { + } +} + +ToolboxController::ToolboxController() : + OPropertyContainer(GetBroadcastHelper()) + , m_bSupportVisible(false) + , m_bInitialized( false ) + , m_bDisposed( false ) + , m_bSidebar( false ) + , m_nToolBoxId( SAL_MAX_UINT16 ) + , m_aListenerContainer( m_aMutex ) + , m_pToolbar(nullptr) + , m_pBuilder(nullptr) +{ + registerProperty( TOOLBARCONTROLLER_PROPNAME_SUPPORTSVISIBLE, + TOOLBARCONTROLLER_PROPHANDLE_SUPPORTSVISIBLE, + css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY, + &m_bSupportVisible, cppu::UnoType<decltype(m_bSupportVisible)>::get()); +} + +ToolboxController::~ToolboxController() +{ +} + +Reference< XFrame > ToolboxController::getFrameInterface() const +{ + SolarMutexGuard aSolarMutexGuard; + return m_xFrame; +} + +const Reference< XComponentContext > & ToolboxController::getContext() const +{ + SolarMutexGuard aSolarMutexGuard; + return m_xContext; +} + +Reference< XLayoutManager > ToolboxController::getLayoutManager() const +{ + Reference< XLayoutManager > xLayoutManager; + Reference< XPropertySet > xPropSet; + { + SolarMutexGuard aSolarMutexGuard; + xPropSet.set( m_xFrame, UNO_QUERY ); + } + + if ( xPropSet.is() ) + { + try + { + xLayoutManager.set(xPropSet->getPropertyValue("LayoutManager"),UNO_QUERY); + } + catch ( Exception& ) + { + } + } + + return xLayoutManager; +} + +// XInterface +Any SAL_CALL ToolboxController::queryInterface( const Type& rType ) +{ + css::uno::Any a(ToolboxController_Base::queryInterface(rType)); + return a.hasValue() ? a : OPropertyContainer::queryInterface(rType); +} + +void SAL_CALL ToolboxController::acquire() noexcept +{ + ToolboxController_Base::acquire(); +} + +void SAL_CALL ToolboxController::release() noexcept +{ + ToolboxController_Base::release(); +} + +css::uno::Sequence<css::uno::Type> ToolboxController::getTypes() +{ + return comphelper::concatSequences(ToolboxController_Base::getTypes(), + getBaseTypes()); +} + +void SAL_CALL ToolboxController::initialize( const Sequence< Any >& aArguments ) +{ + bool bInitialized( true ); + + { + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + bInitialized = m_bInitialized; + } + + if ( bInitialized ) + return; + + SolarMutexGuard aSolarMutexGuard; + m_bInitialized = true; + m_bSupportVisible = false; + PropertyValue aPropValue; + for ( const auto& rArgument : aArguments ) + { + if ( rArgument >>= aPropValue ) + { + if ( aPropValue.Name == "Frame" ) + m_xFrame.set(aPropValue.Value,UNO_QUERY); + else if ( aPropValue.Name == "CommandURL" ) + aPropValue.Value >>= m_aCommandURL; + else if ( aPropValue.Name == "ServiceManager" ) + { + Reference<XMultiServiceFactory> xMSF(aPropValue.Value, UNO_QUERY); + if (xMSF.is()) + m_xContext = comphelper::getComponentContext(xMSF); + } + else if ( aPropValue.Name == "ParentWindow" ) + m_xParentWindow.set(aPropValue.Value,UNO_QUERY); + else if ( aPropValue.Name == "ModuleIdentifier" ) + aPropValue.Value >>= m_sModuleName; + else if ( aPropValue.Name == "Identifier" ) + { + sal_uInt16 nTmp; + if (aPropValue.Value >>= nTmp) + m_nToolBoxId = ToolBoxItemId(nTmp); + } + else if ( aPropValue.Name == "IsSidebar" ) + aPropValue.Value >>= m_bSidebar; + } + } + + try + { + if ( !m_xUrlTransformer.is() && m_xContext.is() ) + m_xUrlTransformer = URLTransformer::create( m_xContext ); + } + catch(const Exception&) + { + } + + if ( !m_aCommandURL.isEmpty() ) + m_aListenerMap.emplace( m_aCommandURL, Reference< XDispatch >() ); + + if (weld::TransportAsXWindow* pTunnel = dynamic_cast<weld::TransportAsXWindow*>(getParent().get())) + { + m_pToolbar = dynamic_cast<weld::Toolbar*>(pTunnel->getWidget()); + assert(m_pToolbar && "must be a toolbar"); + m_pBuilder = pTunnel->getBuilder(); + } +} + +void SAL_CALL ToolboxController::update() +{ + { + SolarMutexGuard aSolarMutexGuard; + if ( m_bDisposed ) + throw DisposedException(); + } + + // Bind all registered listeners to their dispatch objects + bindListener(); +} + +// XComponent +void SAL_CALL ToolboxController::dispose() +{ + Reference< XComponent > xThis(this); + + { + SolarMutexGuard aSolarMutexGuard; + if ( m_bDisposed ) + throw DisposedException(); + } + + css::lang::EventObject aEvent( xThis ); + m_aListenerContainer.disposeAndClear( aEvent ); + + SolarMutexGuard aSolarMutexGuard; + Reference< XStatusListener > xStatusListener(this); + for (auto const& listener : m_aListenerMap) + { + try + { + Reference< XDispatch > xDispatch( listener.second ); + + css::util::URL aTargetURL; + aTargetURL.Complete = listener.first; + if ( m_xUrlTransformer.is() ) + m_xUrlTransformer->parseStrict( aTargetURL ); + + if ( xDispatch.is() && xStatusListener.is() ) + xDispatch->removeStatusListener( xStatusListener, aTargetURL ); + } + catch ( Exception& ) + { + } + + } + + m_bDisposed = true; +} + +void SAL_CALL ToolboxController::addEventListener( const Reference< XEventListener >& xListener ) +{ + m_aListenerContainer.addInterface( cppu::UnoType<XEventListener>::get(), xListener ); +} + +void SAL_CALL ToolboxController::removeEventListener( const Reference< XEventListener >& aListener ) +{ + m_aListenerContainer.removeInterface( cppu::UnoType<XEventListener>::get(), aListener ); +} + +// XEventListener +void SAL_CALL ToolboxController::disposing( const EventObject& Source ) +{ + Reference< XInterface > xSource( Source.Source ); + + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + return; + + for (auto & listener : m_aListenerMap) + { + // Compare references and release dispatch references if they are equal. + Reference< XInterface > xIfac(listener.second, UNO_QUERY); + if ( xSource == xIfac ) + listener.second.clear(); + } + + Reference< XInterface > xIfac( m_xFrame, UNO_QUERY ); + if ( xIfac == xSource ) + m_xFrame.clear(); +} + +// XStatusListener +void SAL_CALL ToolboxController::statusChanged( const FeatureStateEvent& ) +{ + // must be implemented by sub class +} + +// XToolbarController +void SAL_CALL ToolboxController::execute( sal_Int16 KeyModifier ) +{ + Reference< XDispatch > xDispatch; + OUString aCommandURL; + + { + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( m_bInitialized && + m_xFrame.is() && + !m_aCommandURL.isEmpty() ) + { + aCommandURL = m_aCommandURL; + URLToDispatchMap::iterator pIter = m_aListenerMap.find( m_aCommandURL ); + if ( pIter != m_aListenerMap.end() ) + xDispatch = pIter->second; + } + } + + if ( !xDispatch.is() ) + return; + + try + { + css::util::URL aTargetURL; + + // Provide key modifier information to dispatch function + Sequence<PropertyValue> aArgs{ comphelper::makePropertyValue("KeyModifier", KeyModifier) }; + + aTargetURL.Complete = aCommandURL; + if ( m_xUrlTransformer.is() ) + m_xUrlTransformer->parseStrict( aTargetURL ); + xDispatch->dispatch( aTargetURL, aArgs ); + } + catch ( DisposedException& ) + { + } +} + +void SAL_CALL ToolboxController::click() +{ +} + +void SAL_CALL ToolboxController::doubleClick() +{ +} + +Reference< XWindow > SAL_CALL ToolboxController::createPopupWindow() +{ + return Reference< XWindow >(); +} + +Reference< XWindow > SAL_CALL ToolboxController::createItemWindow( const Reference< XWindow >& ) +{ + return Reference< XWindow >(); +} + +void ToolboxController::addStatusListener( const OUString& aCommandURL ) +{ + Reference< XDispatch > xDispatch; + Reference< XStatusListener > xStatusListener; + css::util::URL aTargetURL; + + { + SolarMutexGuard aSolarMutexGuard; + URLToDispatchMap::iterator pIter = m_aListenerMap.find( aCommandURL ); + + // Already in the list of status listener. Do nothing. + if ( pIter != m_aListenerMap.end() ) + return; + + // Check if we are already initialized. Implementation starts adding itself as status listener when + // initialize is called. + if ( !m_bInitialized ) + { + // Put into the unordered_map of status listener. Will be activated when initialized is called + m_aListenerMap.emplace( aCommandURL, Reference< XDispatch >() ); + return; + } + else + { + // Add status listener directly as initialize has already been called. + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + if ( m_xContext.is() && xDispatchProvider.is() ) + { + aTargetURL.Complete = aCommandURL; + if ( m_xUrlTransformer.is() ) + m_xUrlTransformer->parseStrict( aTargetURL ); + xDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + + xStatusListener = this; + URLToDispatchMap::iterator aIter = m_aListenerMap.find( aCommandURL ); + if ( aIter != m_aListenerMap.end() ) + { + Reference< XDispatch > xOldDispatch( aIter->second ); + aIter->second = xDispatch; + + try + { + if ( xOldDispatch.is() ) + xOldDispatch->removeStatusListener( xStatusListener, aTargetURL ); + } + catch ( Exception& ) + { + } + } + else + m_aListenerMap.emplace( aCommandURL, xDispatch ); + } + } + } + + // Call without locked mutex as we are called back from dispatch implementation + try + { + if ( xDispatch.is() ) + xDispatch->addStatusListener( xStatusListener, aTargetURL ); + } + catch ( Exception& ) + { + } +} + +void ToolboxController::removeStatusListener( const OUString& aCommandURL ) +{ + SolarMutexGuard aSolarMutexGuard; + + URLToDispatchMap::iterator pIter = m_aListenerMap.find( aCommandURL ); + if ( pIter == m_aListenerMap.end() ) + return; + + Reference< XDispatch > xDispatch( pIter->second ); + Reference< XStatusListener > xStatusListener(this); + m_aListenerMap.erase( pIter ); + + try + { + css::util::URL aTargetURL; + aTargetURL.Complete = aCommandURL; + if ( m_xUrlTransformer.is() ) + m_xUrlTransformer->parseStrict( aTargetURL ); + + if ( xDispatch.is() && xStatusListener.is() ) + xDispatch->removeStatusListener( xStatusListener, aTargetURL ); + } + catch ( Exception& ) + { + } +} + +void ToolboxController::bindListener() +{ + std::vector< Listener > aDispatchVector; + Reference< XStatusListener > xStatusListener; + + { + SolarMutexGuard aSolarMutexGuard; + + if ( !m_bInitialized ) + return; + + // Collect all registered command URL's and store them temporary + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + if ( m_xContext.is() && xDispatchProvider.is() ) + { + xStatusListener = this; + for (auto & listener : m_aListenerMap) + { + css::util::URL aTargetURL; + aTargetURL.Complete = listener.first; + if ( m_xUrlTransformer.is() ) + m_xUrlTransformer->parseStrict( aTargetURL ); + + Reference< XDispatch > xDispatch(listener.second); + if ( xDispatch.is() ) + { + // We already have a dispatch object => we have to requery. + // Release old dispatch object and remove it as listener + try + { + xDispatch->removeStatusListener( xStatusListener, aTargetURL ); + } + catch ( Exception& ) + { + } + } + + listener.second.clear(); + xDispatch.clear(); + + // Query for dispatch object. Old dispatch will be released with this, too. + try + { + xDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + } + catch ( Exception& ) + { + } + + // it may be a command alias + if (!xDispatch.is()) + { + try + { + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(listener.first, + vcl::CommandInfoProvider::GetModuleIdentifier(m_xFrame)); + OUString sRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties); + + if (!sRealCommand.isEmpty()) + { + aTargetURL.Complete = sRealCommand; + if ( m_xUrlTransformer.is() ) + m_xUrlTransformer->parseStrict( aTargetURL ); + + xDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + } + } + catch ( Exception& ) + { + } + } + + listener.second = xDispatch; + + Listener aListener( aTargetURL, xDispatch ); + aDispatchVector.push_back( aListener ); + } + } + } + + // Call without locked mutex as we are called back from dispatch implementation + if ( !xStatusListener.is() ) + return; + + try + { + for (Listener & rListener : aDispatchVector) + { + if ( rListener.xDispatch.is() ) + rListener.xDispatch->addStatusListener( xStatusListener, rListener.aURL ); + else if ( rListener.aURL.Complete == m_aCommandURL ) + { + try + { + // Send status changed for the main URL, if we cannot get a valid dispatch object. + // UI disables the button. Catch exception as we release our mutex, it is possible + // that someone else already disposed this instance! + FeatureStateEvent aFeatureStateEvent; + aFeatureStateEvent.IsEnabled = false; + aFeatureStateEvent.FeatureURL = rListener.aURL; + aFeatureStateEvent.State = Any(); + xStatusListener->statusChanged( aFeatureStateEvent ); + } + catch ( Exception& ) + { + } + } + } + } + catch ( Exception& ) + { + } +} + +void ToolboxController::unbindListener() +{ + SolarMutexGuard aSolarMutexGuard; + + if ( !m_bInitialized ) + return; + + // Collect all registered command URL's and store them temporary + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + if ( !(m_xContext.is() && xDispatchProvider.is()) ) + return; + + Reference< XStatusListener > xStatusListener(this); + for (auto & listener : m_aListenerMap) + { + css::util::URL aTargetURL; + aTargetURL.Complete = listener.first; + if ( m_xUrlTransformer.is() ) + m_xUrlTransformer->parseStrict( aTargetURL ); + + Reference< XDispatch > xDispatch(listener.second); + if ( xDispatch.is() ) + { + // We already have a dispatch object => we have to requery. + // Release old dispatch object and remove it as listener + try + { + xDispatch->removeStatusListener( xStatusListener, aTargetURL ); + } + catch ( Exception& ) + { + } + } + listener.second.clear(); + } +} + +void ToolboxController::updateStatus() +{ + bindListener(); +} + +void ToolboxController::updateStatus( const OUString& aCommandURL ) +{ + Reference< XDispatch > xDispatch; + Reference< XStatusListener > xStatusListener; + css::util::URL aTargetURL; + + { + SolarMutexGuard aSolarMutexGuard; + + if ( !m_bInitialized ) + return; + + // Try to find a dispatch object for the requested command URL + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + xStatusListener = this; + if ( m_xContext.is() && xDispatchProvider.is() ) + { + aTargetURL.Complete = aCommandURL; + if ( m_xUrlTransformer.is() ) + m_xUrlTransformer->parseStrict( aTargetURL ); + xDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + } + } + + if ( !(xDispatch.is() && xStatusListener.is()) ) + return; + + // Catch exception as we release our mutex, it is possible that someone else + // has already disposed this instance! + // Add/remove status listener to get an update status information from the + // requested command. + try + { + xDispatch->addStatusListener( xStatusListener, aTargetURL ); + xDispatch->removeStatusListener( xStatusListener, aTargetURL ); + } + catch ( Exception& ) + { + } +} + + +void ToolboxController::dispatchCommand( const OUString& sCommandURL, const Sequence< PropertyValue >& rArgs, const OUString &sTarget ) +{ + try + { + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY_THROW ); + URL aURL; + aURL.Complete = sCommandURL; + getURLTransformer()->parseStrict( aURL ); + + Reference< XDispatch > xDispatch( xDispatchProvider->queryDispatch( aURL, sTarget, 0 ), UNO_SET_THROW ); + + std::unique_ptr<DispatchInfo> pDispatchInfo(new DispatchInfo( xDispatch, aURL, rArgs )); + if ( Application::PostUserEvent( LINK(nullptr, ToolboxController, ExecuteHdl_Impl), + pDispatchInfo.get() ) ) + pDispatchInfo.release(); + + } + catch( Exception& ) + { + } +} + + +css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL ToolboxController::getPropertySetInfo() +{ + Reference<XPropertySetInfo> xInfo( createPropertySetInfo( getInfoHelper() ) ); + return xInfo; +} + +::cppu::IPropertyArrayHelper& ToolboxController::getInfoHelper() +{ + return *getArrayHelper(); +} + + +::cppu::IPropertyArrayHelper* ToolboxController::createArrayHelper( ) const +{ + css::uno::Sequence< Property > aProps; + describeProperties(aProps); + return new ::cppu::OPropertyArrayHelper(aProps); +} + +sal_Bool SAL_CALL ToolboxController::convertFastPropertyValue( css::uno::Any& aConvertedValue , + css::uno::Any& aOldValue , + sal_Int32 nHandle , + const css::uno::Any& aValue ) +{ + switch (nHandle) + { + case TOOLBARCONTROLLER_PROPHANDLE_SUPPORTSVISIBLE: + { + bool aNewValue(false); + aValue >>= aNewValue; + if (aNewValue != m_bSupportVisible) + { + aConvertedValue <<= aNewValue; + aOldValue <<= m_bSupportVisible; + return true; + } + return false; + } + } + return OPropertyContainer::convertFastPropertyValue(aConvertedValue, aOldValue, nHandle, aValue); +} + +void SAL_CALL ToolboxController::setFastPropertyValue_NoBroadcast( + sal_Int32 nHandle, + const css::uno::Any& aValue ) +{ + OPropertyContainer::setFastPropertyValue_NoBroadcast(nHandle, aValue); + if (TOOLBARCONTROLLER_PROPHANDLE_SUPPORTSVISIBLE == nHandle) + { + bool rValue(false); + if (( aValue >>= rValue ) && m_bInitialized) + m_bSupportVisible = rValue; + } +} + + +IMPL_STATIC_LINK( ToolboxController, ExecuteHdl_Impl, void*, p, void ) +{ + DispatchInfo* pDispatchInfo = static_cast<DispatchInfo*>(p); + pDispatchInfo->mxDispatch->dispatch( pDispatchInfo->maURL, pDispatchInfo->maArgs ); + delete pDispatchInfo; +} + +void ToolboxController::enable( bool bEnable ) +{ + ToolBox* pToolBox = nullptr; + ToolBoxItemId nItemId; + if( getToolboxId( nItemId, &pToolBox ) ) + { + pToolBox->EnableItem( nItemId, bEnable ); + } +} + +bool ToolboxController::getToolboxId( ToolBoxItemId& rItemId, ToolBox** ppToolBox ) +{ + if( (m_nToolBoxId != ToolBoxItemId(SAL_MAX_UINT16)) && (ppToolBox == nullptr) ) + return false; + + ToolBox* pToolBox = static_cast< ToolBox* >( VCLUnoHelper::GetWindow( getParent() ) ); + + if( (m_nToolBoxId == ToolBoxItemId(SAL_MAX_UINT16)) && pToolBox ) + { + const ToolBox::ImplToolItems::size_type nCount = pToolBox->GetItemCount(); + for ( ToolBox::ImplToolItems::size_type nPos = 0; nPos < nCount; ++nPos ) + { + const ToolBoxItemId nItemId = pToolBox->GetItemId( nPos ); + if ( pToolBox->GetItemCommand( nItemId ) == m_aCommandURL ) + { + m_nToolBoxId = nItemId; + break; + } + } + } + + if( ppToolBox ) + *ppToolBox = pToolBox; + + rItemId = m_nToolBoxId; + + return (rItemId != ToolBoxItemId(SAL_MAX_UINT16)) && (( ppToolBox == nullptr) || (*ppToolBox != nullptr) ); +} +//end + +} // svt + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/unocontroltablemodel.cxx b/svtools/source/uno/unocontroltablemodel.cxx new file mode 100644 index 000000000..14f9c6977 --- /dev/null +++ b/svtools/source/uno/unocontroltablemodel.cxx @@ -0,0 +1,835 @@ +/* -*- 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 "unocontroltablemodel.hxx" +#include "unogridcolumnfacade.hxx" + +#include <table/defaultinputhandler.hxx> +#include <table/gridtablerenderer.hxx> + +#include <com/sun/star/awt/grid/XSortableGridData.hpp> +#include <com/sun/star/util/Color.hpp> +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> +#include <cppuhelper/weakref.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> + + +namespace svt::table +{ + + + using css::uno::Reference; + using css::uno::Sequence; + using css::uno::UNO_QUERY_THROW; + using css::uno::UNO_QUERY; + using css::awt::grid::XGridColumn; + using css::uno::Exception; + using css::awt::grid::XGridDataModel; + using css::awt::grid::XGridColumnModel; + using css::uno::Any; + using css::style::VerticalAlignment_TOP; + using css::style::VerticalAlignment; + using css::uno::WeakReference; + using css::awt::grid::GridDataEvent; + using css::awt::grid::XSortableGridData; + using css::beans::Pair; + + + //= UnoControlTableModel +#define DBG_CHECK_ME() \ + DBG_TESTSOLARMUTEX(); \ + + UnoControlTableModel::UnoControlTableModel() + :aColumns ( ) + ,bHasColumnHeaders ( true ) + ,bHasRowHeaders ( false ) + ,eVScrollMode ( ScrollbarShowNever ) + ,eHScrollMode ( ScrollbarShowNever ) + ,pRenderer ( ) + ,pInputHandler ( ) + ,nRowHeight ( 10 ) + ,nColumnHeaderHeight ( 10 ) + ,nRowHeaderWidth ( 10 ) + ,m_aGridLineColor ( ) + ,m_aHeaderBackgroundColor ( ) + ,m_aHeaderTextColor ( ) + ,m_aActiveSelectionBackColor ( ) + ,m_aInactiveSelectionBackColor ( ) + ,m_aActiveSelectionTextColor ( ) + ,m_aInactiveSelectionTextColor ( ) + ,m_aTextColor ( ) + ,m_aTextLineColor ( ) + ,m_aRowColors ( ) + ,m_eVerticalAlign ( VerticalAlignment_TOP ) + ,bEnabled ( true ) + { + pRenderer = std::make_shared<GridTableRenderer>( *this ); + pInputHandler = std::make_shared<DefaultInputHandler>(); + } + + + UnoControlTableModel::~UnoControlTableModel() + { + } + + + TableSize UnoControlTableModel::getColumnCount() const + { + DBG_CHECK_ME(); + return static_cast<TableSize>(aColumns.size()); + } + + + TableSize UnoControlTableModel::getRowCount() const + { + DBG_CHECK_ME(); + + TableSize nRowCount = 0; + try + { + Reference< XGridDataModel > const xDataModel( m_aDataModel ); + ENSURE_OR_THROW( xDataModel.is(), "no data model anymore!" ); + nRowCount = xDataModel->getRowCount(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + return nRowCount; + } + + + bool UnoControlTableModel::hasColumnHeaders() const + { + DBG_CHECK_ME(); + return bHasColumnHeaders; + } + + + bool UnoControlTableModel::hasRowHeaders() const + { + DBG_CHECK_ME(); + return bHasRowHeaders; + } + + + void UnoControlTableModel::setRowHeaders(bool _bRowHeaders) + { + DBG_CHECK_ME(); + if ( bHasRowHeaders == _bRowHeaders ) + return; + + bHasRowHeaders = _bRowHeaders; + impl_notifyTableMetricsChanged(); + } + + + void UnoControlTableModel::setColumnHeaders(bool _bColumnHeaders) + { + DBG_CHECK_ME(); + if ( bHasColumnHeaders == _bColumnHeaders ) + return; + + bHasColumnHeaders = _bColumnHeaders; + impl_notifyTableMetricsChanged(); + } + + + PColumnModel UnoControlTableModel::getColumnModel( ColPos column ) + { + DBG_CHECK_ME(); + ENSURE_OR_RETURN( ( column >= 0 ) && ( column < getColumnCount() ), + "DefaultTableModel::getColumnModel: invalid index!", PColumnModel() ); + return aColumns[ column ]; + } + + + void UnoControlTableModel::appendColumn( Reference< XGridColumn > const & i_column ) + { + DBG_CHECK_ME(); + insertColumn( aColumns.size(), i_column ); + } + + + void UnoControlTableModel::insertColumn( ColPos const i_position, Reference< XGridColumn > const & i_column ) + { + DBG_CHECK_ME(); + ENSURE_OR_RETURN_VOID( ( i_position >= 0 ) && ( o3tl::make_unsigned( i_position ) <= aColumns.size() ), + "UnoControlTableModel::insertColumn: illegal position!" ); + + const PColumnModel pColumn = std::make_shared<UnoGridColumnFacade>( *this, i_column ); + aColumns.insert( aColumns.begin() + i_position, pColumn ); + + // notify listeners + ModellListeners aListeners( m_aListeners ); + for (auto const& listener : aListeners) + { + listener->columnInserted(); + } + } + + + void UnoControlTableModel::removeColumn( ColPos const i_position ) + { + DBG_CHECK_ME(); + ENSURE_OR_RETURN_VOID( ( i_position >= 0 ) && ( o3tl::make_unsigned( i_position ) <= aColumns.size() ), + "UnoControlTableModel::removeColumn: illegal position!" ); + + // remove the column + ColumnModels::iterator pos = aColumns.begin() + i_position; + const PColumnModel pColumn = *pos; + aColumns.erase( pos ); + + // notify listeners + ModellListeners aListeners( m_aListeners ); + for (auto const& listener : aListeners) + { + listener->columnRemoved(); + } + + // dispose the column + UnoGridColumnFacade* pColumnImpl = dynamic_cast< UnoGridColumnFacade* >( pColumn.get() ); + OSL_ENSURE( pColumnImpl != nullptr, "UnoControlTableModel::removeColumn: illegal column implementation!" ); + if ( pColumnImpl ) + pColumnImpl->dispose(); + } + + + void UnoControlTableModel::removeAllColumns() + { + DBG_CHECK_ME(); + if ( aColumns.empty() ) + return; + + // dispose the column instances + for (auto const& col : aColumns) + { + UnoGridColumnFacade* pColumn = dynamic_cast< UnoGridColumnFacade* >( col.get() ); + if ( !pColumn ) + { + SAL_WARN( "svtools.uno", "UnoControlTableModel::removeAllColumns: illegal column implementation!" ); + continue; + } + + pColumn->dispose(); + } + aColumns.clear(); + + // notify listeners + ModellListeners aListeners( m_aListeners ); + for (auto const& listener : aListeners) + { + listener->allColumnsRemoved(); + } + } + + + void UnoControlTableModel::impl_notifyTableMetricsChanged() const + { + ModellListeners aListeners( m_aListeners ); + for (auto const& listener : aListeners) + { + listener->tableMetricsChanged(); + } + } + + + PTableRenderer UnoControlTableModel::getRenderer() const + { + DBG_CHECK_ME(); + return pRenderer; + } + + + PTableInputHandler UnoControlTableModel::getInputHandler() const + { + DBG_CHECK_ME(); + return pInputHandler; + } + + + TableMetrics UnoControlTableModel::getRowHeight() const + { + DBG_CHECK_ME(); + return nRowHeight; + } + + + void UnoControlTableModel::setRowHeight(TableMetrics _nRowHeight) + { + DBG_CHECK_ME(); + if ( nRowHeight == _nRowHeight ) + return; + + nRowHeight = _nRowHeight; + impl_notifyTableMetricsChanged(); + } + + + TableMetrics UnoControlTableModel::getColumnHeaderHeight() const + { + DBG_CHECK_ME(); + DBG_ASSERT( hasColumnHeaders(), "DefaultTableModel::getColumnHeaderHeight: invalid call!" ); + return nColumnHeaderHeight; + } + + + TableMetrics UnoControlTableModel::getRowHeaderWidth() const + { + DBG_CHECK_ME(); + DBG_ASSERT( hasRowHeaders(), "DefaultTableModel::getRowHeaderWidth: invalid call!" ); + return nRowHeaderWidth; + } + + void UnoControlTableModel::setColumnHeaderHeight(TableMetrics _nHeight) + { + DBG_CHECK_ME(); + if ( nColumnHeaderHeight == _nHeight ) + return; + + nColumnHeaderHeight = _nHeight; + impl_notifyTableMetricsChanged(); + } + + + void UnoControlTableModel::setRowHeaderWidth(TableMetrics _nWidth) + { + DBG_CHECK_ME(); + if ( nRowHeaderWidth == _nWidth ) + return; + + nRowHeaderWidth = _nWidth; + impl_notifyTableMetricsChanged(); + } + + + ScrollbarVisibility UnoControlTableModel::getVerticalScrollbarVisibility() const + { + DBG_CHECK_ME(); + return eVScrollMode; + } + + + ScrollbarVisibility UnoControlTableModel::getHorizontalScrollbarVisibility() const + { + DBG_CHECK_ME(); + return eHScrollMode; + } + + + void UnoControlTableModel::addTableModelListener( const PTableModelListener& i_listener ) + { + DBG_CHECK_ME(); + ENSURE_OR_RETURN_VOID( !!i_listener, "illegal NULL listener" ); + m_aListeners.push_back( i_listener ); + } + + + void UnoControlTableModel::removeTableModelListener( const PTableModelListener& i_listener ) + { + DBG_CHECK_ME(); + auto lookup = std::find(m_aListeners.begin(), m_aListeners.end(), i_listener); + if (lookup != m_aListeners.end()) + { + m_aListeners.erase( lookup ); + return; + } + OSL_ENSURE( false, "UnoControlTableModel::removeTableModelListener: listener is not registered - sure you're doing the right thing here?" ); + } + + + void UnoControlTableModel::setVerticalScrollbarVisibility( ScrollbarVisibility const i_visibility ) + { + DBG_CHECK_ME(); + eVScrollMode = i_visibility; + } + + + void UnoControlTableModel::setHorizontalScrollbarVisibility( ScrollbarVisibility const i_visibility ) + { + DBG_CHECK_ME(); + eHScrollMode = i_visibility; + } + + + void UnoControlTableModel::setDataModel( Reference< XGridDataModel > const & i_gridDataModel ) + { + DBG_CHECK_ME(); + m_aDataModel = i_gridDataModel; + // TODO: register as listener, so we're notified of row/data changes, and can multiplex them to our + // own listeners + } + + + Reference< XGridDataModel > UnoControlTableModel::getDataModel() const + { + Reference< XGridDataModel > const xDataModel( m_aDataModel ); + return xDataModel; + } + + + bool UnoControlTableModel::hasDataModel() const + { + return getDataModel().is(); + } + + + void UnoControlTableModel::setColumnModel( Reference< XGridColumnModel > const & i_gridColumnModel ) + { + DBG_CHECK_ME(); + m_aColumnModel = i_gridColumnModel; + } + + + Reference< XGridColumnModel > UnoControlTableModel::getColumnModel() const + { + Reference< XGridColumnModel > const xColumnModel( m_aColumnModel ); + return xColumnModel; + } + + + bool UnoControlTableModel::hasColumnModel() const + { + return getColumnModel().is(); + } + + + void UnoControlTableModel::getCellContent( ColPos const i_col, RowPos const i_row, Any& o_cellContent ) + { + DBG_CHECK_ME(); + + o_cellContent.clear(); + try + { + Reference< XGridDataModel > const xDataModel( m_aDataModel ); + ENSURE_OR_RETURN_VOID( xDataModel.is(), "UnoControlTableModel::getCellContent: no data model anymore!" ); + + PColumnModel const pColumn = getColumnModel( i_col ); + UnoGridColumnFacade* pColumnImpl = dynamic_cast< UnoGridColumnFacade* >( pColumn.get() ); + ENSURE_OR_RETURN_VOID( pColumnImpl != nullptr, "UnoControlTableModel::getCellContent: no (valid) column at this position!" ); + sal_Int32 const nDataColumnIndex = pColumnImpl->getDataColumnIndex() >= 0 ? pColumnImpl->getDataColumnIndex() : i_col; + + if ( nDataColumnIndex >= xDataModel->getColumnCount() ) + { + // this is allowed, in case the column model has been dynamically extended, but the data model does + // not (yet?) know about it. + // So, handle it gracefully. + #if OSL_DEBUG_LEVEL > 0 + Reference< XGridColumnModel > const xColumnModel( m_aColumnModel ); + OSL_ENSURE( xColumnModel.is() && i_col < xColumnModel->getColumnCount(), + "UnoControlTableModel::getCellContent: request a column's value which the ColumnModel doesn't know about!" ); + #endif + } + else + { + o_cellContent = xDataModel->getCellData( nDataColumnIndex, i_row ); + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + } + + + void UnoControlTableModel::getCellToolTip( ColPos const i_col, RowPos const i_row, Any& o_cellToolTip ) + { + DBG_CHECK_ME(); + try + { + Reference< XGridDataModel > const xDataModel( m_aDataModel ); + ENSURE_OR_THROW( xDataModel.is(), "no data model anymore!" ); + + o_cellToolTip = xDataModel->getCellToolTip( i_col, i_row ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + } + + + Any UnoControlTableModel::getRowHeading( RowPos const i_rowPos ) const + { + DBG_CHECK_ME(); + + Any aRowHeading; + + Reference< XGridDataModel > const xDataModel( m_aDataModel ); + ENSURE_OR_RETURN( xDataModel.is(), "UnoControlTableModel::getRowHeading: no data model anymore!", aRowHeading ); + + try + { + aRowHeading = xDataModel->getRowHeading( i_rowPos ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + return aRowHeading; + } + + + namespace + { + void lcl_setColor( Any const & i_color, ::std::optional< ::Color > & o_convertedColor ) + { + if ( !i_color.hasValue() ) + o_convertedColor.reset(); + else + { + Color nColor = COL_TRANSPARENT; + if ( i_color >>= nColor ) + { + o_convertedColor = nColor; + } + else + { + OSL_ENSURE( false, "lcl_setColor: could not extract color value!" ); + } + } + } + } + + + ::std::optional< ::Color > UnoControlTableModel::getLineColor() const + { + DBG_CHECK_ME(); + return m_aGridLineColor; + } + + + void UnoControlTableModel::setLineColor( Any const & i_color ) + { + DBG_CHECK_ME(); + lcl_setColor( i_color, m_aGridLineColor ); + } + + + ::std::optional< ::Color > UnoControlTableModel::getHeaderBackgroundColor() const + { + DBG_CHECK_ME(); + return m_aHeaderBackgroundColor; + } + + + void UnoControlTableModel::setHeaderBackgroundColor( Any const & i_color ) + { + DBG_CHECK_ME(); + lcl_setColor( i_color, m_aHeaderBackgroundColor ); + } + + + ::std::optional< ::Color > UnoControlTableModel::getHeaderTextColor() const + { + DBG_CHECK_ME(); + return m_aHeaderTextColor; + } + + + ::std::optional< ::Color > UnoControlTableModel::getActiveSelectionBackColor() const + { + DBG_CHECK_ME(); + return m_aActiveSelectionBackColor; + } + + + ::std::optional< ::Color > UnoControlTableModel::getInactiveSelectionBackColor() const + { + DBG_CHECK_ME(); + return m_aInactiveSelectionBackColor; + } + + + ::std::optional< ::Color > UnoControlTableModel::getActiveSelectionTextColor() const + { + DBG_CHECK_ME(); + return m_aActiveSelectionTextColor; + } + + + ::std::optional< ::Color > UnoControlTableModel::getInactiveSelectionTextColor() const + { + DBG_CHECK_ME(); + return m_aInactiveSelectionTextColor; + } + + + void UnoControlTableModel::setHeaderTextColor( Any const & i_color ) + { + DBG_CHECK_ME(); + lcl_setColor( i_color, m_aHeaderTextColor ); + } + + + void UnoControlTableModel::setActiveSelectionBackColor( Any const & i_color ) + { + DBG_CHECK_ME(); + lcl_setColor( i_color, m_aActiveSelectionBackColor ); + } + + + void UnoControlTableModel::setInactiveSelectionBackColor( Any const & i_color ) + { + DBG_CHECK_ME(); + lcl_setColor( i_color, m_aInactiveSelectionBackColor ); + } + + + void UnoControlTableModel::setActiveSelectionTextColor( Any const & i_color ) + { + DBG_CHECK_ME(); + lcl_setColor( i_color, m_aActiveSelectionTextColor ); + } + + + void UnoControlTableModel::setInactiveSelectionTextColor( Any const & i_color ) + { + DBG_CHECK_ME(); + lcl_setColor( i_color, m_aInactiveSelectionTextColor ); + } + + + ::std::optional< ::Color > UnoControlTableModel::getTextColor() const + { + DBG_CHECK_ME(); + return m_aTextColor; + } + + + void UnoControlTableModel::setTextColor( Any const & i_color ) + { + DBG_CHECK_ME(); + lcl_setColor( i_color, m_aTextColor ); + } + + + ::std::optional< ::Color > UnoControlTableModel::getTextLineColor() const + { + DBG_CHECK_ME(); + return m_aTextColor; + } + + + void UnoControlTableModel::setTextLineColor( Any const & i_color ) + { + DBG_CHECK_ME(); + lcl_setColor( i_color, m_aTextLineColor ); + } + + + ::std::optional< ::std::vector< ::Color > > UnoControlTableModel::getRowBackgroundColors() const + { + DBG_CHECK_ME(); + return m_aRowColors; + } + + + void UnoControlTableModel::setRowBackgroundColors( css::uno::Any const & i_APIValue ) + { + DBG_CHECK_ME(); + Sequence< css::util::Color > aAPIColors; + if ( !( i_APIValue >>= aAPIColors ) ) + m_aRowColors.reset(); + else + { + ::std::vector< ::Color > aColors( aAPIColors.getLength() ); + std::transform(std::cbegin(aAPIColors), std::cend(aAPIColors), aColors.begin(), + [](const css::util::Color& rAPIColor) -> ::Color { return Color(ColorTransparency, rAPIColor); }); + m_aRowColors = aColors; + } + } + + + VerticalAlignment UnoControlTableModel::getVerticalAlign() const + { + DBG_CHECK_ME(); + return m_eVerticalAlign; + } + + + void UnoControlTableModel::setVerticalAlign( VerticalAlignment _xAlign ) + { + DBG_CHECK_ME(); + m_eVerticalAlign = _xAlign; + } + + + ColPos UnoControlTableModel::getColumnPos( UnoGridColumnFacade const & i_column ) const + { + DBG_CHECK_ME(); + ColPos nPos = 0; + for (auto const& col : aColumns) + { + if ( &i_column == col.get() ) + return nPos; + ++nPos; + } + OSL_ENSURE( false, "UnoControlTableModel::getColumnPos: column not found!" ); + return COL_INVALID; + } + + + ITableDataSort* UnoControlTableModel::getSortAdapter() + { + DBG_CHECK_ME(); + + Reference< XSortableGridData > const xSortAccess( getDataModel(), UNO_QUERY ); + if ( xSortAccess.is() ) + return this; + return nullptr; + } + + + bool UnoControlTableModel::isEnabled() const + { + DBG_CHECK_ME(); + return bEnabled; + } + + + void UnoControlTableModel::setEnabled( bool _bEnabled ) + { + DBG_CHECK_ME(); + bEnabled = _bEnabled; + } + + + void UnoControlTableModel::sortByColumn( ColPos const i_column, ColumnSortDirection const i_sortDirection ) + { + DBG_CHECK_ME(); + + try + { + Reference< XSortableGridData > const xSortAccess( getDataModel(), UNO_QUERY_THROW ); + xSortAccess->sortByColumn( i_column, i_sortDirection == ColumnSortAscending ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + } + + + ColumnSort UnoControlTableModel::getCurrentSortOrder() const + { + DBG_CHECK_ME(); + + ColumnSort currentSort; + try + { + Reference< XSortableGridData > const xSortAccess( getDataModel(), UNO_QUERY_THROW ); + Pair< ::sal_Int32, sal_Bool > const aCurrentSortOrder( xSortAccess->getCurrentSortOrder() ); + currentSort.nColumnPos = aCurrentSortOrder.First; + currentSort.eSortDirection = aCurrentSortOrder.Second ? ColumnSortAscending : ColumnSortDescending; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + return currentSort; + } + + + void UnoControlTableModel::notifyColumnChange( ColPos const i_columnPos, ColumnAttributeGroup const i_attributeGroup ) const + { + DBG_CHECK_ME(); + ENSURE_OR_RETURN_VOID( ( i_columnPos >= 0 ) && ( i_columnPos < getColumnCount() ), + "UnoControlTableModel::notifyColumnChange: invalid column index!" ); + + ModellListeners aListeners( m_aListeners ); + for (auto const& listener : aListeners) + { + listener->columnChanged( i_columnPos, i_attributeGroup ); + } + } + + + void UnoControlTableModel::notifyRowsInserted( GridDataEvent const & i_event ) const + { + // check sanity of the event + ENSURE_OR_RETURN_VOID( i_event.FirstRow >= 0, "UnoControlTableModel::notifyRowsInserted: invalid first row!" ); + ENSURE_OR_RETURN_VOID( i_event.LastRow >= i_event.FirstRow, "UnoControlTableModel::notifyRowsInserted: invalid row indexes!" ); + + // check own sanity + Reference< XGridColumnModel > const xColumnModel( m_aColumnModel ); + ENSURE_OR_RETURN_VOID( xColumnModel.is(), "UnoControlTableModel::notifyRowsInserted: no column model anymore!" ); + + Reference< XGridDataModel > const xDataModel( m_aDataModel ); + ENSURE_OR_RETURN_VOID( xDataModel.is(), "UnoControlTableModel::notifyRowsInserted: no data model anymore!" ); + + // implicitly add columns to the column model + // TODO: is this really a good idea? + sal_Int32 const dataColumnCount = xDataModel->getColumnCount(); + OSL_ENSURE( dataColumnCount > 0, "UnoControlTableModel::notifyRowsInserted: no columns at all?" ); + + sal_Int32 const modelColumnCount = xColumnModel->getColumnCount(); + if ( ( modelColumnCount == 0 ) && ( dataColumnCount > 0 ) ) + { + // TODO: shouldn't we clear the mutexes guard for this call? + xColumnModel->setDefaultColumns( dataColumnCount ); + } + + // multiplex the event to our own listeners + ModellListeners aListeners( m_aListeners ); + for (auto const& listener : aListeners) + { + listener->rowsInserted( i_event.FirstRow, i_event.LastRow ); + } + } + + + void UnoControlTableModel::notifyRowsRemoved( GridDataEvent const & i_event ) const + { + ModellListeners aListeners( m_aListeners ); + for (auto const& listener : aListeners) + { + listener->rowsRemoved( i_event.FirstRow, i_event.LastRow ); + } + } + + + void UnoControlTableModel::notifyDataChanged( css::awt::grid::GridDataEvent const & i_event ) const + { + RowPos const firstRow = i_event.FirstRow == -1 ? 0 : i_event.FirstRow; + RowPos const lastRow = i_event.FirstRow == -1 ? getRowCount() - 1 : i_event.LastRow; + + ModellListeners aListeners( m_aListeners ); + for (auto const& listener : aListeners) + { + listener->cellsUpdated( firstRow, lastRow ); + } + } + + + void UnoControlTableModel::notifyAllDataChanged() const + { + ModellListeners aListeners( m_aListeners ); + for (auto const& listener : aListeners) + { + listener->cellsUpdated( 0, getRowCount() - 1 ); + } + } + + +} // svt::table + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/unocontroltablemodel.hxx b/svtools/source/uno/unocontroltablemodel.hxx new file mode 100644 index 000000000..4a6935762 --- /dev/null +++ b/svtools/source/uno/unocontroltablemodel.hxx @@ -0,0 +1,181 @@ +/* -*- 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 <table/tablemodel.hxx> +#include <table/tablesort.hxx> +#include <tools/color.hxx> + +#include <com/sun/star/awt/grid/GridDataEvent.hpp> +#include <com/sun/star/awt/grid/XGridColumnModel.hpp> +#include <com/sun/star/awt/grid/XGridDataModel.hpp> +#include <com/sun/star/awt/grid/XGridColumn.hpp> +#include <com/sun/star/awt/grid/XGridColumn.hpp> +#include <com/sun/star/style/VerticalAlignment.hpp> +#include <cppuhelper/weakref.hxx> + + +namespace svt::table +{ + + + //= UnoControlTableModel + + class UnoGridColumnFacade; + class UnoControlTableModel : public ITableModel, public ITableDataSort + { + public: + UnoControlTableModel(); + virtual ~UnoControlTableModel() override; + + public: + // ITableModel overridables + virtual TableSize getColumnCount() const override; + virtual TableSize getRowCount() const override; + virtual bool hasColumnHeaders() const override; + virtual bool hasRowHeaders() const override; + virtual PColumnModel getColumnModel( ColPos column ) override; + virtual PTableRenderer getRenderer() const override; + virtual PTableInputHandler getInputHandler() const override; + virtual TableMetrics getRowHeight() const override; + virtual TableMetrics getColumnHeaderHeight() const override; + virtual TableMetrics getRowHeaderWidth() const override; + virtual ScrollbarVisibility getVerticalScrollbarVisibility() const override; + virtual ScrollbarVisibility getHorizontalScrollbarVisibility() const override; + virtual void addTableModelListener( const PTableModelListener& i_listener ) override; + virtual void removeTableModelListener( const PTableModelListener& i_listener ) override; + virtual void getCellContent( ColPos const i_col, RowPos const i_row, css::uno::Any& o_cellContent ) override; + virtual void getCellToolTip( ColPos const i_col, RowPos const i_row, css::uno::Any & o_cellToolTip ) override; + virtual css::uno::Any getRowHeading( RowPos const i_rowPos ) const override; + virtual ::std::optional< ::Color > getLineColor() const override; + virtual ::std::optional< ::Color > getHeaderBackgroundColor() const override; + virtual ::std::optional< ::Color > getHeaderTextColor() const override; + virtual ::std::optional< ::Color > getActiveSelectionBackColor() const override; + virtual ::std::optional< ::Color > getInactiveSelectionBackColor() const override; + virtual ::std::optional< ::Color > getActiveSelectionTextColor() const override; + virtual ::std::optional< ::Color > getInactiveSelectionTextColor() const override; + virtual ::std::optional< ::Color > getTextColor() const override; + virtual ::std::optional< ::Color > getTextLineColor() const override; + virtual ::std::optional< ::std::vector< ::Color > > + getRowBackgroundColors() const override; + virtual css::style::VerticalAlignment + getVerticalAlign() const override; + virtual ITableDataSort* getSortAdapter() override; + virtual bool isEnabled() const override; + + // ITableDataSort overridables + virtual void sortByColumn( ColPos const i_column, ColumnSortDirection const i_sortDirection ) override; + virtual ColumnSort getCurrentSortOrder() const override; + + // column write access + void appendColumn( css::uno::Reference< css::awt::grid::XGridColumn > const & i_column ); + void insertColumn( ColPos const i_position, css::uno::Reference< css::awt::grid::XGridColumn > const & i_column ); + void removeColumn( ColPos const i_position ); + void removeAllColumns(); + + // other operations + void setVerticalScrollbarVisibility( ScrollbarVisibility const i_visibility ); + void setHorizontalScrollbarVisibility( ScrollbarVisibility const i_visibility ); + + void setDataModel( css::uno::Reference< css::awt::grid::XGridDataModel > const & i_gridDataModel ); + bool hasDataModel() const; + css::uno::Reference< css::awt::grid::XGridDataModel > + getDataModel() const; + void setColumnModel( css::uno::Reference< css::awt::grid::XGridColumnModel > const & i_gridColumnModel ); + bool hasColumnModel() const; + css::uno::Reference< css::awt::grid::XGridColumnModel > + getColumnModel() const; + + void setRowHeaders(bool _bRowHeaders); + void setColumnHeaders(bool _bColumnHeaders); + + void setRowHeight( TableMetrics _nHeight ); + void setRowHeaderWidth( TableMetrics _nWidth ); + void setColumnHeaderHeight( TableMetrics _nHeight ); + + void setLineColor( css::uno::Any const & i_color ); + void setHeaderBackgroundColor( css::uno::Any const & i_color ); + void setHeaderTextColor( css::uno::Any const & i_color ); + void setActiveSelectionBackColor( css::uno::Any const & i_color ); + void setInactiveSelectionBackColor( css::uno::Any const & i_color ); + void setActiveSelectionTextColor( css::uno::Any const & i_color ); + void setInactiveSelectionTextColor( css::uno::Any const & i_color ); + void setTextColor( css::uno::Any const & i_color ); + void setTextLineColor( css::uno::Any const & i_color ); + void setRowBackgroundColors( css::uno::Any const & i_APIValue ); + + void setVerticalAlign(css::style::VerticalAlignment _rAlign); + void setEnabled( bool _bEnabled ); + + // multiplexing of XGridDataListener events + void notifyRowsInserted( css::awt::grid::GridDataEvent const & i_event ) const; + void notifyRowsRemoved( css::awt::grid::GridDataEvent const & i_event ) const; + void notifyDataChanged( css::awt::grid::GridDataEvent const & i_event ) const; + + /// retrieves the index of a column within the model + ColPos getColumnPos( UnoGridColumnFacade const & i_column ) const; + + /// notifies a change in a column belonging to the model + void notifyColumnChange( ColPos const i_columnPos, ColumnAttributeGroup const i_attributeGroup ) const; + + /** notifies a change in all data represented by the model. To be used if you cannot specified the changed data + in more detail. + */ + void notifyAllDataChanged() const; + + private: + void impl_notifyTableMetricsChanged() const; + + typedef ::std::vector< PTableModelListener > ModellListeners; + typedef ::std::vector< PColumnModel > ColumnModels; + + ColumnModels aColumns; + bool bHasColumnHeaders; + bool bHasRowHeaders; + ScrollbarVisibility eVScrollMode; + ScrollbarVisibility eHScrollMode; + PTableRenderer pRenderer; + PTableInputHandler pInputHandler; + TableMetrics nRowHeight; + TableMetrics nColumnHeaderHeight; + TableMetrics nRowHeaderWidth; + ::std::optional< ::Color > m_aGridLineColor; + ::std::optional< ::Color > m_aHeaderBackgroundColor; + ::std::optional< ::Color > m_aHeaderTextColor; + ::std::optional< ::Color > m_aActiveSelectionBackColor; + ::std::optional< ::Color > m_aInactiveSelectionBackColor; + ::std::optional< ::Color > m_aActiveSelectionTextColor; + ::std::optional< ::Color > m_aInactiveSelectionTextColor; + ::std::optional< ::Color > m_aTextColor; + ::std::optional< ::Color > m_aTextLineColor; + ::std::optional< ::std::vector< ::Color > > m_aRowColors; + css::style::VerticalAlignment m_eVerticalAlign; + bool bEnabled; + ModellListeners m_aListeners; + css::uno::WeakReference< css::awt::grid::XGridDataModel > m_aDataModel; + css::uno::WeakReference< css::awt::grid::XGridColumnModel > m_aColumnModel; + }; + + +} // svt::table + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/unoevent.cxx b/svtools/source/uno/unoevent.cxx new file mode 100644 index 000000000..30f4cbecb --- /dev/null +++ b/svtools/source/uno/unoevent.cxx @@ -0,0 +1,450 @@ +/* -*- 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 <com/sun/star/beans/PropertyValue.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> +#include <sfx2/event.hxx> +#include <svtools/unoevent.hxx> +#include <svl/macitem.hxx> + +using namespace ::com::sun::star; +using namespace css::uno; + +using css::container::NoSuchElementException; +using css::container::XNameReplace; +using css::lang::IllegalArgumentException; +using css::beans::PropertyValue; + + +constexpr OUStringLiteral sAPI_ServiceName = u"com.sun.star.container.XNameReplace"; +constexpr OUStringLiteral sEventType = u"EventType"; +constexpr OUStringLiteral sMacroName = u"MacroName"; +constexpr OUStringLiteral sLibrary = u"Library"; +constexpr OUStringLiteral sStarBasic = u"StarBasic"; +constexpr OUStringLiteral sScript = u"Script"; +constexpr OUStringLiteral sNone = u"None"; + +namespace { + +void getAnyFromMacro(Any& rAny, const SvxMacro& rMacro) +{ + bool bRetValueOK = false; // do we have a ret value? + + if (rMacro.HasMacro()) + { + switch (rMacro.GetScriptType()) + { + case STARBASIC: + { + // create sequence + Sequence<PropertyValue> aSequence( + // create type + { comphelper::makePropertyValue(sEventType, OUString(sStarBasic)), + // macro name + comphelper::makePropertyValue(sMacroName, rMacro.GetMacName()), + // library name + comphelper::makePropertyValue(sLibrary, rMacro.GetLibName()) }); + + rAny <<= aSequence; + bRetValueOK = true; + break; + } + case EXTENDED_STYPE: + { + // create sequence + Sequence<PropertyValue> aSequence( + // create type + { comphelper::makePropertyValue(sEventType, OUString(sScript)), + // macro name + comphelper::makePropertyValue(sScript, rMacro.GetMacName()) }); + + rAny <<= aSequence; + bRetValueOK = true; + break; + } + case JAVASCRIPT: + default: + OSL_FAIL("not implemented"); + } + } + // else: bRetValueOK not set + + // if we don't have a return value, make an empty one + if ( bRetValueOK) + return; + + // create "None" macro + Sequence<PropertyValue> aSequence{ comphelper::makePropertyValue(sEventType, OUString(sNone)) }; + rAny <<= aSequence; +} + +/// @throws IllegalArgumentException +void getMacroFromAny( + SvxMacro& rMacro, + const Any& rAny) +{ + // get sequence + Sequence<PropertyValue> aSequence; + rAny >>= aSequence; + + // process ... + bool bTypeOK = false; + bool bNone = false; // true if EventType=="None" + enum ScriptType eType = EXTENDED_STYPE; + OUString sScriptVal; + OUString sMacroVal; + OUString sLibVal; + for (const PropertyValue& aValue : std::as_const(aSequence)) + { + if (aValue.Name == sEventType) + { + OUString sTmp; + aValue.Value >>= sTmp; + if (sTmp == sStarBasic) + { + eType = STARBASIC; + bTypeOK = true; + } + else if (sTmp == "JavaScript") + { + eType = JAVASCRIPT; + bTypeOK = true; + } + else if (sTmp == sScript) + { + eType = EXTENDED_STYPE; + bTypeOK = true; + } + else if (sTmp == sNone) + { + bNone = true; + bTypeOK = true; + } + // else: unknown script type + } + else if (aValue.Name == sMacroName) + { + aValue.Value >>= sMacroVal; + } + else if (aValue.Name == sLibrary) + { + aValue.Value >>= sLibVal; + } + else if (aValue.Name == sScript) + { + aValue.Value >>= sScriptVal; + } + // else: unknown PropertyValue -> ignore + } + + if (!bTypeOK) + { + // no valid type: abort + throw IllegalArgumentException(); + } + + if (bNone) + { + // return empty macro + rMacro = SvxMacro( "", "" ); + } + else + { + if (eType == STARBASIC) + { + // create macro and return + SvxMacro aMacro(sMacroVal, sLibVal, eType); + rMacro = aMacro; + } + else if (eType == EXTENDED_STYPE) + { + SvxMacro aMacro(sScriptVal, sScript); + rMacro = aMacro; + } + else + { + // we can't process type: abort + // TODO: JavaScript macros + throw IllegalArgumentException(); + } + } +} + +} + +SvBaseEventDescriptor::SvBaseEventDescriptor( const SvEventDescription* pSupportedMacroItems ) : + mpSupportedMacroItems(pSupportedMacroItems), + mnMacroItems(0) +{ + assert(pSupportedMacroItems != nullptr && "Need a list of supported events!"); + + for( ; mpSupportedMacroItems[mnMacroItems].mnEvent != SvMacroItemId::NONE; mnMacroItems++) ; +} + + +SvBaseEventDescriptor::~SvBaseEventDescriptor() +{ +} + +void SvBaseEventDescriptor::replaceByName( + const OUString& rName, + const Any& rElement ) +{ + SvMacroItemId nMacroID = getMacroID(rName); + + // error checking + if (SvMacroItemId::NONE == nMacroID) + throw NoSuchElementException(); + if (rElement.getValueType() != getElementType()) + throw IllegalArgumentException(); + + // get sequence + Sequence<PropertyValue> aSequence; + rElement >>= aSequence; + + // perform replace (in subclass) + SvxMacro aMacro("",""); + getMacroFromAny(aMacro, rElement); + replaceByName(nMacroID, aMacro); +} + +Any SvBaseEventDescriptor::getByName( + const OUString& rName ) +{ + SvMacroItemId nMacroID = getMacroID(rName); + + // error checking + if (SvMacroItemId::NONE == nMacroID) + throw NoSuchElementException(); + + // perform get (in subclass) + Any aAny; + SvxMacro aMacro( "", "" ); + getByName(aMacro, nMacroID); + getAnyFromMacro(aAny, aMacro); + return aAny; +} + +Sequence<OUString> SvBaseEventDescriptor::getElementNames() +{ + // create and fill sequence + Sequence<OUString> aSequence(mnMacroItems); + auto aSequenceRange = asNonConstRange(aSequence); + for( sal_Int16 i = 0; i < mnMacroItems; i++) + { + aSequenceRange[i] = OUString::createFromAscii( mpSupportedMacroItems[i].mpEventName ); + } + + return aSequence; +} + +sal_Bool SvBaseEventDescriptor::hasByName( + const OUString& rName ) +{ + SvMacroItemId nMacroID = getMacroID(rName); + return (nMacroID != SvMacroItemId::NONE); +} + +Type SvBaseEventDescriptor::getElementType() +{ + return cppu::UnoType<Sequence<PropertyValue>>::get(); +} + +sal_Bool SvBaseEventDescriptor::hasElements() +{ + return mnMacroItems != 0; +} + +sal_Bool SvBaseEventDescriptor::supportsService(const OUString& rServiceName) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence<OUString> SvBaseEventDescriptor::getSupportedServiceNames() +{ + return { sAPI_ServiceName }; +} + +SvMacroItemId SvBaseEventDescriptor::mapNameToEventID(std::u16string_view rName) const +{ + // iterate over known event names + for(sal_Int16 i = 0; i < mnMacroItems; i++) + { + if( o3tl::equalsAscii(rName, mpSupportedMacroItems[i].mpEventName)) + { + return mpSupportedMacroItems[i].mnEvent; + } + } + + // not found -> return zero + return SvMacroItemId::NONE; +} + +SvMacroItemId SvBaseEventDescriptor::getMacroID(std::u16string_view rName) const +{ + return mapNameToEventID(rName); +} + +SvEventDescriptor::SvEventDescriptor( + XInterface& rParent, + const SvEventDescription* pSupportedMacroItems) : + SvBaseEventDescriptor(pSupportedMacroItems), + xParentRef(&rParent) +{ +} + + +SvEventDescriptor::~SvEventDescriptor() +{ + // automatically release xParentRef ! +} + +void SvEventDescriptor::replaceByName( + const SvMacroItemId nEvent, + const SvxMacro& rMacro) +{ + SvxMacroItem aItem(getMacroItemWhich()); + aItem.SetMacroTable(getMacroItem().GetMacroTable()); + aItem.SetMacro(nEvent, rMacro); + setMacroItem(aItem); +} + +void SvEventDescriptor::getByName( + SvxMacro& rMacro, + const SvMacroItemId nEvent ) +{ + const SvxMacroItem& rItem = getMacroItem(); + if( rItem.HasMacro( nEvent ) ) + rMacro = rItem.GetMacro(nEvent); + else + { + SvxMacro aEmptyMacro("", ""); + rMacro = aEmptyMacro; + } +} + +SvDetachedEventDescriptor::SvDetachedEventDescriptor( + const SvEventDescription* pSupportedMacroItems) : + SvBaseEventDescriptor(pSupportedMacroItems) +{ + aMacros.resize(mnMacroItems); +} + +SvDetachedEventDescriptor::~SvDetachedEventDescriptor() +{ +} + +sal_Int16 SvDetachedEventDescriptor::getIndex(const SvMacroItemId nID) const +{ + // iterate over supported events + sal_Int16 nIndex = 0; + while ( (mpSupportedMacroItems[nIndex].mnEvent != nID) && + (mpSupportedMacroItems[nIndex].mnEvent != SvMacroItemId::NONE) ) + { + nIndex++; + } + return (mpSupportedMacroItems[nIndex].mnEvent == nID) ? nIndex : -1; +} + +OUString SvDetachedEventDescriptor::getImplementationName() +{ + return "SvDetachedEventDescriptor"; +} + + +void SvDetachedEventDescriptor::replaceByName( + const SvMacroItemId nEvent, + const SvxMacro& rMacro) +{ + sal_Int16 nIndex = getIndex(nEvent); + if (-1 == nIndex) + throw IllegalArgumentException(); + + aMacros[nIndex].reset( new SvxMacro(rMacro.GetMacName(), rMacro.GetLibName(), + rMacro.GetScriptType() ) ); +} + + +void SvDetachedEventDescriptor::getByName( + SvxMacro& rMacro, + const SvMacroItemId nEvent ) +{ + sal_Int16 nIndex = getIndex(nEvent); + if (-1 == nIndex ) + throw NoSuchElementException(); + + if( aMacros[nIndex] ) + rMacro = *aMacros[nIndex]; +} + +bool SvDetachedEventDescriptor::hasById( + const SvMacroItemId nEvent ) const /// item ID of event +{ + sal_Int16 nIndex = getIndex(nEvent); + if (-1 == nIndex) + throw IllegalArgumentException(); + + return (nullptr != aMacros[nIndex]) && aMacros[nIndex]->HasMacro(); +} + + +SvMacroTableEventDescriptor::SvMacroTableEventDescriptor(const SvEventDescription* pSupportedMacroItems) : + SvDetachedEventDescriptor(pSupportedMacroItems) +{ +} + +SvMacroTableEventDescriptor::SvMacroTableEventDescriptor( + const SvxMacroTableDtor& rMacroTable, + const SvEventDescription* pSupportedMacroItems) : + SvDetachedEventDescriptor(pSupportedMacroItems) +{ + assert(mpSupportedMacroItems); + for(sal_Int16 i = 0; mpSupportedMacroItems[i].mnEvent != SvMacroItemId::NONE; i++) + { + const SvMacroItemId nEvent = mpSupportedMacroItems[i].mnEvent; + const SvxMacro* pMacro = rMacroTable.Get(nEvent); + if (nullptr != pMacro) + replaceByName(nEvent, *pMacro); + } +} + +SvMacroTableEventDescriptor::~SvMacroTableEventDescriptor() +{ +} + +void SvMacroTableEventDescriptor::copyMacrosIntoTable( + SvxMacroTableDtor& rMacroTable) +{ + for(sal_Int16 i = 0; mpSupportedMacroItems[i].mnEvent != SvMacroItemId::NONE; i++) + { + const SvMacroItemId nEvent = mpSupportedMacroItems[i].mnEvent; + if (hasById(nEvent)) + { + SvxMacro& rMacro = rMacroTable.Insert(nEvent, SvxMacro("", "")); + getByName(rMacro, nEvent); + } + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/unogridcolumnfacade.cxx b/svtools/source/uno/unogridcolumnfacade.cxx new file mode 100644 index 000000000..c41600cee --- /dev/null +++ b/svtools/source/uno/unogridcolumnfacade.cxx @@ -0,0 +1,310 @@ +/* -*- 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 "unogridcolumnfacade.hxx" +#include "unocontroltablemodel.hxx" + +#include <com/sun/star/awt/grid/XGridColumn.hpp> +#include <com/sun/star/awt/grid/XGridColumnListener.hpp> + +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <vcl/svapp.hxx> +#include <cppuhelper/implbase.hxx> + + +namespace svt::table +{ + + + using css::uno::Reference; + using css::awt::grid::XGridColumn; + using css::uno::Exception; + using css::awt::grid::XGridColumnListener; + using css::lang::EventObject; + using css::awt::grid::GridColumnEvent; + using css::style::HorizontalAlignment_LEFT; + using css::style::HorizontalAlignment; + + + namespace + { + template< class T1, class T2 > + void lcl_set( Reference< XGridColumn > const & i_column, void ( SAL_CALL XGridColumn::*i_setter )( T1 ), + T2 i_value ) + { + try + { + (i_column.get()->*i_setter) ( i_value ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + } + + template< class ATTRIBUTE_TYPE > + ATTRIBUTE_TYPE lcl_get( Reference< XGridColumn > const & i_column, ATTRIBUTE_TYPE ( SAL_CALL XGridColumn::*i_getter )() ) + { + ATTRIBUTE_TYPE value = ATTRIBUTE_TYPE(); + try + { + value = (i_column.get()->*i_getter)(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + return value; + } + } + + + //= ColumnChangeMultiplexer + + typedef ::cppu::WeakImplHelper < XGridColumnListener + > ColumnChangeMultiplexer_Base; + class ColumnChangeMultiplexer :public ColumnChangeMultiplexer_Base + { + public: + explicit ColumnChangeMultiplexer( UnoGridColumnFacade& i_colImpl ); + ColumnChangeMultiplexer(const ColumnChangeMultiplexer&) = delete; + ColumnChangeMultiplexer& operator=(const ColumnChangeMultiplexer&) = delete; + + void dispose(); + + protected: + virtual ~ColumnChangeMultiplexer() override; + + // XGridColumnListener + virtual void SAL_CALL columnChanged( const GridColumnEvent& i_event ) override; + + // XEventListener + virtual void SAL_CALL disposing( const EventObject& i_event ) override; + + private: + UnoGridColumnFacade* m_pColumnImplementation; + }; + + + ColumnChangeMultiplexer::ColumnChangeMultiplexer( UnoGridColumnFacade& i_colImpl ) + :m_pColumnImplementation( &i_colImpl ) + { + } + + + ColumnChangeMultiplexer::~ColumnChangeMultiplexer() + { + } + + + void ColumnChangeMultiplexer::dispose() + { + DBG_TESTSOLARMUTEX(); + m_pColumnImplementation = nullptr; + } + + + void SAL_CALL ColumnChangeMultiplexer::columnChanged( const GridColumnEvent& i_event ) + { + if ( i_event.AttributeName == "DataColumnIndex" ) + { + SolarMutexGuard aGuard; + if ( m_pColumnImplementation != nullptr ) + m_pColumnImplementation->dataColumnIndexChanged(); + return; + } + + ColumnAttributeGroup nChangedAttributes( ColumnAttributeGroup::NONE ); + + if ( i_event.AttributeName == "HorizontalAlign" ) + nChangedAttributes |= ColumnAttributeGroup::APPEARANCE; + + if ( i_event.AttributeName == "ColumnWidth" + || i_event.AttributeName == "MaxWidth" + || i_event.AttributeName == "MinWidth" + || i_event.AttributeName == "PreferredWidth" + || i_event.AttributeName == "Resizeable" + || i_event.AttributeName == "Flexibility" + ) + nChangedAttributes |= ColumnAttributeGroup::WIDTH; + + OSL_ENSURE( nChangedAttributes != ColumnAttributeGroup::NONE, + "ColumnChangeMultiplexer::columnChanged: unknown column attributed changed!" ); + + SolarMutexGuard aGuard; + if ( m_pColumnImplementation != nullptr ) + m_pColumnImplementation->columnChanged( nChangedAttributes ); + } + + + void SAL_CALL ColumnChangeMultiplexer::disposing( const EventObject& ) + { + } + + + //= UnoGridColumnFacade + + + UnoGridColumnFacade::UnoGridColumnFacade( UnoControlTableModel const & i_owner, Reference< XGridColumn > const & i_gridColumn ) + :m_pOwner( &i_owner ) + ,m_nDataColumnIndex( -1 ) + ,m_xGridColumn( i_gridColumn, css::uno::UNO_SET_THROW ) + ,m_pChangeMultiplexer( new ColumnChangeMultiplexer( *this ) ) + { + m_xGridColumn->addGridColumnListener( m_pChangeMultiplexer ); + impl_updateDataColumnIndex_nothrow(); + } + + + UnoGridColumnFacade::~UnoGridColumnFacade() + { + } + + + void UnoGridColumnFacade::dispose() + { + DBG_TESTSOLARMUTEX(); + ENSURE_OR_RETURN_VOID( m_pOwner != nullptr, "UnoGridColumnFacade::dispose: already disposed!" ); + + m_xGridColumn->removeGridColumnListener( m_pChangeMultiplexer ); + m_pChangeMultiplexer->dispose(); + m_pChangeMultiplexer.clear(); + m_xGridColumn.clear(); + m_pOwner = nullptr; + } + + + void UnoGridColumnFacade::impl_updateDataColumnIndex_nothrow() + { + m_nDataColumnIndex = -1; + ENSURE_OR_RETURN_VOID( m_xGridColumn.is(), "UnoGridColumnFacade: already disposed!" ); + try + { + m_nDataColumnIndex = m_xGridColumn->getDataColumnIndex(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + } + + + void UnoGridColumnFacade::dataColumnIndexChanged() + { + DBG_TESTSOLARMUTEX(); + impl_updateDataColumnIndex_nothrow(); + if ( m_pOwner != nullptr ) + m_pOwner->notifyAllDataChanged(); + } + + + void UnoGridColumnFacade::columnChanged( ColumnAttributeGroup const i_attributeGroup ) + { + DBG_TESTSOLARMUTEX(); + if ( m_pOwner != nullptr ) + m_pOwner->notifyColumnChange( m_pOwner->getColumnPos( *this ), i_attributeGroup ); + } + + + OUString UnoGridColumnFacade::getName() const + { + OUString sName; + ENSURE_OR_RETURN( m_xGridColumn.is(), "UnoGridColumnFacade: already disposed!", sName ); + try + { + sName = m_xGridColumn->getTitle(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + return sName; + } + + + OUString UnoGridColumnFacade::getHelpText() const + { + OUString sHelpText; + ENSURE_OR_RETURN( m_xGridColumn.is(), "UnoGridColumnFacade: already disposed!", sHelpText ); + try + { + sHelpText = m_xGridColumn->getHelpText(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + return sHelpText; + } + + + bool UnoGridColumnFacade::isResizable() const + { + ENSURE_OR_RETURN( m_xGridColumn.is(), "UnoGridColumnFacade: already disposed!", false ); + return lcl_get( m_xGridColumn, &XGridColumn::getResizeable ); + } + + + sal_Int32 UnoGridColumnFacade::getFlexibility() const + { + ENSURE_OR_RETURN( m_xGridColumn.is(), "UnoGridColumnFacade: already disposed!", 1 ); + return lcl_get( m_xGridColumn, &XGridColumn::getFlexibility ); + } + + + TableMetrics UnoGridColumnFacade::getWidth() const + { + ENSURE_OR_RETURN( m_xGridColumn.is(), "UnoGridColumnFacade: already disposed!", 0 ); + return lcl_get( m_xGridColumn, &XGridColumn::getColumnWidth ); + } + + + void UnoGridColumnFacade::setWidth( TableMetrics _nWidth ) + { + ENSURE_OR_RETURN_VOID( m_xGridColumn.is(), "UnoGridColumnFacade: already disposed!" ); + lcl_set( m_xGridColumn, &XGridColumn::setColumnWidth, _nWidth ); + } + + + TableMetrics UnoGridColumnFacade::getMinWidth() const + { + ENSURE_OR_RETURN( m_xGridColumn.is(), "UnoGridColumnFacade: already disposed!", 0 ); + return lcl_get( m_xGridColumn, &XGridColumn::getMinWidth ); + } + + + TableMetrics UnoGridColumnFacade::getMaxWidth() const + { + ENSURE_OR_RETURN( m_xGridColumn.is(), "UnoGridColumnFacade: already disposed!", 0 ); + return lcl_get( m_xGridColumn, &XGridColumn::getMaxWidth ); + } + + + css::style::HorizontalAlignment UnoGridColumnFacade::getHorizontalAlign() + { + ENSURE_OR_RETURN( m_xGridColumn.is(), "UnoGridColumnFacade: already disposed!", HorizontalAlignment_LEFT ); + return lcl_get( m_xGridColumn, &XGridColumn::getHorizontalAlign ); + } + + +} // svt::table + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/unogridcolumnfacade.hxx b/svtools/source/uno/unogridcolumnfacade.hxx new file mode 100644 index 000000000..672397276 --- /dev/null +++ b/svtools/source/uno/unogridcolumnfacade.hxx @@ -0,0 +1,89 @@ +/* -*- 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 <table/tablemodel.hxx> + +#include <com/sun/star/awt/grid/XGridColumn.hpp> +#include <com/sun/star/style/HorizontalAlignment.hpp> + +#include <rtl/ref.hxx> + + +namespace svt::table +{ + + + //= UnoGridColumnFacade + + class ColumnChangeMultiplexer; + class UnoControlTableModel; + class UnoGridColumnFacade :public IColumnModel + { + public: + UnoGridColumnFacade( + UnoControlTableModel const & i_owner, + css::uno::Reference< css::awt::grid::XGridColumn > const & i_gridColumn + ); + virtual ~UnoGridColumnFacade() override; + UnoGridColumnFacade(const UnoGridColumnFacade&) = delete; + UnoGridColumnFacade& operator=(const UnoGridColumnFacade&) = delete; + + // IColumnModel overridables + virtual OUString getName() const override; + virtual OUString getHelpText() const override; + virtual bool isResizable() const override; + virtual sal_Int32 getFlexibility() const override; + virtual TableMetrics getWidth() const override; + virtual void setWidth( TableMetrics _nWidth ) override; + virtual TableMetrics getMinWidth() const override; + virtual TableMetrics getMaxWidth() const override; + virtual css::style::HorizontalAlignment getHorizontalAlign() override; + + /** disposes the column wrapper + + Note that the XGridColumn which is wrapped by the instance is <strong>not</strong> disposed, as we + do not own it. + */ + void dispose(); + + sal_Int32 + getDataColumnIndex() const { return m_nDataColumnIndex; } + + // callbacks for the XGridColumnListener + void columnChanged( ColumnAttributeGroup const i_attributeGroup ); + void dataColumnIndexChanged(); + + private: + void impl_updateDataColumnIndex_nothrow(); + + private: + UnoControlTableModel const * m_pOwner; + sal_Int32 m_nDataColumnIndex; + css::uno::Reference< css::awt::grid::XGridColumn > m_xGridColumn; + ::rtl::Reference< ColumnChangeMultiplexer > m_pChangeMultiplexer; + }; + + +} // svt::table + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/unoiface.cxx b/svtools/source/uno/unoiface.cxx new file mode 100644 index 000000000..71bc18150 --- /dev/null +++ b/svtools/source/uno/unoiface.cxx @@ -0,0 +1,50 @@ +/* -*- 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 <unoiface.hxx> +#include "svtxgridcontrol.hxx" +#include <table/tablecontrol.hxx> + +// help function for the toolkit... + +extern "C" { + +SAL_DLLPUBLIC_EXPORT vcl::Window* CreateWindow( rtl::Reference<VCLXWindow>* ppNewComp, const css::awt::WindowDescriptor* pDescriptor, vcl::Window* pParent, WinBits nWinBits ) +{ + vcl::Window* pWindow = nullptr; + OUString aServiceName( pDescriptor->WindowServiceName ); + if ( aServiceName.equalsIgnoreAsciiCase( "Grid" ) ) + { + if ( pParent ) + { + pWindow = VclPtr< ::svt::table::TableControl>::Create(pParent, nWinBits); + *ppNewComp = new SVTXGridControl; + } + else + { + *ppNewComp = nullptr; + return nullptr; + } + } + return pWindow; +} + +} // extern "C" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/unoimap.cxx b/svtools/source/uno/unoimap.cxx new file mode 100644 index 000000000..4d64cc8b4 --- /dev/null +++ b/svtools/source/uno/unoimap.cxx @@ -0,0 +1,720 @@ +/* -*- 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 <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/document/XEventsSupplier.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/drawing/PointSequence.hpp> +#include <comphelper/servicehelper.hxx> +#include <comphelper/propertysethelper.hxx> +#include <comphelper/propertysetinfo.hxx> +#include <cppuhelper/weakagg.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <algorithm> +#include <osl/diagnose.h> +#include <rtl/ref.hxx> +#include <svtools/unoevent.hxx> +#include <svtools/unoimap.hxx> +#include <vcl/imap.hxx> +#include <vcl/imapcirc.hxx> +#include <vcl/imaprect.hxx> +#include <vcl/imappoly.hxx> + +using namespace comphelper; +using namespace cppu; +using namespace com::sun::star; +using namespace css::uno; +using namespace css::lang; +using namespace css::container; +using namespace css::beans; +using namespace css::document; +using namespace css::drawing; + +const sal_Int32 HANDLE_URL = 1; +const sal_Int32 HANDLE_DESCRIPTION = 2; +const sal_Int32 HANDLE_TARGET = 3; +const sal_Int32 HANDLE_NAME = 4; +const sal_Int32 HANDLE_ISACTIVE = 5; +const sal_Int32 HANDLE_POLYGON = 6; +const sal_Int32 HANDLE_CENTER = 7; +const sal_Int32 HANDLE_RADIUS = 8; +const sal_Int32 HANDLE_BOUNDARY = 9; +const sal_Int32 HANDLE_TITLE = 10; + +namespace { + +class SvUnoImageMapObject : public OWeakAggObject, + public XEventsSupplier, + public XServiceInfo, + public PropertySetHelper, + public XTypeProvider, + public XUnoTunnel +{ +public: + SvUnoImageMapObject( IMapObjectType nType, const SvEventDescription* pSupportedMacroItems ); + SvUnoImageMapObject( const IMapObject& rMapObject, const SvEventDescription* pSupportedMacroItems ); + + UNO3_GETIMPLEMENTATION_DECL( SvUnoImageMapObject ) + + std::unique_ptr<IMapObject> createIMapObject() const; + + rtl::Reference<SvMacroTableEventDescriptor> mxEvents; + + // overridden helpers from PropertySetHelper + virtual void _setPropertyValues( const PropertyMapEntry** ppEntries, const Any* pValues ) override; + virtual void _getPropertyValues( const PropertyMapEntry** ppEntries, Any* pValue ) override; + + // XInterface + virtual Any SAL_CALL queryAggregation( const Type & rType ) override; + virtual Any SAL_CALL queryInterface( const Type & rType ) override; + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + + // XTypeProvider + virtual Sequence< Type > SAL_CALL getTypes( ) override; + virtual Sequence< sal_Int8 > SAL_CALL getImplementationId( ) override; + + // XEventsSupplier + virtual Reference< css::container::XNameReplace > SAL_CALL getEvents( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + +private: + static rtl::Reference<PropertySetInfo> createPropertySetInfo( IMapObjectType nType ); + + + IMapObjectType mnType; + + OUString maURL; + OUString maAltText; + OUString maDesc; + OUString maTarget; + OUString maName; + bool mbIsActive; + awt::Rectangle maBoundary; + awt::Point maCenter; + sal_Int32 mnRadius; + PointSequence maPolygon; +}; + +} + +UNO3_GETIMPLEMENTATION_IMPL( SvUnoImageMapObject ); + +rtl::Reference<PropertySetInfo> SvUnoImageMapObject::createPropertySetInfo( IMapObjectType nType ) +{ + switch( nType ) + { + case IMapObjectType::Polygon: + { + static PropertyMapEntry const aPolygonObj_Impl[] = + { + { OUString("URL"), HANDLE_URL, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("Title"), HANDLE_TITLE, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("Description"), HANDLE_DESCRIPTION, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("Target"), HANDLE_TARGET, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("Name"), HANDLE_NAME, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("IsActive"), HANDLE_ISACTIVE, cppu::UnoType<bool>::get(), 0, 0 }, + { OUString("Polygon"), HANDLE_POLYGON, cppu::UnoType<PointSequence>::get(), 0, 0 }, + }; + + return rtl::Reference<PropertySetInfo>(new PropertySetInfo( aPolygonObj_Impl )); + } + case IMapObjectType::Circle: + { + static PropertyMapEntry const aCircleObj_Impl[] = + { + { OUString("URL"), HANDLE_URL, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("Title"), HANDLE_TITLE, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("Description"), HANDLE_DESCRIPTION, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("Target"), HANDLE_TARGET, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("Name"), HANDLE_NAME, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("IsActive"), HANDLE_ISACTIVE, cppu::UnoType<bool>::get(), 0, 0 }, + { OUString("Center"), HANDLE_CENTER, cppu::UnoType<awt::Point>::get(), 0, 0 }, + { OUString("Radius"), HANDLE_RADIUS, cppu::UnoType<sal_Int32>::get(), 0, 0 }, + }; + + return rtl::Reference<PropertySetInfo>(new PropertySetInfo( aCircleObj_Impl )); + } + case IMapObjectType::Rectangle: + default: + { + static PropertyMapEntry const aRectangleObj_Impl[] = + { + { OUString("URL"), HANDLE_URL, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("Title"), HANDLE_TITLE, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("Description"), HANDLE_DESCRIPTION, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("Target"), HANDLE_TARGET, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("Name"), HANDLE_NAME, cppu::UnoType<OUString>::get(), 0, 0 }, + { OUString("IsActive"), HANDLE_ISACTIVE, cppu::UnoType<bool>::get(), 0, 0 }, + { OUString("Boundary"), HANDLE_BOUNDARY, cppu::UnoType<awt::Rectangle>::get(), 0, 0 }, + }; + + return rtl::Reference<PropertySetInfo>(new PropertySetInfo( aRectangleObj_Impl )); + } + } +} + +SvUnoImageMapObject::SvUnoImageMapObject( IMapObjectType nType, const SvEventDescription* pSupportedMacroItems ) +: PropertySetHelper( createPropertySetInfo( nType ) ), + mnType( nType ) +, mbIsActive( true ) +, mnRadius( 0 ) +{ + mxEvents = new SvMacroTableEventDescriptor( pSupportedMacroItems ); +} + +SvUnoImageMapObject::SvUnoImageMapObject( const IMapObject& rMapObject, const SvEventDescription* pSupportedMacroItems ) +: PropertySetHelper( createPropertySetInfo( rMapObject.GetType() ) ), + mnType( rMapObject.GetType() ) +, mbIsActive( true ) +, mnRadius( 0 ) +{ + maURL = rMapObject.GetURL(); + maAltText = rMapObject.GetAltText(); + maDesc = rMapObject.GetDesc(); + maTarget = rMapObject.GetTarget(); + maName = rMapObject.GetName(); + mbIsActive = rMapObject.IsActive(); + + switch( mnType ) + { + case IMapObjectType::Rectangle: + { + const tools::Rectangle aRect( static_cast<const IMapRectangleObject*>(&rMapObject)->GetRectangle(false) ); + maBoundary.X = aRect.Left(); + maBoundary.Y = aRect.Top(); + maBoundary.Width = aRect.GetWidth(); + maBoundary.Height = aRect.GetHeight(); + } + break; + case IMapObjectType::Circle: + { + mnRadius = static_cast<const IMapCircleObject*>(&rMapObject)->GetRadius(false); + const Point aPoint( static_cast<const IMapCircleObject*>(&rMapObject)->GetCenter(false) ); + + maCenter.X = aPoint.X(); + maCenter.Y = aPoint.Y(); + } + break; + case IMapObjectType::Polygon: + default: + { + const tools::Polygon aPoly( static_cast<const IMapPolygonObject*>(&rMapObject)->GetPolygon(false) ); + + const sal_uInt16 nCount = aPoly.GetSize(); + maPolygon.realloc( nCount ); + awt::Point* pPoints = maPolygon.getArray(); + + for( sal_uInt16 nPoint = 0; nPoint < nCount; nPoint++ ) + { + const Point& rPoint = aPoly.GetPoint( nPoint ); + pPoints->X = rPoint.X(); + pPoints->Y = rPoint.Y(); + + pPoints++; + } + } + } + + mxEvents = new SvMacroTableEventDescriptor( rMapObject.GetMacroTable(), pSupportedMacroItems ); +} + +std::unique_ptr<IMapObject> SvUnoImageMapObject::createIMapObject() const +{ + const OUString aURL( maURL ); + const OUString aAltText( maAltText ); + const OUString aDesc( maDesc ); + const OUString aTarget( maTarget ); + const OUString aName( maName ); + + std::unique_ptr<IMapObject> pNewIMapObject; + + switch( mnType ) + { + case IMapObjectType::Rectangle: + { + const tools::Rectangle aRect( maBoundary.X, maBoundary.Y, maBoundary.X + maBoundary.Width - 1, maBoundary.Y + maBoundary.Height - 1 ); + pNewIMapObject.reset(new IMapRectangleObject( aRect, aURL, aAltText, aDesc, aTarget, aName, mbIsActive, false )); + } + break; + + case IMapObjectType::Circle: + { + const Point aCenter( maCenter.X, maCenter.Y ); + pNewIMapObject.reset(new IMapCircleObject( aCenter, mnRadius, aURL, aAltText, aDesc, aTarget, aName, mbIsActive, false )); + } + break; + + case IMapObjectType::Polygon: + default: + { + const sal_uInt16 nCount = static_cast<sal_uInt16>(maPolygon.getLength()); + + tools::Polygon aPoly( nCount ); + for( sal_uInt16 nPoint = 0; nPoint < nCount; nPoint++ ) + { + Point aPoint( maPolygon[nPoint].X, maPolygon[nPoint].Y ); + aPoly.SetPoint( aPoint, nPoint ); + } + + aPoly.Optimize( PolyOptimizeFlags::CLOSE ); + pNewIMapObject.reset(new IMapPolygonObject( aPoly, aURL, aAltText, aDesc, aTarget, aName, mbIsActive, false )); + } + break; + } + + SvxMacroTableDtor aMacroTable; + mxEvents->copyMacrosIntoTable(aMacroTable); + pNewIMapObject->SetMacroTable( aMacroTable ); + + return pNewIMapObject; +} + +// XInterface + +Any SAL_CALL SvUnoImageMapObject::queryInterface( const Type & rType ) +{ + return OWeakAggObject::queryInterface( rType ); +} + +Any SAL_CALL SvUnoImageMapObject::queryAggregation( const Type & rType ) +{ + Any aAny; + + if( rType == cppu::UnoType<XServiceInfo>::get()) + aAny <<= Reference< XServiceInfo >(this); + else if( rType == cppu::UnoType<XTypeProvider>::get()) + aAny <<= Reference< XTypeProvider >(this); + else if( rType == cppu::UnoType<XPropertySet>::get()) + aAny <<= Reference< XPropertySet >(this); + else if( rType == cppu::UnoType<XEventsSupplier>::get()) + aAny <<= Reference< XEventsSupplier >(this); + else if( rType == cppu::UnoType<XMultiPropertySet>::get()) + aAny <<= Reference< XMultiPropertySet >(this); + else if( rType == cppu::UnoType<XUnoTunnel>::get()) + aAny <<= Reference< XUnoTunnel >(this); + else + aAny = OWeakAggObject::queryAggregation( rType ); + + return aAny; +} + +void SAL_CALL SvUnoImageMapObject::acquire() noexcept +{ + OWeakAggObject::acquire(); +} + +void SAL_CALL SvUnoImageMapObject::release() noexcept +{ + OWeakAggObject::release(); +} + +uno::Sequence< uno::Type > SAL_CALL SvUnoImageMapObject::getTypes() +{ + static const uno::Sequence< uno::Type > aTypes { + cppu::UnoType<XAggregation>::get(), + cppu::UnoType<XEventsSupplier>::get(), + cppu::UnoType<XServiceInfo>::get(), + cppu::UnoType<XPropertySet>::get(), + cppu::UnoType<XMultiPropertySet>::get(), + cppu::UnoType<XTypeProvider>::get(), + cppu::UnoType<XUnoTunnel>::get() }; + return aTypes; +} + +uno::Sequence< sal_Int8 > SAL_CALL SvUnoImageMapObject::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XServiceInfo +sal_Bool SAL_CALL SvUnoImageMapObject::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL SvUnoImageMapObject::getSupportedServiceNames() +{ + Sequence< OUString > aSNS( 2 ); + aSNS.getArray()[0] = "com.sun.star.image.ImageMapObject"; + switch( mnType ) + { + case IMapObjectType::Polygon: + default: + aSNS.getArray()[1] = "com.sun.star.image.ImageMapPolygonObject"; + break; + case IMapObjectType::Rectangle: + aSNS.getArray()[1] = "com.sun.star.image.ImageMapRectangleObject"; + break; + case IMapObjectType::Circle: + aSNS.getArray()[1] = "com.sun.star.image.ImageMapCircleObject"; + break; + } + return aSNS; +} + +OUString SAL_CALL SvUnoImageMapObject::getImplementationName() +{ + switch( mnType ) + { + case IMapObjectType::Polygon: + default: + return "org.openoffice.comp.svt.ImageMapPolygonObject"; + case IMapObjectType::Circle: + return "org.openoffice.comp.svt.ImageMapCircleObject"; + case IMapObjectType::Rectangle: + return "org.openoffice.comp.svt.ImageMapRectangleObject"; + } +} + +// overridden helpers from PropertySetHelper +void SvUnoImageMapObject::_setPropertyValues( const PropertyMapEntry** ppEntries, const Any* pValues ) +{ + bool bOk = false; + + while( *ppEntries ) + { + switch( (*ppEntries)->mnHandle ) + { + case HANDLE_URL: + bOk = *pValues >>= maURL; + break; + case HANDLE_TITLE: + bOk = *pValues >>= maAltText; + break; + case HANDLE_DESCRIPTION: + bOk = *pValues >>= maDesc; + break; + case HANDLE_TARGET: + bOk = *pValues >>= maTarget; + break; + case HANDLE_NAME: + bOk = *pValues >>= maName; + break; + case HANDLE_ISACTIVE: + bOk = *pValues >>= mbIsActive; + break; + case HANDLE_BOUNDARY: + bOk = *pValues >>= maBoundary; + break; + case HANDLE_CENTER: + bOk = *pValues >>= maCenter; + break; + case HANDLE_RADIUS: + bOk = *pValues >>= mnRadius; + break; + case HANDLE_POLYGON: + bOk = *pValues >>= maPolygon; + break; + default: + OSL_FAIL( "SvUnoImageMapObject::_setPropertyValues: unexpected property handle" ); + break; + } + + if( !bOk ) + throw IllegalArgumentException(); + + ppEntries++; + pValues++; + } +} + +void SvUnoImageMapObject::_getPropertyValues( const PropertyMapEntry** ppEntries, Any* pValues ) +{ + while( *ppEntries ) + { + switch( (*ppEntries)->mnHandle ) + { + case HANDLE_URL: + *pValues <<= maURL; + break; + case HANDLE_TITLE: + *pValues <<= maAltText; + break; + case HANDLE_DESCRIPTION: + *pValues <<= maDesc; + break; + case HANDLE_TARGET: + *pValues <<= maTarget; + break; + case HANDLE_NAME: + *pValues <<= maName; + break; + case HANDLE_ISACTIVE: + *pValues <<= mbIsActive; + break; + case HANDLE_BOUNDARY: + *pValues <<= maBoundary; + break; + case HANDLE_CENTER: + *pValues <<= maCenter; + break; + case HANDLE_RADIUS: + *pValues <<= mnRadius; + break; + case HANDLE_POLYGON: + *pValues <<= maPolygon; + break; + default: + OSL_FAIL( "SvUnoImageMapObject::_getPropertyValues: unexpected property handle" ); + break; + } + + ppEntries++; + pValues++; + } +} + + +Reference< XNameReplace > SAL_CALL SvUnoImageMapObject::getEvents() +{ + return mxEvents; +} + +namespace { + +class SvUnoImageMap : public WeakImplHelper< XIndexContainer, XServiceInfo, XUnoTunnel > +{ +public: + explicit SvUnoImageMap(); + SvUnoImageMap( const ImageMap& rMap, const SvEventDescription* pSupportedMacroItems ); + + void fillImageMap( ImageMap& rMap ) const; + /// @throws IllegalArgumentException + static SvUnoImageMapObject* getObject( const Any& aElement ); + + UNO3_GETIMPLEMENTATION_DECL( SvUnoImageMap ) + + // XIndexContainer + virtual void SAL_CALL insertByIndex( sal_Int32 Index, const Any& Element ) override; + virtual void SAL_CALL removeByIndex( sal_Int32 Index ) override; + + // XIndexReplace + virtual void SAL_CALL replaceByIndex( sal_Int32 Index, const Any& Element ) override; + + // XIndexAccess + virtual sal_Int32 SAL_CALL getCount( ) override; + virtual Any SAL_CALL getByIndex( sal_Int32 Index ) override; + + // XElementAccess + virtual Type SAL_CALL getElementType( ) override; + virtual sal_Bool SAL_CALL hasElements( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + +private: + OUString maName; + + std::vector< rtl::Reference<SvUnoImageMapObject> > maObjectList; +}; + +} + +UNO3_GETIMPLEMENTATION_IMPL( SvUnoImageMap ); + +SvUnoImageMap::SvUnoImageMap() +{ +} + +SvUnoImageMap::SvUnoImageMap( const ImageMap& rMap, const SvEventDescription* pSupportedMacroItems ) +{ + maName = rMap.GetName(); + + const std::size_t nCount = rMap.GetIMapObjectCount(); + for( std::size_t nPos = 0; nPos < nCount; nPos++ ) + { + IMapObject* pMapObject = rMap.GetIMapObject( nPos ); + rtl::Reference<SvUnoImageMapObject> xUnoObj = new SvUnoImageMapObject( *pMapObject, pSupportedMacroItems ); + maObjectList.push_back( xUnoObj ); + } +} + +SvUnoImageMapObject* SvUnoImageMap::getObject( const Any& aElement ) +{ + Reference< XInterface > xObject; + aElement >>= xObject; + + SvUnoImageMapObject* pObject = comphelper::getFromUnoTunnel<SvUnoImageMapObject>( xObject ); + if( nullptr == pObject ) + throw IllegalArgumentException(); + + return pObject; +} + +// XIndexContainer +void SAL_CALL SvUnoImageMap::insertByIndex( sal_Int32 nIndex, const Any& Element ) +{ + SvUnoImageMapObject* pObject = getObject( Element ); + const sal_Int32 nCount = maObjectList.size(); + if( nullptr == pObject || nIndex > nCount ) + throw IndexOutOfBoundsException(); + + if( nIndex == nCount ) + maObjectList.emplace_back(pObject ); + else + { + auto aIter = maObjectList.begin(); + std::advance(aIter, nIndex); + maObjectList.insert( aIter, pObject ); + } +} + +void SAL_CALL SvUnoImageMap::removeByIndex( sal_Int32 nIndex ) +{ + const sal_Int32 nCount = maObjectList.size(); + if( nIndex >= nCount ) + throw IndexOutOfBoundsException(); + + if( nCount - 1 == nIndex ) + { + maObjectList.pop_back(); + } + else + { + auto aIter = maObjectList.begin(); + std::advance(aIter, nIndex); + maObjectList.erase( aIter ); + } +} + +// XIndexReplace +void SAL_CALL SvUnoImageMap::replaceByIndex( sal_Int32 nIndex, const Any& Element ) +{ + SvUnoImageMapObject* pObject = getObject( Element ); + const sal_Int32 nCount = maObjectList.size(); + if( nullptr == pObject || nIndex >= nCount ) + throw IndexOutOfBoundsException(); + + auto aIter = maObjectList.begin(); + std::advance(aIter, nIndex); + *aIter = pObject; +} + +// XIndexAccess +sal_Int32 SAL_CALL SvUnoImageMap::getCount( ) +{ + return maObjectList.size(); +} + +Any SAL_CALL SvUnoImageMap::getByIndex( sal_Int32 nIndex ) +{ + const sal_Int32 nCount = maObjectList.size(); + if( nIndex >= nCount ) + throw IndexOutOfBoundsException(); + + auto aIter = maObjectList.begin(); + std::advance(aIter, nIndex); + + Reference< XPropertySet > xObj( *aIter ); + return Any( xObj ); +} + +// XElementAccess +Type SAL_CALL SvUnoImageMap::getElementType( ) +{ + return cppu::UnoType<XPropertySet>::get(); +} + +sal_Bool SAL_CALL SvUnoImageMap::hasElements( ) +{ + return (!maObjectList.empty()); +} + +// XServiceInfo +OUString SAL_CALL SvUnoImageMap::getImplementationName( ) +{ + return "org.openoffice.comp.svt.SvUnoImageMap"; +} + +sal_Bool SAL_CALL SvUnoImageMap::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL SvUnoImageMap::getSupportedServiceNames( ) +{ + return { "com.sun.star.image.ImageMap" }; +} + +void SvUnoImageMap::fillImageMap( ImageMap& rMap ) const +{ + rMap.ClearImageMap(); + + rMap.SetName( maName ); + + for (auto const& elem : maObjectList) + { + std::unique_ptr<IMapObject> pNewMapObject = elem->createIMapObject(); + rMap.InsertIMapObject( std::move(pNewMapObject) ); + } +} + + +// factory helper methods + + +Reference< XInterface > SvUnoImageMapRectangleObject_createInstance( const SvEventDescription* pSupportedMacroItems ) +{ + return static_cast<XWeak*>(new SvUnoImageMapObject( IMapObjectType::Rectangle, pSupportedMacroItems )); +} + +Reference< XInterface > SvUnoImageMapCircleObject_createInstance( const SvEventDescription* pSupportedMacroItems ) +{ + return static_cast<XWeak*>(new SvUnoImageMapObject( IMapObjectType::Circle, pSupportedMacroItems )); +} + +Reference< XInterface > SvUnoImageMapPolygonObject_createInstance( const SvEventDescription* pSupportedMacroItems ) +{ + return static_cast<XWeak*>(new SvUnoImageMapObject( IMapObjectType::Polygon, pSupportedMacroItems )); +} + +Reference< XInterface > SvUnoImageMap_createInstance() +{ + return static_cast<XWeak*>(new SvUnoImageMap); +} + +Reference< XInterface > SvUnoImageMap_createInstance( const ImageMap& rMap, const SvEventDescription* pSupportedMacroItems ) +{ + return static_cast<XWeak*>(new SvUnoImageMap( rMap, pSupportedMacroItems )); +} + +bool SvUnoImageMap_fillImageMap( const Reference< XInterface >& xImageMap, ImageMap& rMap ) +{ + SvUnoImageMap* pUnoImageMap = comphelper::getFromUnoTunnel<SvUnoImageMap>( xImageMap ); + if( nullptr == pUnoImageMap ) + return false; + + pUnoImageMap->fillImageMap( rMap ); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/wizard/unowizard.cxx b/svtools/source/uno/wizard/unowizard.cxx new file mode 100644 index 000000000..0a73eb49a --- /dev/null +++ b/svtools/source/uno/wizard/unowizard.cxx @@ -0,0 +1,452 @@ +/* -*- 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 <string_view> + +#include "wizardshell.hxx" + +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/ucb/AlreadyInitializedException.hpp> +#include <com/sun/star/ui/dialogs/XWizard.hpp> +#include <com/sun/star/ui/dialogs/XWizardController.hpp> +#include <com/sun/star/ui/dialogs/WizardButton.hpp> +#include <com/sun/star/util/InvalidStateException.hpp> + +#include <comphelper/proparrhlp.hxx> +#include <cppuhelper/implbase.hxx> +#include <svtools/genericunodialog.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/diagnose_ex.h> +#include <osl/mutex.hxx> +#include <vcl/svapp.hxx> +#include <tools/urlobj.hxx> + +using namespace ::com::sun::star; +using namespace ::svt::uno; + +namespace { + + using css::uno::Reference; + using css::uno::XInterface; + using css::uno::UNO_QUERY; + using css::uno::Any; + using css::uno::Sequence; + using css::ui::dialogs::XWizard; + using css::beans::XPropertySetInfo; + using css::uno::XComponentContext; + using css::beans::Property; + using css::lang::IllegalArgumentException; + using css::ucb::AlreadyInitializedException; + using css::ui::dialogs::XWizardController; + using css::ui::dialogs::XWizardPage; + using css::container::NoSuchElementException; + using css::util::InvalidStateException; + using css::awt::XWindow; + + namespace WizardButton = css::ui::dialogs::WizardButton; + + WizardButtonFlags lcl_convertWizardButtonToWZB( const sal_Int16 i_nWizardButton ) + { + switch ( i_nWizardButton ) + { + case WizardButton::NONE: return WizardButtonFlags::NONE; + case WizardButton::NEXT: return WizardButtonFlags::NEXT; + case WizardButton::PREVIOUS: return WizardButtonFlags::PREVIOUS; + case WizardButton::FINISH: return WizardButtonFlags::FINISH; + case WizardButton::CANCEL: return WizardButtonFlags::CANCEL; + case WizardButton::HELP: return WizardButtonFlags::HELP; + } + OSL_FAIL( "lcl_convertWizardButtonToWZB: invalid WizardButton constant!" ); + return WizardButtonFlags::NONE; + } + + typedef ::cppu::ImplInheritanceHelper < ::svt::OGenericUnoDialog + , ui::dialogs::XWizard + > Wizard_Base; + class Wizard; + typedef ::comphelper::OPropertyArrayUsageHelper< Wizard > Wizard_PBase; + class Wizard : public Wizard_Base + , public Wizard_PBase + { + public: + explicit Wizard( const css::uno::Reference< css::uno::XComponentContext >& i_rContext ); + + // lang::XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // beans::XPropertySet + virtual css::uno::Reference< beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override; + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + + // OPropertyArrayUsageHelper + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override; + + // ui::dialogs::XWizard + virtual OUString SAL_CALL getHelpURL() override; + virtual void SAL_CALL setHelpURL( const OUString& _helpurl ) override; + virtual css::uno::Reference< awt::XWindow > SAL_CALL getDialogWindow() override; + virtual css::uno::Reference< ui::dialogs::XWizardPage > SAL_CALL getCurrentPage( ) override; + virtual void SAL_CALL enableButton( ::sal_Int16 WizardButton, sal_Bool Enable ) override; + virtual void SAL_CALL setDefaultButton( ::sal_Int16 WizardButton ) override; + virtual sal_Bool SAL_CALL travelNext( ) override; + virtual sal_Bool SAL_CALL travelPrevious( ) override; + virtual void SAL_CALL enablePage( ::sal_Int16 PageID, sal_Bool Enable ) override; + virtual void SAL_CALL updateTravelUI( ) override; + virtual sal_Bool SAL_CALL advanceTo( ::sal_Int16 PageId ) override; + virtual sal_Bool SAL_CALL goBackTo( ::sal_Int16 PageId ) override; + virtual void SAL_CALL activatePath( ::sal_Int16 PathIndex, sal_Bool Final ) override; + + // ui::dialogs::XExecutableDialog + virtual void SAL_CALL setTitle( const OUString& aTitle ) override; + virtual ::sal_Int16 SAL_CALL execute( ) override; + + // lang::XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + protected: + virtual ~Wizard() override; + + protected: + virtual std::unique_ptr<weld::DialogController> createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) override; + + private: + css::uno::Sequence< css::uno::Sequence< sal_Int16 > > m_aWizardSteps; + css::uno::Reference< ui::dialogs::XWizardController > m_xController; + OUString m_sHelpURL; + }; + + Wizard::Wizard( const Reference< XComponentContext >& _rxContext ) + :Wizard_Base( _rxContext ) + { + } + + OUString lcl_getHelpURL( std::string_view sHelpId ) + { + OUStringBuffer aBuffer; + OUString aTmp( + OStringToOUString( sHelpId, RTL_TEXTENCODING_UTF8 ) ); + INetURLObject aHID( aTmp ); + if ( aHID.GetProtocol() == INetProtocol::NotValid ) + aBuffer.append( INET_HID_SCHEME ); + aBuffer.append( aTmp ); + return aBuffer.makeStringAndClear(); + } + + Wizard::~Wizard() + { + if (m_xDialog) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if (m_xDialog) + { + m_sHelpURL = lcl_getHelpURL(m_xDialog->get_help_id()); + destroyDialog(); + } + } + } + + void lcl_checkPaths( const Sequence< Sequence< sal_Int16 > >& i_rPaths, const Reference< XInterface >& i_rContext ) + { + // need at least one path + if ( !i_rPaths.hasElements() ) + throw IllegalArgumentException( OUString(), i_rContext, 2 ); + + // each path must be of length 1, at least + sal_Int32 i = 0; + for ( const Sequence< sal_Int16 >& rPath : i_rPaths ) + { + if ( !rPath.hasElements() ) + throw IllegalArgumentException( OUString(), i_rContext, 2 ); + + // page IDs must be in ascending order + auto pPageId = std::adjacent_find(rPath.begin(), rPath.end(), std::greater_equal<sal_Int16>()); + if (pPageId != rPath.end()) + { + throw IllegalArgumentException( + "Path " + OUString::number(i) + + ": invalid page ID sequence - each page ID must be greater than the previous one.", + i_rContext, 2 ); + } + ++i; + } + + // if we have one path, that's okay + if ( i_rPaths.getLength() == 1 ) + return; + + // if we have multiple paths, they must start with the same page id + const sal_Int16 nFirstPageId = i_rPaths[0][0]; + if (std::any_of(i_rPaths.begin(), i_rPaths.end(), + [nFirstPageId](const Sequence< sal_Int16 >& rPath) { return rPath[0] != nFirstPageId; })) + throw IllegalArgumentException( + "All paths must start with the same page id.", + i_rContext, 2 ); + } + + void SAL_CALL Wizard::initialize( const Sequence< Any >& i_Arguments ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( m_bInitialized ) + throw AlreadyInitializedException( OUString(), *this ); + + if ( i_Arguments.getLength() != 2 ) + throw IllegalArgumentException( OUString(), *this, -1 ); + + // the second argument must be a XWizardController, for each constructor + m_xController.set( i_Arguments[1], UNO_QUERY ); + if ( !m_xController.is() ) + throw IllegalArgumentException( OUString(), *this, 2 ); + + // the first arg is either a single path (short[]), or multiple paths (short[][]) + Sequence< sal_Int16 > aSinglePath; + i_Arguments[0] >>= aSinglePath; + Sequence< Sequence< sal_Int16 > > aMultiplePaths; + i_Arguments[0] >>= aMultiplePaths; + + if ( !aMultiplePaths.hasElements() ) + { + aMultiplePaths = { aSinglePath }; + } + lcl_checkPaths( aMultiplePaths, *this ); + // if we survived this, the paths are valid, and we're done here ... + m_aWizardSteps = aMultiplePaths; + + m_bInitialized = true; + } + + OString lcl_getHelpId( std::u16string_view _rHelpURL ) + { + INetURLObject aHID( _rHelpURL ); + if ( aHID.GetProtocol() == INetProtocol::Hid ) + return OUStringToOString( aHID.GetURLPath(), RTL_TEXTENCODING_UTF8 ); + else + return OUStringToOString( _rHelpURL, RTL_TEXTENCODING_UTF8 ); + } + + std::unique_ptr<weld::DialogController> Wizard::createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) + { + auto xDialog = std::make_unique<WizardShell>(Application::GetFrameWeld(rParent), m_xController, m_aWizardSteps); + xDialog->set_help_id(lcl_getHelpId(m_sHelpURL)); + xDialog->setTitleBase( m_sTitle ); + return xDialog; + } + + OUString SAL_CALL Wizard::getImplementationName() + { + return "com.sun.star.comp.svtools.uno.Wizard"; + } + + Sequence< OUString > SAL_CALL Wizard::getSupportedServiceNames() + { + return { "com.sun.star.ui.dialogs.Wizard" }; + } + + Reference< XPropertySetInfo > SAL_CALL Wizard::getPropertySetInfo() + { + return createPropertySetInfo( getInfoHelper() ); + } + + ::cppu::IPropertyArrayHelper& SAL_CALL Wizard::getInfoHelper() + { + return *getArrayHelper(); + } + + ::cppu::IPropertyArrayHelper* Wizard::createArrayHelper( ) const + { + Sequence< Property > aProps; + describeProperties( aProps ); + return new ::cppu::OPropertyArrayHelper( aProps ); + } + + OUString SAL_CALL Wizard::getHelpURL() + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + if (!m_xDialog) + return m_sHelpURL; + + return lcl_getHelpURL(m_xDialog->get_help_id()); + } + + void SAL_CALL Wizard::setHelpURL( const OUString& i_HelpURL ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + if (!m_xDialog) + m_sHelpURL = i_HelpURL; + else + m_xDialog->set_help_id(lcl_getHelpId(i_HelpURL)); + } + + Reference< XWindow > SAL_CALL Wizard::getDialogWindow() + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + ENSURE_OR_RETURN( m_xDialog, "Wizard::getDialogWindow: illegal call (execution did not start, yet)!", nullptr ); + return m_xDialog->getDialog()->GetXWindow(); + } + + void SAL_CALL Wizard::enableButton( ::sal_Int16 i_WizardButton, sal_Bool i_Enable ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get()); + ENSURE_OR_RETURN_VOID( pWizardImpl, "Wizard::enableButtons: invalid dialog implementation!" ); + + pWizardImpl->enableButtons( lcl_convertWizardButtonToWZB( i_WizardButton ), i_Enable ); + } + + void SAL_CALL Wizard::setDefaultButton( ::sal_Int16 i_WizardButton ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get()); + ENSURE_OR_RETURN_VOID( pWizardImpl, "Wizard::setDefaultButton: invalid dialog implementation!" ); + + pWizardImpl->defaultButton( lcl_convertWizardButtonToWZB( i_WizardButton ) ); + } + + sal_Bool SAL_CALL Wizard::travelNext( ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get()); + ENSURE_OR_RETURN_FALSE( pWizardImpl, "Wizard::travelNext: invalid dialog implementation!" ); + + return pWizardImpl->travelNext(); + } + + sal_Bool SAL_CALL Wizard::travelPrevious( ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get()); + ENSURE_OR_RETURN_FALSE( pWizardImpl, "Wizard::travelPrevious: invalid dialog implementation!" ); + + return pWizardImpl->travelPrevious(); + } + + void SAL_CALL Wizard::enablePage( ::sal_Int16 i_PageID, sal_Bool i_Enable ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get()); + ENSURE_OR_RETURN_VOID( pWizardImpl, "Wizard::enablePage: invalid dialog implementation!" ); + + if ( !pWizardImpl->knowsPage( i_PageID ) ) + throw NoSuchElementException( OUString(), *this ); + + if ( i_PageID == pWizardImpl->getCurrentPage() ) + throw InvalidStateException( OUString(), *this ); + + pWizardImpl->enablePage( i_PageID, i_Enable ); + } + + void SAL_CALL Wizard::updateTravelUI( ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get()); + ENSURE_OR_RETURN_VOID( pWizardImpl, "Wizard::updateTravelUI: invalid dialog implementation!" ); + + pWizardImpl->updateTravelUI(); + } + + sal_Bool SAL_CALL Wizard::advanceTo( ::sal_Int16 i_PageId ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get()); + ENSURE_OR_RETURN_FALSE( pWizardImpl, "Wizard::advanceTo: invalid dialog implementation!" ); + + return pWizardImpl->advanceTo( i_PageId ); + } + + sal_Bool SAL_CALL Wizard::goBackTo( ::sal_Int16 i_PageId ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get()); + ENSURE_OR_RETURN_FALSE( pWizardImpl, "Wizard::goBackTo: invalid dialog implementation!" ); + + return pWizardImpl->goBackTo( i_PageId ); + } + + Reference< XWizardPage > SAL_CALL Wizard::getCurrentPage( ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get()); + ENSURE_OR_RETURN( pWizardImpl, "Wizard::getCurrentPage: invalid dialog implementation!", Reference< XWizardPage >() ); + + return pWizardImpl->getCurrentWizardPage(); + } + + void SAL_CALL Wizard::activatePath( ::sal_Int16 i_PathIndex, sal_Bool i_Final ) + { + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + if ( ( i_PathIndex < 0 ) || ( i_PathIndex >= m_aWizardSteps.getLength() ) ) + throw NoSuchElementException( OUString(), *this ); + + WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get()); + ENSURE_OR_RETURN_VOID( pWizardImpl, "Wizard::activatePath: invalid dialog implementation!" ); + + pWizardImpl->activatePath( i_PathIndex, i_Final ); + } + + void SAL_CALL Wizard::setTitle( const OUString& i_Title ) + { + // simply disambiguate + Wizard_Base::OGenericUnoDialog::setTitle( i_Title ); + } + + ::sal_Int16 SAL_CALL Wizard::execute( ) + { + return Wizard_Base::OGenericUnoDialog::execute(); + } +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_svtools_uno_Wizard_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new Wizard(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/wizard/wizardpagecontroller.cxx b/svtools/source/uno/wizard/wizardpagecontroller.cxx new file mode 100644 index 000000000..69b864fbf --- /dev/null +++ b/svtools/source/uno/wizard/wizardpagecontroller.cxx @@ -0,0 +1,130 @@ +/* -*- 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 "wizardpagecontroller.hxx" +#include "wizardshell.hxx" + +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/diagnose_ex.h> + + +namespace svt::uno +{ + + + using css::uno::Reference; + using css::uno::UNO_SET_THROW; + using css::uno::Exception; + using css::ui::dialogs::XWizardController; + using css::awt::XWindow; + + using namespace ::com::sun::star; + + + //= WizardPageController + + + WizardPageController::WizardPageController(weld::Container* pParent, const Reference< XWizardController >& i_rController, + const sal_Int16 i_nPageId ) + :m_xController( i_rController ) + ,m_xWizardPage() + { + ENSURE_OR_THROW( m_xController.is(), "no controller" ); + try + { + // Plug a toplevel SalFrame into the native page which can host our awt widgetry + m_xWizardPage.set(m_xController->createPage(pParent->CreateChildFrame(), i_nPageId), UNO_SET_THROW); + + Reference< XWindow > xPageWindow(m_xWizardPage->getWindow(), UNO_SET_THROW); + xPageWindow->setVisible( true ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + } + + WizardPageController::~WizardPageController() + { + try + { + if ( m_xWizardPage.is() ) + m_xWizardPage->dispose(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + } + + void WizardPageController::initializePage() + { + if ( !m_xWizardPage.is() ) + return; + + try + { + m_xWizardPage->activatePage(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + } + + bool WizardPageController::commitPage( vcl::WizardTypes::CommitPageReason i_eReason ) + { + if ( !m_xWizardPage.is() ) + return true; + + try + { + return m_xWizardPage->commitPage( WizardShell::convertCommitReasonToTravelType( i_eReason ) ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + + return true; + } + + bool WizardPageController::canAdvance() const + { + if ( !m_xWizardPage.is() ) + return true; + + try + { + return m_xWizardPage->canAdvance(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + + return true; + } + + +} // namespace svt::uno + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/wizard/wizardpagecontroller.hxx b/svtools/source/uno/wizard/wizardpagecontroller.hxx new file mode 100644 index 000000000..68d724591 --- /dev/null +++ b/svtools/source/uno/wizard/wizardpagecontroller.hxx @@ -0,0 +1,62 @@ +/* -*- 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 <vcl/wizardmachine.hxx> +#include <com/sun/star/ui/dialogs/XWizardController.hpp> + +namespace svt::uno +{ + + + class WizardShell; + + + //= WizardPageController + + class WizardPageController : public vcl::IWizardPageController + { + public: + WizardPageController( + weld::Container* pParent, + const css::uno::Reference< css::ui::dialogs::XWizardController >& i_rController, + const sal_Int16 i_nPageId + ); + virtual ~WizardPageController(); + + // IWizardPageController overridables + virtual void initializePage() override; + virtual bool commitPage( vcl::WizardTypes::CommitPageReason _eReason ) override; + virtual bool canAdvance() const override; + + const css::uno::Reference< css::ui::dialogs::XWizardPage >& + getWizardPage() const { return m_xWizardPage; } + + private: + const css::uno::Reference< css::ui::dialogs::XWizardController > m_xController; + css::uno::Reference< css::ui::dialogs::XWizardPage > m_xWizardPage; + }; + + +} // namespace svt::uno + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/wizard/wizardshell.cxx b/svtools/source/uno/wizard/wizardshell.cxx new file mode 100644 index 000000000..7c946f0cf --- /dev/null +++ b/svtools/source/uno/wizard/wizardshell.cxx @@ -0,0 +1,257 @@ +/* -*- 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 "wizardshell.hxx" +#include "wizardpagecontroller.hxx" + +#include <tools/diagnose_ex.h> + +#include <com/sun/star/ui/dialogs/WizardTravelType.hpp> + +using vcl::RoadmapWizardTypes::WizardPath; + +namespace svt::uno +{ + + + using css::uno::Reference; + using css::uno::Exception; + using css::uno::Sequence; + using css::ui::dialogs::XWizardController; + using css::ui::dialogs::XWizardPage; + + namespace WizardTravelType = css::ui::dialogs::WizardTravelType; + + + namespace + { + + sal_Int16 lcl_determineFirstPageID( const Sequence< Sequence< sal_Int16 > >& i_rPaths ) + { + ENSURE_OR_THROW( i_rPaths.hasElements() && i_rPaths[0].hasElements(), "illegal paths" ); + return i_rPaths[0][0]; + } + } + + //= WizardShell + WizardShell::WizardShell(weld::Window* i_pParent, const Reference< XWizardController >& i_rController, + const Sequence< Sequence< sal_Int16 > >& i_rPaths) + :WizardShell_Base( i_pParent ) + ,m_xController( i_rController ) + ,m_nFirstPageID( lcl_determineFirstPageID( i_rPaths ) ) + { + ENSURE_OR_THROW( m_xController.is(), "invalid controller" ); + + // declare the paths + for ( sal_Int32 i=0; i<i_rPaths.getLength(); ++i ) + { + const Sequence< sal_Int16 >& rPath( i_rPaths[i] ); + WizardPath aPath( rPath.getLength() ); + std::transform(rPath.begin(), rPath.end(), aPath.begin(), + [this](const sal_Int16 nPageId) -> WizardPath::value_type { return impl_pageIdToState(nPageId); }); + declarePath( i, aPath ); + } + + // create the first page, to know the page size + GetOrCreatePage( impl_pageIdToState( i_rPaths[0][0] ) ); + m_xAssistant->set_current_page(0); + + // some defaults + enableAutomaticNextButtonState(); + } + + short WizardShell::run() + { + ActivatePage(); + return WizardShell_Base::run(); + } + + sal_Int16 WizardShell::convertCommitReasonToTravelType( const CommitPageReason i_eReason ) + { + switch ( i_eReason ) + { + case vcl::WizardTypes::eTravelForward: + return WizardTravelType::FORWARD; + + case vcl::WizardTypes::eTravelBackward: + return WizardTravelType::BACKWARD; + + case vcl::WizardTypes::eFinish: + return WizardTravelType::FINISH; + + default: + break; + } + OSL_FAIL( "WizardShell::convertCommitReasonToTravelType: unsupported CommitPageReason!" ); + return WizardTravelType::FINISH; + } + + + void WizardShell::enterState( WizardState i_nState ) + { + WizardShell_Base::enterState( i_nState ); + + if ( !m_xController.is() ) + return; + + try + { + m_xController->onActivatePage( impl_stateToPageId( i_nState ) ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + } + + + bool WizardShell::leaveState( WizardState i_nState ) + { + if ( !WizardShell_Base::leaveState( i_nState ) ) + return false; + + if ( !m_xController.is() ) + return true; + + try + { + m_xController->onDeactivatePage( impl_stateToPageId( i_nState ) ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + + return true; + } + + + PWizardPageController WizardShell::impl_getController(BuilderPage* i_pPage) const + { + Page2ControllerMap::const_iterator pos = m_aPageControllers.find( i_pPage ); + ENSURE_OR_RETURN( pos != m_aPageControllers.end(), "WizardShell::impl_getController: no controller for this page!", PWizardPageController() ); + return pos->second; + } + + + Reference< XWizardPage > WizardShell::getCurrentWizardPage() const + { + const WizardState eState = getCurrentState(); + + PWizardPageController pController( impl_getController( GetPage( eState ) ) ); + ENSURE_OR_RETURN( pController, "WizardShell::getCurrentWizardPage: invalid page/controller!", nullptr ); + + return pController->getWizardPage(); + } + + void WizardShell::enablePage( const sal_Int16 i_nPageID, const bool i_bEnable ) + { + enableState( impl_pageIdToState( i_nPageID ), i_bEnable ); + } + + namespace + { + class EmptyPage : public BuilderPage + { + public: + EmptyPage(weld::Widget* pParent, weld::DialogController* pController) + : BuilderPage(pParent, pController, "svt/ui/emptypage.ui", "EmptyPage") + { + m_xContainer->set_size_request(m_xContainer->get_approximate_digit_width() * 70, + m_xContainer->get_text_height() * 10); + } + weld::Container* GetContainer() const { return m_xContainer.get(); } + }; + } + + std::unique_ptr<BuilderPage> WizardShell::createPage( WizardState i_nState ) + { + ENSURE_OR_RETURN( m_xController.is(), "WizardShell::createPage: no WizardController!", nullptr ); + + sal_Int16 nPageId = impl_stateToPageId(i_nState); + + OString sIdent(OString::number(nPageId)); + weld::Container* pPageContainer = m_xAssistant->append_page(sIdent); + + auto xPage = std::make_unique<EmptyPage>(pPageContainer, this); + auto pController = std::make_shared<WizardPageController>(xPage->GetContainer(), m_xController, nPageId); + + m_aPageControllers[xPage.get()] = pController; + + return xPage; + } + + vcl::IWizardPageController* WizardShell::getPageController(BuilderPage* i_pCurrentPage) const + { + return impl_getController( i_pCurrentPage ).get(); + } + + OUString WizardShell::getStateDisplayName( WizardState i_nState ) const + { + try + { + if ( m_xController.is() ) + return m_xController->getPageTitle( impl_stateToPageId( i_nState ) ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + // fallback for ill-behaved clients: the numeric state + return OUString::number(i_nState); + } + + + bool WizardShell::canAdvance() const + { + try + { + if ( m_xController.is() && !m_xController->canAdvance() ) + return false; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + + return WizardShell_Base::canAdvance(); + } + + + bool WizardShell::onFinish() + { + try + { + if ( m_xController.is() && !m_xController->confirmFinish() ) + return false; + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("svtools.uno"); + } + + return WizardShell_Base::onFinish(); + } + + +} // namespace svt::uno + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/uno/wizard/wizardshell.hxx b/svtools/source/uno/wizard/wizardshell.hxx new file mode 100644 index 000000000..a563ab954 --- /dev/null +++ b/svtools/source/uno/wizard/wizardshell.hxx @@ -0,0 +1,129 @@ +/* -*- 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 <com/sun/star/ui/dialogs/XWizardController.hpp> +#include <vcl/roadmapwizard.hxx> +#include <map> +#include <memory> + +using vcl::WizardTypes::WizardState; +using vcl::WizardTypes::CommitPageReason; + +namespace svt::uno +{ + + + class WizardPageController; + typedef std::shared_ptr< WizardPageController > PWizardPageController; + + + //= WizardShell + + typedef ::vcl::RoadmapWizardMachine WizardShell_Base; + class WizardShell : public WizardShell_Base + { + public: + WizardShell( + weld::Window* pParent, + const css::uno::Reference< css::ui::dialogs::XWizardController >& i_rController, + const css::uno::Sequence< css::uno::Sequence< sal_Int16 > >& i_rPaths + ); + + // Dialog overridables + virtual short run() override; + + // OWizardMachine overridables + virtual std::unique_ptr<BuilderPage> createPage( WizardState i_nState ) override; + virtual void enterState( WizardState i_nState ) override; + virtual bool leaveState( WizardState i_nState ) override; + virtual OUString getStateDisplayName( WizardState i_nState ) const override; + virtual bool canAdvance() const override; + virtual bool onFinish() override; + virtual vcl::IWizardPageController* getPageController(BuilderPage* pCurrentPage) const override; + + static sal_Int16 convertCommitReasonToTravelType( const CommitPageReason i_eReason ); + + // operations + bool advanceTo( const sal_Int16 i_nPageId ) + { + return skipUntil( impl_pageIdToState( i_nPageId ) ); + } + bool goBackTo( const sal_Int16 i_nPageId ) + { + return skipBackwardUntil( impl_pageIdToState( i_nPageId ) ); + } + using WizardShell_Base::travelNext; + using WizardShell_Base::travelPrevious; + + void activatePath( const sal_Int16 i_nPathID, const bool i_bFinal ) + { + WizardShell_Base::activatePath( vcl::RoadmapWizardTypes::PathId( i_nPathID ), i_bFinal ); + } + + css::uno::Reference< css::ui::dialogs::XWizardPage > + getCurrentWizardPage() const; + + sal_Int16 getCurrentPage() const + { + return impl_stateToPageId( getCurrentState() ); + } + + void enablePage( const sal_Int16 i_PageID, const bool i_Enable ); + + bool knowsPage( const sal_Int16 i_nPageID ) const + { + return knowsState( impl_pageIdToState( i_nPageID ) ); + } + + private: + sal_Int16 impl_stateToPageId( const WizardState i_nState ) const + { + return static_cast< sal_Int16 >( i_nState + m_nFirstPageID ); + } + + WizardState impl_pageIdToState( const sal_Int16 i_nPageId ) const + { + return static_cast<WizardState>(i_nPageId - m_nFirstPageID); + } + + PWizardPageController impl_getController(BuilderPage* i_pPage) const; + + // prevent outside access to some base class members + using WizardShell_Base::skip; + using WizardShell_Base::skipUntil; + using WizardShell_Base::skipBackwardUntil; + using WizardShell_Base::getCurrentState; + using WizardShell_Base::activatePath; + + private: + typedef std::map<BuilderPage*, PWizardPageController> Page2ControllerMap; + + const css::uno::Reference< css::ui::dialogs::XWizardController > m_xController; + const sal_Int16 m_nFirstPageID; + Page2ControllerMap m_aPageControllers; + }; + + +} // namespace svt::uno + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |