diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/source/window | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/window')
68 files changed, 60372 insertions, 0 deletions
diff --git a/vcl/source/window/EnumContext.cxx b/vcl/source/window/EnumContext.cxx new file mode 100644 index 000000000..6ca075eb6 --- /dev/null +++ b/vcl/source/window/EnumContext.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 . + */ +#include <vcl/EnumContext.hxx> + +#include <osl/diagnose.h> +#include <o3tl/enumarray.hxx> + +#include <map> + +namespace vcl { + +namespace { + +typedef ::std::map<OUString,EnumContext::Application> ApplicationMap; + +ApplicationMap maApplicationMap; +o3tl::enumarray<EnumContext::Application, OUString> maApplicationVector; + +typedef ::std::map<OUString,EnumContext::Context> ContextMap; + +ContextMap maContextMap; +o3tl::enumarray<EnumContext::Context, OUString> maContextVector; + +} + +const sal_Int32 EnumContext::NoMatch = 4; + +EnumContext::EnumContext() + : meApplication(Application::NONE), + meContext(Context::Unknown) +{ +} + +EnumContext::EnumContext ( + const Application eApplication, + const Context eContext) + : meApplication(eApplication), + meContext(eContext) +{ +} + +sal_Int32 EnumContext::GetCombinedContext_DI() const +{ + return CombinedEnumContext(GetApplication_DI(), meContext); +} + +EnumContext::Application EnumContext::GetApplication_DI() const +{ + switch (meApplication) + { + case Application::Draw: + case Application::Impress: + return Application::DrawImpress; + + case Application::Writer: + case Application::WriterGlobal: + case Application::WriterWeb: + case Application::WriterXML: + case Application::WriterForm: + case Application::WriterReport: + return Application::WriterVariants; + + default: + return meApplication; + } +} + +bool EnumContext::operator== (const EnumContext& rOther) const +{ + return meApplication==rOther.meApplication + && meContext==rOther.meContext; +} + +bool EnumContext::operator!= (const EnumContext& rOther) const +{ + return meApplication!=rOther.meApplication + || meContext!=rOther.meContext; +} + +void EnumContext::AddEntry (const OUString& rsName, const Application eApplication) +{ + maApplicationMap[rsName] = eApplication; + OSL_ASSERT(eApplication<=Application::LAST); + maApplicationVector[eApplication]=rsName; +} + +void EnumContext::ProvideApplicationContainers() +{ + if (!maApplicationMap.empty()) + return; + + AddEntry("com.sun.star.text.TextDocument", EnumContext::Application::Writer); + AddEntry("com.sun.star.text.GlobalDocument", EnumContext::Application::WriterGlobal); + AddEntry("com.sun.star.text.WebDocument", EnumContext::Application::WriterWeb); + AddEntry("com.sun.star.xforms.XMLFormDocument", EnumContext::Application::WriterXML); + AddEntry("com.sun.star.sdb.FormDesign", EnumContext::Application::WriterForm); + AddEntry("com.sun.star.sdb.TextReportDesign", EnumContext::Application::WriterReport); + AddEntry("com.sun.star.sheet.SpreadsheetDocument", EnumContext::Application::Calc); + AddEntry("com.sun.star.chart2.ChartDocument", EnumContext::Application::Chart); + AddEntry("com.sun.star.drawing.DrawingDocument", EnumContext::Application::Draw); + AddEntry("com.sun.star.presentation.PresentationDocument", EnumContext::Application::Impress); + AddEntry("com.sun.star.formula.FormulaProperties", EnumContext::Application::Formula); + AddEntry("com.sun.star.sdb.OfficeDatabaseDocument", EnumContext::Application::Base); + AddEntry("any", EnumContext::Application::Any); + AddEntry("none", EnumContext::Application::NONE); + +} + +EnumContext::Application EnumContext::GetApplicationEnum (const OUString& rsApplicationName) +{ + ProvideApplicationContainers(); + + ApplicationMap::const_iterator iApplication( + maApplicationMap.find(rsApplicationName)); + if (iApplication != maApplicationMap.end()) + return iApplication->second; + else + return EnumContext::Application::NONE; +} + +const OUString& EnumContext::GetApplicationName (const Application eApplication) +{ + ProvideApplicationContainers(); + return maApplicationVector[eApplication]; +} + +void EnumContext::AddEntry (const OUString& rsName, const Context eContext) +{ + maContextMap[rsName] = eContext; + maContextVector[eContext] = rsName; +} + +void EnumContext::ProvideContextContainers() +{ + if (!maContextMap.empty()) + return; + + AddEntry("3DObject", Context::ThreeDObject); + AddEntry("Annotation", Context::Annotation); + AddEntry("Auditing", Context::Auditing); + AddEntry("Axis", Context::Axis); + AddEntry("Cell", Context::Cell); + AddEntry("Chart", Context::Chart); + AddEntry("ChartElements", Context::ChartElements); + AddEntry("Draw", Context::Draw); + AddEntry("DrawFontwork", Context::DrawFontwork); + AddEntry("DrawLine", Context::DrawLine); + AddEntry("DrawPage", Context::DrawPage); + AddEntry("DrawText", Context::DrawText); + AddEntry("EditCell", Context::EditCell); + AddEntry("ErrorBar", Context::ErrorBar); + AddEntry("Form", Context::Form); + AddEntry("Frame", Context::Frame); + AddEntry("Graphic", Context::Graphic); + AddEntry("Grid", Context::Grid); + AddEntry("HandoutPage", Context::HandoutPage); + AddEntry("MasterPage", Context::MasterPage); + AddEntry("Math", Context::Math); + AddEntry("Media", Context::Media); + AddEntry("MultiObject", Context::MultiObject); + AddEntry("NotesPage", Context::NotesPage); + AddEntry("OLE", Context::OLE); + AddEntry("OutlineText", Context::OutlineText); + AddEntry("Pivot", Context::Pivot); + AddEntry("Printpreview", Context::Printpreview); + AddEntry("Series", Context::Series); + AddEntry("SlidesorterPage", Context::SlidesorterPage); + AddEntry("Table", Context::Table); + AddEntry("Text", Context::Text); + AddEntry("TextObject", Context::TextObject); + AddEntry("Trendline", Context::Trendline); + AddEntry("Sparkline", Context::Sparkline); + + // other general contexts + AddEntry("any", Context::Any); + AddEntry("default", Context::Default); + AddEntry("empty", Context::Empty); +} + +EnumContext::Context EnumContext::GetContextEnum (const OUString& rsContextName) +{ + ProvideContextContainers(); + + ContextMap::const_iterator iContext( maContextMap.find(rsContextName) ); + if (iContext != maContextMap.end()) + return iContext->second; + else + return EnumContext::Context::Unknown; +} + +const OUString& EnumContext::GetContextName (const Context eContext) +{ + ProvideContextContainers(); + return maContextVector[eContext]; +} + +} // end of namespace vcl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/NotebookBarAddonsMerger.cxx b/vcl/source/window/NotebookBarAddonsMerger.cxx new file mode 100644 index 000000000..274b455bd --- /dev/null +++ b/vcl/source/window/NotebookBarAddonsMerger.cxx @@ -0,0 +1,184 @@ +/* -*- 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 <cstddef> + +#include <vcl/notebookbar/NotebookBarAddonsMerger.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/menu.hxx> +#include <vcl/vclenum.hxx> +#include <vcl/toolbox.hxx> +#include <IPrioritable.hxx> +#include <OptionalBox.hxx> + +const char STYLE_TEXT[] = "Text"; +const char STYLE_ICON[] = "Icon"; + +const char MERGE_NOTEBOOKBAR_URL[] = "URL"; +const char MERGE_NOTEBOOKBAR_TITLE[] = "Title"; +const char MERGE_NOTEBOOKBAR_CONTEXT[] = "Context"; +const char MERGE_NOTEBOOKBAR_TARGET[] = "Target"; +const char MERGE_NOTEBOOKBAR_CONTROLTYPE[] = "ControlType"; +const char MERGE_NOTEBOOKBAR_WIDTH[] = "Width"; +const char MERGE_NOTEBOOKBAR_STYLE[] = "Style"; + +static void GetAddonNotebookBarItem(const css::uno::Sequence<css::beans::PropertyValue>& pExtension, + AddonNotebookBarItem& aAddonNotebookBarItem) +{ + for (const auto& i : pExtension) + { + if (i.Name == MERGE_NOTEBOOKBAR_URL) + i.Value >>= aAddonNotebookBarItem.sCommandURL; + else if (i.Name == MERGE_NOTEBOOKBAR_TITLE) + i.Value >>= aAddonNotebookBarItem.sLabel; + else if (i.Name == MERGE_NOTEBOOKBAR_CONTEXT) + i.Value >>= aAddonNotebookBarItem.sContext; + else if (i.Name == MERGE_NOTEBOOKBAR_TARGET) + i.Value >>= aAddonNotebookBarItem.sTarget; + else if (i.Name == MERGE_NOTEBOOKBAR_CONTROLTYPE) + i.Value >>= aAddonNotebookBarItem.sControlType; + else if (i.Name == MERGE_NOTEBOOKBAR_WIDTH) + i.Value >>= aAddonNotebookBarItem.nWidth; + else if (i.Name == MERGE_NOTEBOOKBAR_STYLE) + i.Value >>= aAddonNotebookBarItem.sStyle; + } +} + +static void CreateNotebookBarToolBox(vcl::Window* pNotebookbarToolBox, + const css::uno::Reference<css::frame::XFrame>& m_xFrame, + const AddonNotebookBarItem& aAddonNotebookBarItem, + const std::vector<Image>& aImageVec, const tools::ULong nIter) +{ + ToolBoxItemId nItemId(0); + ToolBox* pToolbox = dynamic_cast<ToolBox*>(pNotebookbarToolBox); + if (!pToolbox) + return; + + pToolbox->InsertSeparator(); + pToolbox->Show(); + Size aSize(0, 0); + Image sImage; + pToolbox->InsertItem(aAddonNotebookBarItem.sCommandURL, m_xFrame, ToolBoxItemBits::NONE, aSize); + nItemId = pToolbox->GetItemId(aAddonNotebookBarItem.sCommandURL); + pToolbox->SetItemCommand(nItemId, aAddonNotebookBarItem.sCommandURL); + pToolbox->SetQuickHelpText(nItemId, aAddonNotebookBarItem.sLabel); + + if (nIter < aImageVec.size()) + { + sImage = aImageVec[nIter]; + if (!sImage) + { + sImage = vcl::CommandInfoProvider::GetImageForCommand(aAddonNotebookBarItem.sCommandURL, + m_xFrame); + } + } + + if (aAddonNotebookBarItem.sStyle == STYLE_TEXT) + pToolbox->SetItemText(nItemId, aAddonNotebookBarItem.sLabel); + else if (aAddonNotebookBarItem.sStyle == STYLE_ICON) + pToolbox->SetItemImage(nItemId, sImage); + else + { + pToolbox->SetItemText(nItemId, aAddonNotebookBarItem.sLabel); + pToolbox->SetItemImage(nItemId, sImage); + } + pToolbox->Show(); +} + +namespace NotebookBarAddonsMerger +{ +void MergeNotebookBarAddons(vcl::Window* pParent, const VclBuilder::customMakeWidget& pFunction, + const css::uno::Reference<css::frame::XFrame>& m_xFrame, + const NotebookBarAddonsItem& aNotebookBarAddonsItem, + VclBuilder::stringmap& rMap) +{ + std::vector<Image> aImageVec = aNotebookBarAddonsItem.aImageValues; + tools::ULong nIter = 0; + sal_uInt16 nPriorityIdx = aImageVec.size(); + css::uno::Sequence<css::uno::Sequence<css::beans::PropertyValue>> aExtension; + for (std::size_t nIdx = 0; nIdx < aNotebookBarAddonsItem.aAddonValues.size(); nIdx++) + { + aExtension = aNotebookBarAddonsItem.aAddonValues[nIdx]; + + for (const css::uno::Sequence<css::beans::PropertyValue>& pExtension : + std::as_const(aExtension)) + { + VclPtr<vcl::Window> pOptionalParent; + pOptionalParent = VclPtr<OptionalBox>::Create(pParent); + pOptionalParent->Show(); + + vcl::IPrioritable* pPrioritable + = dynamic_cast<vcl::IPrioritable*>(pOptionalParent.get()); + if (pPrioritable) + pPrioritable->SetPriority(nPriorityIdx - nIter); + + VclPtr<vcl::Window> pNotebookbarToolBox; + pFunction(pNotebookbarToolBox, pOptionalParent, rMap); + + AddonNotebookBarItem aAddonNotebookBarItem; + GetAddonNotebookBarItem(pExtension, aAddonNotebookBarItem); + + CreateNotebookBarToolBox(pNotebookbarToolBox, m_xFrame, aAddonNotebookBarItem, + aImageVec, nIter); + nIter++; + } + } +} + +void MergeNotebookBarMenuAddons(Menu* pPopupMenu, sal_Int16 nItemId, const OString& sItemIdName, + NotebookBarAddonsItem& aNotebookBarAddonsItem) +{ + std::vector<Image> aImageVec = aNotebookBarAddonsItem.aImageValues; + tools::ULong nIter = 0; + css::uno::Sequence<css::uno::Sequence<css::beans::PropertyValue>> aExtension; + for (std::size_t nIdx = 0; nIdx < aNotebookBarAddonsItem.aAddonValues.size(); nIdx++) + { + aExtension = aNotebookBarAddonsItem.aAddonValues[nIdx]; + + for (int nSecIdx = 0; nSecIdx < aExtension.getLength(); nSecIdx++) + { + AddonNotebookBarItem aAddonNotebookBarItem; + Image sImage; + MenuItemBits nBits = MenuItemBits::ICON; + const css::uno::Sequence<css::beans::PropertyValue> pExtension = aExtension[nSecIdx]; + + GetAddonNotebookBarItem(pExtension, aAddonNotebookBarItem); + + pPopupMenu->InsertItem(nItemId, aAddonNotebookBarItem.sLabel, nBits, sItemIdName); + pPopupMenu->SetItemCommand(nItemId, aAddonNotebookBarItem.sCommandURL); + + if (nIter < aImageVec.size()) + { + sImage = aImageVec[nIter]; + nIter++; + } + pPopupMenu->SetItemImage(nItemId, sImage); + + if (nSecIdx == aExtension.getLength() - 1) + pPopupMenu->InsertSeparator(); + + ++nItemId; + } + } +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/OptionalBox.cxx b/vcl/source/window/OptionalBox.cxx new file mode 100644 index 000000000..28055f7e2 --- /dev/null +++ b/vcl/source/window/OptionalBox.cxx @@ -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 . + */ + +#include <vcl/layout.hxx> +#include <OptionalBox.hxx> + +/* + * OptionalBox - shows or hides the content. To use with PriorityHBox + * or PriorityMergedHBox + */ + +OptionalBox::OptionalBox(vcl::Window* pParent) + : VclHBox(pParent) + , m_bInFullView(true) +{ +} + +OptionalBox::~OptionalBox() { disposeOnce(); } + +void OptionalBox::HideContent() +{ + if (m_bInFullView) + { + m_bInFullView = false; + + for (int i = 0; i < GetChildCount(); i++) + GetChild(i)->Hide(); + + SetOutputSizePixel(Size(10, GetSizePixel().Height())); + } +} + +void OptionalBox::ShowContent() +{ + if (!m_bInFullView) + { + m_bInFullView = true; + + for (int i = 0; i < GetChildCount(); i++) + GetChild(i)->Show(); + } +} + +bool OptionalBox::IsHidden() { return !m_bInFullView; } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/abstdlg.cxx b/vcl/source/window/abstdlg.cxx new file mode 100644 index 000000000..29d79b0cd --- /dev/null +++ b/vcl/source/window/abstdlg.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 <osl/module.hxx> +#include <vcl/abstdlg.hxx> +#include <vcl/bitmapex.hxx> + +typedef VclAbstractDialogFactory*(SAL_CALL* FuncPtrCreateDialogFactory)(); + +#ifndef DISABLE_DYNLOADING +extern "C" { +static void thisModule() {} +} +#else +extern "C" VclAbstractDialogFactory* CreateDialogFactory(); +#endif + +VclAbstractDialogFactory* VclAbstractDialogFactory::Create() +{ + static auto fp = []() -> FuncPtrCreateDialogFactory { +#ifndef DISABLE_DYNLOADING + ::osl::Module aDialogLibrary; + if (aDialogLibrary.loadRelative(&thisModule, CUI_DLL_NAME, + SAL_LOADMODULE_GLOBAL | SAL_LOADMODULE_LAZY)) + { + auto const p = reinterpret_cast<FuncPtrCreateDialogFactory>( + aDialogLibrary.getFunctionSymbol("CreateDialogFactory")); + aDialogLibrary.release(); + return p; + } + return nullptr; +#else + return CreateDialogFactory; +#endif + }(); + if (fp) + return fp(); + return nullptr; +} + +VclAbstractDialog::~VclAbstractDialog() {} + +bool VclAbstractDialog::StartExecuteAsync(AsyncContext&) +{ + assert(false); + return false; +} + +std::vector<OString> VclAbstractDialog::getAllPageUIXMLDescriptions() const +{ + // default has no pages + return std::vector<OString>(); +} + +bool VclAbstractDialog::selectPageByUIXMLDescription(const OString& /*rUIXMLDescription*/) +{ + // default cannot select a page (which is okay, return true) + return true; +} + +BitmapEx VclAbstractDialog::createScreenshot() const +{ + // default returns empty bitmap + return BitmapEx(); +} + +VclAbstractDialogFactory::~VclAbstractDialogFactory() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/accel.cxx b/vcl/source/window/accel.cxx new file mode 100644 index 000000000..a20c289f2 --- /dev/null +++ b/vcl/source/window/accel.cxx @@ -0,0 +1,279 @@ +/* -*- 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 <osl/diagnose.h> +#include <accel.hxx> + +#define ACCELENTRY_NOTFOUND (sal_uInt16(0xFFFF)) + +static sal_uInt16 ImplAccelEntryGetIndex( const ImplAccelList* pList, sal_uInt16 nId, + sal_uInt16* pIndex = nullptr ) +{ + size_t nLow; + size_t nHigh; + size_t nMid; + size_t nCount = pList->size(); + sal_uInt16 nCompareId; + + // check if first key is larger then the key to compare + if ( !nCount || (nId < (*pList)[ 0 ]->mnId) ) + { + if ( pIndex ) + *pIndex = 0; + return ACCELENTRY_NOTFOUND; + } + + // Binary search + nLow = 0; + nHigh = nCount-1; + do + { + nMid = (nLow + nHigh) / 2; + nCompareId = (*pList)[ nMid ]->mnId; + if ( nId < nCompareId ) + nHigh = nMid-1; + else + { + if ( nId > nCompareId ) + nLow = nMid + 1; + else + return static_cast<sal_uInt16>(nMid); + } + } + while ( nLow <= nHigh ); + + if ( pIndex ) + { + if ( nId > nCompareId ) + *pIndex = static_cast<sal_uInt16>(nMid+1); + else + *pIndex = static_cast<sal_uInt16>(nMid); + } + + return ACCELENTRY_NOTFOUND; +} + +static void ImplAccelEntryInsert( ImplAccelList* pList, std::unique_ptr<ImplAccelEntry> pEntry ) +{ + sal_uInt16 nInsIndex(0); + std::vector<ImplAccelEntry *>::size_type nIndex = ImplAccelEntryGetIndex( pList, pEntry->mnId, &nInsIndex ); + + if ( nIndex != ACCELENTRY_NOTFOUND ) + { + do + { + nIndex++; + ImplAccelEntry* pTempEntry = nullptr; + if ( nIndex < pList->size() ) + pTempEntry = (*pList)[ nIndex ].get(); + if ( !pTempEntry || (pTempEntry->mnId != pEntry->mnId) ) + break; + } + while ( nIndex < pList->size() ); + + if ( nIndex < pList->size() ) { + pList->insert( pList->begin() + nIndex, std::move(pEntry) ); + } else { + pList->push_back( std::move(pEntry) ); + } + } + else { + if ( nInsIndex < pList->size() ) { + pList->insert( pList->begin() + nInsIndex, std::move(pEntry) ); + } else { + pList->push_back( std::move(pEntry) ); + } + } +} + +void Accelerator::ImplInit() +{ + mnCurId = 0; + mpDel = nullptr; +} + +ImplAccelEntry* Accelerator::ImplGetAccelData( const vcl::KeyCode& rKeyCode ) const +{ + auto it = maKeyMap.find( rKeyCode.GetFullCode() ); + if( it != maKeyMap.end() ) + return it->second; + else + return nullptr; +} + +void Accelerator::ImplCopyData( const Accelerator& rAccelData ) +{ + // copy table + for (const std::unique_ptr<ImplAccelEntry>& i : rAccelData.maIdList) + { + std::unique_ptr<ImplAccelEntry> pEntry(new ImplAccelEntry( *i )); + + // sequence accelerator, then copy also + if ( pEntry->mpAccel ) + { + pEntry->mpAccel = new Accelerator( *(pEntry->mpAccel) ); + pEntry->mpAutoAccel = pEntry->mpAccel; + } + else + pEntry->mpAutoAccel = nullptr; + + maKeyMap.insert( std::make_pair( pEntry->maKeyCode.GetFullCode(), pEntry.get() ) ); + maIdList.push_back( std::move(pEntry) ); + } +} + +void Accelerator::ImplDeleteData() +{ + // delete accelerator-entries using the id-table + for (const std::unique_ptr<ImplAccelEntry>& pEntry : maIdList) { + delete pEntry->mpAutoAccel; + } + maIdList.clear(); +} + +void Accelerator::ImplInsertAccel( sal_uInt16 nItemId, const vcl::KeyCode& rKeyCode, + bool bEnable, Accelerator* pAutoAccel ) +{ + SAL_WARN_IF( !nItemId, "vcl", "Accelerator::InsertItem(): ItemId == 0" ); + + if ( rKeyCode.IsFunction() ) + { + sal_uInt16 nCode1; + sal_uInt16 nCode2; + sal_uInt16 nCode3; + sal_uInt16 nCode4; + ImplGetKeyCode( rKeyCode.GetFunction(), nCode1, nCode2, nCode3, nCode4 ); + if ( nCode1 ) + ImplInsertAccel( nItemId, vcl::KeyCode( nCode1, nCode1 ), bEnable, pAutoAccel ); + if ( nCode2 ) + { + if ( pAutoAccel ) + pAutoAccel = new Accelerator( *pAutoAccel ); + ImplInsertAccel( nItemId, vcl::KeyCode( nCode2, nCode2 ), bEnable, pAutoAccel ); + if ( nCode3 ) + { + if ( pAutoAccel ) + pAutoAccel = new Accelerator( *pAutoAccel ); + ImplInsertAccel( nItemId, vcl::KeyCode( nCode3, nCode3 ), bEnable, pAutoAccel ); + } + } + return; + } + + // fetch and fill new entries + std::unique_ptr<ImplAccelEntry> pEntry(new ImplAccelEntry); + pEntry->mnId = nItemId; + pEntry->maKeyCode = rKeyCode; + pEntry->mpAccel = pAutoAccel; + pEntry->mpAutoAccel = pAutoAccel; + pEntry->mbEnabled = bEnable; + + // now into the tables + sal_uLong nCode = rKeyCode.GetFullCode(); + if ( !nCode ) + { + OSL_FAIL( "Accelerator::InsertItem(): KeyCode with KeyCode 0 not allowed" ); + } + else if ( !maKeyMap.insert( std::make_pair( nCode, pEntry.get() ) ).second ) + { + SAL_WARN( "vcl", "Accelerator::InsertItem(): KeyCode (Key: " << nCode << ") already exists" ); + } + else + ImplAccelEntryInsert( &maIdList, std::move(pEntry) ); +} + +Accelerator::Accelerator() +{ + ImplInit(); +} + +Accelerator::Accelerator(const Accelerator& rAccel) +{ + ImplInit(); + ImplCopyData(rAccel); +} + +Accelerator::~Accelerator() +{ + + // inform AccelManager about deleting the Accelerator + if ( mpDel ) + *mpDel = true; + + ImplDeleteData(); +} + +void Accelerator::Activate() +{ + maActivateHdl.Call( *this ); +} + +void Accelerator::Select() +{ + maSelectHdl.Call( *this ); +} + +void Accelerator::InsertItem( sal_uInt16 nItemId, const vcl::KeyCode& rKeyCode ) +{ + ImplInsertAccel( nItemId, rKeyCode, true, nullptr ); +} + +sal_uInt16 Accelerator::GetItemCount() const +{ + return static_cast<sal_uInt16>(maIdList.size()); +} + +sal_uInt16 Accelerator::GetItemId( sal_uInt16 nPos ) const +{ + + ImplAccelEntry* pEntry = ( nPos < maIdList.size() ) ? maIdList[ nPos ].get() : nullptr; + if ( pEntry ) + return pEntry->mnId; + else + return 0; +} + +Accelerator* Accelerator::GetAccel( sal_uInt16 nItemId ) const +{ + + sal_uInt16 nIndex = ImplAccelEntryGetIndex( &maIdList, nItemId ); + if ( nIndex != ACCELENTRY_NOTFOUND ) + return maIdList[ nIndex ]->mpAccel; + else + return nullptr; +} + +Accelerator& Accelerator::operator=( const Accelerator& rAccel ) +{ + if(this == &rAccel) + return *this; + + // assign new data + mnCurId = 0; + + // delete and copy tables + ImplDeleteData(); + maKeyMap.clear(); + ImplCopyData(rAccel); + + return *this; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/accessibility.cxx b/vcl/source/window/accessibility.cxx new file mode 100644 index 000000000..d332da62a --- /dev/null +++ b/vcl/source/window/accessibility.cxx @@ -0,0 +1,612 @@ +/* -*- 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/layout.hxx> +#include <vcl/toolkit/fixed.hxx> +#include <vcl/window.hxx> +#include <vcl/menu.hxx> +#include <vcl/wrkwin.hxx> + +#include <window.h> +#include <brdwin.hxx> + +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/XAccessibleEditableText.hpp> +#include <com/sun/star/awt/XWindowPeer.hpp> + +#include <sal/log.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::datatransfer::clipboard; +using namespace ::com::sun::star::datatransfer::dnd; +using namespace ::com::sun::star; + + +ImplAccessibleInfos::ImplAccessibleInfos() +{ + nAccessibleRole = 0xFFFF; + pLabeledByWindow = nullptr; + pLabelForWindow = nullptr; +} + +ImplAccessibleInfos::~ImplAccessibleInfos() +{ +} + +namespace vcl { + +css::uno::Reference< css::accessibility::XAccessible > Window::GetAccessible( bool bCreate ) +{ + // do not optimize hierarchy for the top level border win (ie, when there is no parent) + /* // do not optimize accessible hierarchy at all to better reflect real VCL hierarchy + if ( GetParent() && ( GetType() == WindowType::BORDERWINDOW ) && ( GetChildCount() == 1 ) ) + //if( !ImplIsAccessibleCandidate() ) + { + vcl::Window* pChild = GetAccessibleChildWindow( 0 ); + if ( pChild ) + return pChild->GetAccessible(); + } + */ + if ( !mpWindowImpl ) + return css::uno::Reference< css::accessibility::XAccessible >(); + if ( !mpWindowImpl->mxAccessible.is() && bCreate ) + mpWindowImpl->mxAccessible = CreateAccessible(); + + return mpWindowImpl->mxAccessible; +} + +css::uno::Reference< css::accessibility::XAccessible > Window::CreateAccessible() +{ + css::uno::Reference< css::accessibility::XAccessible > xAcc( GetComponentInterface(), css::uno::UNO_QUERY ); + return xAcc; +} + +void Window::SetAccessible( const css::uno::Reference< css::accessibility::XAccessible >& x ) +{ + if (!mpWindowImpl) + return; + + mpWindowImpl->mxAccessible = x; +} + +// skip all border windows that are not top level frames +bool Window::ImplIsAccessibleCandidate() const +{ + if( !mpWindowImpl->mbBorderWin ) + return true; + else + // #101741 do not check for WB_CLOSEABLE because undecorated floaters (like menus!) are closeable + if( mpWindowImpl->mbFrame && mpWindowImpl->mnStyle & (WB_MOVEABLE | WB_SIZEABLE) ) + return true; + else + return false; +} + +bool Window::ImplIsAccessibleNativeFrame() const +{ + if( mpWindowImpl->mbFrame ) + // #101741 do not check for WB_CLOSEABLE because undecorated floaters (like menus!) are closeable + if( mpWindowImpl->mnStyle & (WB_MOVEABLE | WB_SIZEABLE) ) + return true; + else + return false; + else + return false; +} + +vcl::Window* Window::GetAccessibleParentWindow() const +{ + if (!mpWindowImpl || ImplIsAccessibleNativeFrame()) + return nullptr; + + vcl::Window* pParent = mpWindowImpl->mpParent; + if( GetType() == WindowType::MENUBARWINDOW ) + { + // report the menubar as a child of THE workwindow + vcl::Window *pWorkWin = GetParent()->mpWindowImpl->mpFirstChild; + while( pWorkWin && (pWorkWin == this) ) + pWorkWin = pWorkWin->mpWindowImpl->mpNext; + pParent = pWorkWin; + } + // If this is a floating window which has a native border window, then that border should be reported as + // the accessible parent + else if( GetType() == WindowType::FLOATINGWINDOW && + mpWindowImpl->mpBorderWindow && mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame ) + { + pParent = mpWindowImpl->mpBorderWindow; + } + else if( pParent && !pParent->ImplIsAccessibleCandidate() ) + { + pParent = pParent->mpWindowImpl->mpParent; + } + return pParent; +} + +sal_uInt16 Window::GetAccessibleChildWindowCount() +{ + if (!mpWindowImpl) + return 0; + + sal_uInt16 nChildren = 0; + vcl::Window* pChild = mpWindowImpl->mpFirstChild; + while( pChild ) + { + if( pChild->IsVisible() ) + nChildren++; + pChild = pChild->mpWindowImpl->mpNext; + } + + // report the menubarwindow as a child of THE workwindow + if( GetType() == WindowType::BORDERWINDOW ) + { + ImplBorderWindow *pBorderWindow = static_cast<ImplBorderWindow*>(this); + if( pBorderWindow->mpMenuBarWindow && + pBorderWindow->mpMenuBarWindow->IsVisible() + ) + --nChildren; + } + else if( GetType() == WindowType::WORKWINDOW ) + { + WorkWindow *pWorkWindow = static_cast<WorkWindow*>(this); + if( pWorkWindow->GetMenuBar() && + pWorkWindow->GetMenuBar()->GetWindow() && + pWorkWindow->GetMenuBar()->GetWindow()->IsVisible() + ) + ++nChildren; + } + + return nChildren; +} + +vcl::Window* Window::GetAccessibleChildWindow( sal_uInt16 n ) +{ + // report the menubarwindow as the first child of THE workwindow + if( GetType() == WindowType::WORKWINDOW && static_cast<WorkWindow *>(this)->GetMenuBar() ) + { + if( n == 0 ) + { + MenuBar *pMenuBar = static_cast<WorkWindow *>(this)->GetMenuBar(); + if( pMenuBar->GetWindow() && pMenuBar->GetWindow()->IsVisible() ) + return pMenuBar->GetWindow(); + } + else + --n; + } + + // transform n to child number including invisible children + sal_uInt16 nChildren = n; + vcl::Window* pChild = mpWindowImpl->mpFirstChild; + while( pChild ) + { + if( pChild->IsVisible() ) + { + if( ! nChildren ) + break; + nChildren--; + } + pChild = pChild->mpWindowImpl->mpNext; + } + + if( GetType() == WindowType::BORDERWINDOW && pChild && pChild->GetType() == WindowType::MENUBARWINDOW ) + { + do pChild = pChild->mpWindowImpl->mpNext; while( pChild && ! pChild->IsVisible() ); + SAL_WARN_IF( !pChild, "vcl", "GetAccessibleChildWindow(): wrong index in border window"); + } + + if ( pChild && ( pChild->GetType() == WindowType::BORDERWINDOW ) && ( pChild->GetChildCount() == 1 ) ) + { + pChild = pChild->GetChild( 0 ); + } + return pChild; +} + +void Window::SetAccessibleRole( sal_uInt16 nRole ) +{ + if ( !mpWindowImpl->mpAccessibleInfos ) + mpWindowImpl->mpAccessibleInfos.reset( new ImplAccessibleInfos ); + + SAL_WARN_IF( mpWindowImpl->mpAccessibleInfos->nAccessibleRole != 0xFFFF, "vcl", "AccessibleRole already set!" ); + mpWindowImpl->mpAccessibleInfos->nAccessibleRole = nRole; +} + +sal_uInt16 Window::getDefaultAccessibleRole() const +{ + sal_uInt16 nRole = 0xFFFF; + switch ( GetType() ) + { + case WindowType::MESSBOX: // MT: Would be nice to have special roles! + case WindowType::INFOBOX: + case WindowType::WARNINGBOX: + case WindowType::ERRORBOX: + case WindowType::QUERYBOX: nRole = accessibility::AccessibleRole::ALERT; break; + + case WindowType::MODELESSDIALOG: + case WindowType::TABDIALOG: + case WindowType::BUTTONDIALOG: + case WindowType::DIALOG: nRole = accessibility::AccessibleRole::DIALOG; break; + + case WindowType::PUSHBUTTON: + case WindowType::OKBUTTON: + case WindowType::CANCELBUTTON: + case WindowType::HELPBUTTON: + case WindowType::IMAGEBUTTON: + case WindowType::MOREBUTTON: nRole = accessibility::AccessibleRole::PUSH_BUTTON; break; + case WindowType::MENUBUTTON: nRole = accessibility::AccessibleRole::BUTTON_MENU; break; + + case WindowType::RADIOBUTTON: nRole = accessibility::AccessibleRole::RADIO_BUTTON; break; + case WindowType::TRISTATEBOX: + case WindowType::CHECKBOX: nRole = accessibility::AccessibleRole::CHECK_BOX; break; + + case WindowType::MULTILINEEDIT: nRole = accessibility::AccessibleRole::SCROLL_PANE; break; + + case WindowType::PATTERNFIELD: + case WindowType::EDIT: nRole = static_cast<Edit const *>(this)->IsPassword() ? accessibility::AccessibleRole::PASSWORD_TEXT : accessibility::AccessibleRole::TEXT; break; + + case WindowType::PATTERNBOX: + case WindowType::NUMERICBOX: + case WindowType::METRICBOX: + case WindowType::CURRENCYBOX: + case WindowType::LONGCURRENCYBOX: + case WindowType::COMBOBOX: nRole = accessibility::AccessibleRole::COMBO_BOX; break; + + case WindowType::LISTBOX: + case WindowType::MULTILISTBOX: nRole = accessibility::AccessibleRole::LIST; break; + + case WindowType::TREELISTBOX: nRole = accessibility::AccessibleRole::TREE; break; + + case WindowType::FIXEDTEXT: nRole = accessibility::AccessibleRole::LABEL; break; + case WindowType::FIXEDLINE: + if( !GetText().isEmpty() ) + nRole = accessibility::AccessibleRole::LABEL; + else + nRole = accessibility::AccessibleRole::SEPARATOR; + break; + + case WindowType::FIXEDBITMAP: + case WindowType::FIXEDIMAGE: nRole = accessibility::AccessibleRole::ICON; break; + case WindowType::GROUPBOX: nRole = accessibility::AccessibleRole::GROUP_BOX; break; + case WindowType::SCROLLBAR: nRole = accessibility::AccessibleRole::SCROLL_BAR; break; + + case WindowType::SLIDER: + case WindowType::SPLITTER: + case WindowType::SPLITWINDOW: nRole = accessibility::AccessibleRole::SPLIT_PANE; break; + + case WindowType::DATEBOX: + case WindowType::TIMEBOX: + case WindowType::DATEFIELD: + case WindowType::TIMEFIELD: nRole = accessibility::AccessibleRole::DATE_EDITOR; break; + + case WindowType::METRICFIELD: + case WindowType::CURRENCYFIELD: + case WindowType::SPINBUTTON: + case WindowType::SPINFIELD: + case WindowType::FORMATTEDFIELD: nRole = accessibility::AccessibleRole::SPIN_BOX; break; + + case WindowType::TOOLBOX: nRole = accessibility::AccessibleRole::TOOL_BAR; break; + case WindowType::STATUSBAR: nRole = accessibility::AccessibleRole::STATUS_BAR; break; + + case WindowType::TABPAGE: nRole = accessibility::AccessibleRole::PANEL; break; + case WindowType::TABCONTROL: nRole = accessibility::AccessibleRole::PAGE_TAB_LIST; break; + + case WindowType::DOCKINGWINDOW: nRole = (mpWindowImpl->mbFrame) ? accessibility::AccessibleRole::FRAME : + accessibility::AccessibleRole::PANEL; break; + + case WindowType::FLOATINGWINDOW: nRole = ( mpWindowImpl->mbFrame || + (mpWindowImpl->mpBorderWindow && mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame) || + (GetStyle() & WB_OWNERDRAWDECORATION) ) ? accessibility::AccessibleRole::FRAME : + accessibility::AccessibleRole::WINDOW; break; + + case WindowType::WORKWINDOW: nRole = accessibility::AccessibleRole::ROOT_PANE; break; + + case WindowType::SCROLLBARBOX: nRole = accessibility::AccessibleRole::FILLER; break; + + case WindowType::HELPTEXTWINDOW: nRole = accessibility::AccessibleRole::TOOL_TIP; break; + + case WindowType::RULER: nRole = accessibility::AccessibleRole::RULER; break; + + case WindowType::SCROLLWINDOW: nRole = accessibility::AccessibleRole::SCROLL_PANE; break; + + case WindowType::WINDOW: + case WindowType::CONTROL: + case WindowType::BORDERWINDOW: + case WindowType::SYSTEMCHILDWINDOW: + default: + if (ImplIsAccessibleNativeFrame() ) + nRole = accessibility::AccessibleRole::FRAME; + else if( IsScrollable() ) + nRole = accessibility::AccessibleRole::SCROLL_PANE; + else if( this->ImplGetWindow()->IsMenuFloatingWindow() ) + nRole = accessibility::AccessibleRole::WINDOW; // #106002#, contextmenus are windows (i.e. toplevel) + else + // #104051# WINDOW seems to be a bad default role, use LAYEREDPANE instead + // a WINDOW is interpreted as a top-level window, which is typically not the case + //nRole = accessibility::AccessibleRole::WINDOW; + nRole = accessibility::AccessibleRole::PANEL; + } + return nRole; +} + +sal_uInt16 Window::GetAccessibleRole() const +{ + if (!mpWindowImpl) + return 0; + + sal_uInt16 nRole = mpWindowImpl->mpAccessibleInfos ? mpWindowImpl->mpAccessibleInfos->nAccessibleRole : 0xFFFF; + if ( nRole == 0xFFFF ) + nRole = getDefaultAccessibleRole(); + return nRole; +} + +void Window::SetAccessibleName( const OUString& rName ) +{ + if ( !mpWindowImpl->mpAccessibleInfos ) + mpWindowImpl->mpAccessibleInfos.reset( new ImplAccessibleInfos ); + + OUString oldName = GetAccessibleName(); + + mpWindowImpl->mpAccessibleInfos->pAccessibleName = rName; + + CallEventListeners( VclEventId::WindowFrameTitleChanged, &oldName ); +} + +OUString Window::GetAccessibleName() const +{ + if (!mpWindowImpl) + return OUString(); + + if (mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pAccessibleName) + return *mpWindowImpl->mpAccessibleInfos->pAccessibleName; + return getDefaultAccessibleName(); +} + +OUString Window::getDefaultAccessibleName() const +{ + OUString aAccessibleName; + switch ( GetType() ) + { + case WindowType::MULTILINEEDIT: + case WindowType::PATTERNFIELD: + case WindowType::METRICFIELD: + case WindowType::CURRENCYFIELD: + case WindowType::EDIT: + + case WindowType::DATEBOX: + case WindowType::TIMEBOX: + case WindowType::CURRENCYBOX: + case WindowType::LONGCURRENCYBOX: + case WindowType::DATEFIELD: + case WindowType::TIMEFIELD: + case WindowType::SPINFIELD: + case WindowType::FORMATTEDFIELD: + + case WindowType::COMBOBOX: + case WindowType::LISTBOX: + case WindowType::MULTILISTBOX: + case WindowType::TREELISTBOX: + case WindowType::METRICBOX: + { + vcl::Window *pLabel = GetAccessibleRelationLabeledBy(); + if ( pLabel && pLabel != this ) + aAccessibleName = pLabel->GetText(); + if (aAccessibleName.isEmpty()) + aAccessibleName = GetQuickHelpText(); + if (aAccessibleName.isEmpty()) + aAccessibleName = GetText(); + } + break; + + case WindowType::IMAGEBUTTON: + case WindowType::PUSHBUTTON: + aAccessibleName = GetText(); + if (aAccessibleName.isEmpty()) + { + aAccessibleName = GetQuickHelpText(); + if (aAccessibleName.isEmpty()) + aAccessibleName = GetHelpText(); + } + break; + + case WindowType::TOOLBOX: + aAccessibleName = GetText(); + break; + + case WindowType::MOREBUTTON: + aAccessibleName = mpWindowImpl->maText; + break; + + default: + aAccessibleName = GetText(); + break; + } + + return OutputDevice::GetNonMnemonicString( aAccessibleName ); +} + +void Window::SetAccessibleDescription( const OUString& rDescription ) +{ + if ( ! mpWindowImpl->mpAccessibleInfos ) + mpWindowImpl->mpAccessibleInfos.reset( new ImplAccessibleInfos ); + + std::optional<OUString>& rCurrentDescription = mpWindowImpl->mpAccessibleInfos->pAccessibleDescription; + SAL_WARN_IF( rCurrentDescription && *rCurrentDescription != rDescription, "vcl", "AccessibleDescription already set" ); + rCurrentDescription = rDescription; +} + +OUString Window::GetAccessibleDescription() const +{ + if (!mpWindowImpl) + return OUString(); + + OUString aAccessibleDescription; + if ( mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pAccessibleDescription ) + { + aAccessibleDescription = *mpWindowImpl->mpAccessibleInfos->pAccessibleDescription; + } + else + { + // Special code for help text windows. ZT asks the border window for the + // description so we have to forward this request to our inner window. + const vcl::Window* pWin = this->ImplGetWindow(); + if ( pWin->GetType() == WindowType::HELPTEXTWINDOW ) + aAccessibleDescription = pWin->GetHelpText(); + else + aAccessibleDescription = GetHelpText(); + } + + return aAccessibleDescription; +} + +void Window::SetAccessibleRelationLabeledBy( vcl::Window* pLabeledBy ) +{ + if ( !mpWindowImpl->mpAccessibleInfos ) + mpWindowImpl->mpAccessibleInfos.reset( new ImplAccessibleInfos ); + mpWindowImpl->mpAccessibleInfos->pLabeledByWindow = pLabeledBy; +} + +void Window::SetAccessibleRelationLabelFor( vcl::Window* pLabelFor ) +{ + if ( !mpWindowImpl->mpAccessibleInfos ) + mpWindowImpl->mpAccessibleInfos.reset( new ImplAccessibleInfos ); + mpWindowImpl->mpAccessibleInfos->pLabelForWindow = pLabelFor; +} + +vcl::Window* Window::GetAccessibleRelationMemberOf() const +{ + if (!isContainerWindow(this) && !isContainerWindow(GetParent())) + return getLegacyNonLayoutAccessibleRelationMemberOf(); + + return nullptr; +} + +vcl::Window* Window::getAccessibleRelationLabelFor() const +{ + if (mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pLabelForWindow) + return mpWindowImpl->mpAccessibleInfos->pLabelForWindow; + + return nullptr; +} + +vcl::Window* Window::GetAccessibleRelationLabelFor() const +{ + vcl::Window* pWindow = getAccessibleRelationLabelFor(); + + if (pWindow) + return pWindow; + + if (!isContainerWindow(this) && !isContainerWindow(GetParent())) + return getLegacyNonLayoutAccessibleRelationLabelFor(); + + return nullptr; +} + +vcl::Window* Window::GetAccessibleRelationLabeledBy() const +{ + if (mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pLabeledByWindow) + return mpWindowImpl->mpAccessibleInfos->pLabeledByWindow; + + std::vector<VclPtr<FixedText> > aMnemonicLabels(list_mnemonic_labels()); + if (!aMnemonicLabels.empty()) + { + //if we have multiple labels, then prefer the first that is visible + for (auto const & rCandidate : aMnemonicLabels) + { + if (rCandidate->IsVisible()) + return rCandidate; + } + return aMnemonicLabels[0]; + } + + if (!isContainerWindow(this) && !isContainerWindow(GetParent())) + return getLegacyNonLayoutAccessibleRelationLabeledBy(); + + return nullptr; +} + +bool Window::IsAccessibilityEventsSuppressed( bool bTraverseParentPath ) +{ + if( !bTraverseParentPath ) + return mpWindowImpl->mbSuppressAccessibilityEvents; + else + { + vcl::Window *pParent = this; + while ( pParent && pParent->mpWindowImpl) + { + if( pParent->mpWindowImpl->mbSuppressAccessibilityEvents ) + return true; + else + pParent = pParent->mpWindowImpl->mpParent; // do not use GetParent() to find borderwindows that are frames + } + return false; + } +} + +void Window::SetAccessibilityEventsSuppressed(bool bSuppressed) +{ + mpWindowImpl->mbSuppressAccessibilityEvents = bSuppressed; +} + +} /* namespace vcl */ + +uno::Reference<accessibility::XAccessibleEditableText> +FindFocusedEditableText(uno::Reference<accessibility::XAccessibleContext> const& xContext) +{ + if (!xContext.is()) + return uno::Reference<accessibility::XAccessibleEditableText>(); + + uno::Reference<accessibility::XAccessibleStateSet> xState = xContext->getAccessibleStateSet(); + if (xState.is()) + { + if (xState->contains(accessibility::AccessibleStateType::FOCUSED)) + { + uno::Reference<accessibility::XAccessibleEditableText> xText(xContext, uno::UNO_QUERY); + if (xText.is()) + return xText; + if (xState->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS)) + return uno::Reference<accessibility::XAccessibleEditableText>(); + } + } + + bool bSafeToIterate = true; + sal_Int32 nCount = xContext->getAccessibleChildCount(); + if (nCount < 0 || nCount > SAL_MAX_UINT16 /* slow enough for anyone */) + bSafeToIterate = false; + if (!bSafeToIterate) + return uno::Reference<accessibility::XAccessibleEditableText>(); + + for (sal_Int32 i = 0; i < xContext->getAccessibleChildCount(); ++i) + { + uno::Reference<accessibility::XAccessible> xChild = xContext->getAccessibleChild(i); + if (!xChild.is()) + continue; + uno::Reference<accessibility::XAccessibleContext> xChildContext + = xChild->getAccessibleContext(); + if (!xChildContext.is()) + continue; + uno::Reference<accessibility::XAccessibleEditableText> xText + = FindFocusedEditableText(xChildContext); + if (xText.is()) + return xText; + } + return uno::Reference<accessibility::XAccessibleEditableText>(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/accmgr.cxx b/vcl/source/window/accmgr.cxx new file mode 100644 index 000000000..26ea9846e --- /dev/null +++ b/vcl/source/window/accmgr.cxx @@ -0,0 +1,228 @@ +/* -*- 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 <accel.hxx> +#include <accmgr.hxx> + +#include <algorithm> + +ImplAccelManager::~ImplAccelManager() +{ +} + +bool ImplAccelManager::InsertAccel( Accelerator* pAccel ) +{ + if ( !mxAccelList ) { + mxAccelList.emplace(); + } else { + for (Accelerator* i : *mxAccelList) { + if ( i == pAccel ) { + return false; + } + } + } + + mxAccelList->insert( mxAccelList->begin(), pAccel ); + return true; +} + +void ImplAccelManager::RemoveAccel( Accelerator const * pAccel ) +{ + // do we have a list ? + if ( !mxAccelList ) + return; + + //e.g. #i90599#. Someone starts typing a sequence in a dialog, but doesn't + //end it, and then closes the dialog, deleting the accelerators. So if + //we're removing an accelerator that a sub-accelerator which is in the + //sequence list, throw away the entire sequence + if ( mxSequenceList ) { + for (sal_uInt16 i = 0; i < pAccel->GetItemCount(); ++i) { + Accelerator* pSubAccel = pAccel->GetAccel( pAccel->GetItemId(i) ); + for (Accelerator* j : *mxSequenceList) { + if ( j == pSubAccel ) { + EndSequence(); + i = pAccel->GetItemCount(); + break; + } + } + } + } + + // throw it away + auto it = std::find(mxAccelList->begin(), mxAccelList->end(), pAccel); + if (it != mxAccelList->end()) + mxAccelList->erase( it ); +} + +void ImplAccelManager::EndSequence() +{ + // are we in a list ? + if ( !mxSequenceList ) + return; + + for (Accelerator* pTempAccel : *mxSequenceList) + { + pTempAccel->mpDel = nullptr; + } + + // delete sequence-list + mxSequenceList.reset(); +} + +bool ImplAccelManager::IsAccelKey( const vcl::KeyCode& rKeyCode ) +{ + Accelerator* pAccel; + + // do we have accelerators ?? + if ( !mxAccelList ) + return false; + if ( mxAccelList->empty() ) + return false; + + // are we in a sequence ? + if ( mxSequenceList ) + { + pAccel = mxSequenceList->empty() ? nullptr : (*mxSequenceList)[ 0 ]; + + // not found ? + if ( !pAccel ) + { + // abort sequence + FlushAccel(); + return false; + } + + // can the entry be found ? + ImplAccelEntry* pEntry = pAccel->ImplGetAccelData( rKeyCode ); + if ( pEntry ) + { + Accelerator* pNextAccel = pEntry->mpAccel; + + // is an accelerator coupled ? + if ( pNextAccel ) + { + + mxSequenceList->insert( mxSequenceList->begin(), pNextAccel ); + + // call Activate-Handler of the new one + pNextAccel->Activate(); + return true; + } + else + { + // it is there already ! + if ( pEntry->mbEnabled ) + { + // stop sequence (first call deactivate-handler) + EndSequence(); + + // set accelerator of the actual item + // and call the handler + bool bDel = false; + pAccel->mnCurId = pEntry->mnId; + pAccel->mpDel = &bDel; + pAccel->Select(); + + // did the accelerator survive the call + if ( !bDel ) + { + pAccel->mnCurId = 0; + pAccel->mpDel = nullptr; + } + + return true; + } + else + { + // stop sequence as the accelerator was disabled + // transfer the key (to the system) + FlushAccel(); + return false; + } + } + } + else + { + // wrong key => stop sequence + FlushAccel(); + return false; + } + } + + // step through the list of accelerators + for (Accelerator* i : *mxAccelList) + { + pAccel = i; + + // is the entry contained ? + ImplAccelEntry* pEntry = pAccel->ImplGetAccelData( rKeyCode ); + if ( pEntry ) + { + Accelerator* pNextAccel = pEntry->mpAccel; + + // is an accelerator assigned ? + if ( pNextAccel ) + { + + // create sequence list + mxSequenceList.emplace(); + mxSequenceList->insert( mxSequenceList->begin(), pAccel ); + mxSequenceList->insert( mxSequenceList->begin(), pNextAccel ); + + // call activate-Handler of the new one + pNextAccel->Activate(); + + return true; + } + else + { + // already assigned ! + if ( pEntry->mbEnabled ) + { + // first call activate/deactivate-Handler + pAccel->Activate(); + + // define accelerator of the actual item + // and call the handler + bool bDel = false; + pAccel->mnCurId = pEntry->mnId; + pAccel->mpDel = &bDel; + pAccel->Select(); + + // if the accelerator did survive the call + if ( !bDel ) + { + pAccel->mnCurId = 0; + pAccel->mpDel = nullptr; + } + + return true; + } + else + return false; + } + } + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/brdwin.cxx b/vcl/source/window/brdwin.cxx new file mode 100644 index 000000000..c003ed17b --- /dev/null +++ b/vcl/source/window/brdwin.cxx @@ -0,0 +1,2003 @@ +/* -*- 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 <strings.hrc> +#include <svdata.hxx> +#include <brdwin.hxx> +#include <window.h> + +#include <vcl/textrectinfo.hxx> +#include <vcl/event.hxx> +#include <vcl/decoview.hxx> +#include <vcl/syswin.hxx> +#include <vcl/dockwin.hxx> +#include <vcl/toolkit/floatwin.hxx> +#include <vcl/help.hxx> +#include <vcl/toolkit/edit.hxx> +#include <vcl/settings.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/ptrstyle.hxx> + +using namespace ::com::sun::star::uno; + +// useful caption height for title bar buttons +#define MIN_CAPTION_HEIGHT 18 + +namespace vcl { + +void Window::ImplCalcSymbolRect( tools::Rectangle& rRect ) +{ + // Add border, not shown in the non-default representation, + // as we want to use it for small buttons + rRect.AdjustLeft( -1 ); + rRect.AdjustTop( -1 ); + rRect.AdjustRight( 1 ); + rRect.AdjustBottom( 1 ); + + // we leave 5% room between the symbol and the button border + tools::Long nExtraWidth = ((rRect.GetWidth()*50)+500)/1000; + tools::Long nExtraHeight = ((rRect.GetHeight()*50)+500)/1000; + rRect.AdjustLeft(nExtraWidth ); + rRect.AdjustRight( -nExtraWidth ); + rRect.AdjustTop(nExtraHeight ); + rRect.AdjustBottom( -nExtraHeight ); +} + +} /* namespace vcl */ + +static void ImplDrawBrdWinSymbol( vcl::RenderContext* pDev, + const tools::Rectangle& rRect, SymbolType eSymbol ) +{ + // we leave 5% room between the symbol and the button border + DecorationView aDecoView( pDev ); + tools::Rectangle aTempRect = rRect; + vcl::Window::ImplCalcSymbolRect( aTempRect ); + aDecoView.DrawSymbol( aTempRect, eSymbol, + pDev->GetSettings().GetStyleSettings().GetButtonTextColor() ); +} + +static void ImplDrawBrdWinSymbolButton( vcl::RenderContext* pDev, + const tools::Rectangle& rRect, + SymbolType eSymbol, DrawButtonFlags nState ) +{ + bool bMouseOver(nState & DrawButtonFlags::Highlight); + nState &= ~DrawButtonFlags::Highlight; + + tools::Rectangle aTempRect; + vcl::Window *pWin = pDev->GetOwnerWindow(); + if( pWin ) + { + if( bMouseOver ) + { + // provide a bright background for selection effect + pDev->SetFillColor( pDev->GetSettings().GetStyleSettings().GetWindowColor() ); + pDev->SetLineColor(); + pDev->DrawRect( rRect ); + pWin->DrawSelectionBackground( rRect, 2, bool(nState & DrawButtonFlags::Pressed), + true ); + } + aTempRect = rRect; + aTempRect.AdjustLeft(3 ); + aTempRect.AdjustRight( -4 ); + aTempRect.AdjustTop(3 ); + aTempRect.AdjustBottom( -4 ); + } + else + { + DecorationView aDecoView( pDev ); + aTempRect = aDecoView.DrawButton( rRect, nState|DrawButtonFlags::Flat ); + } + ImplDrawBrdWinSymbol( pDev, aTempRect, eSymbol ); +} + + +ImplBorderWindowView::~ImplBorderWindowView() +{ +} + +bool ImplBorderWindowView::MouseMove( const MouseEvent& ) +{ + return false; +} + +bool ImplBorderWindowView::MouseButtonDown( const MouseEvent& ) +{ + return false; +} + +bool ImplBorderWindowView::Tracking( const TrackingEvent& ) +{ + return false; +} + +OUString ImplBorderWindowView::RequestHelp( const Point&, tools::Rectangle& ) +{ + return OUString(); +} + +tools::Rectangle ImplBorderWindowView::GetMenuRect() const +{ + return tools::Rectangle(); +} + +void ImplBorderWindowView::ImplInitTitle(ImplBorderFrameData* pData) +{ + ImplBorderWindow* pBorderWindow = pData->mpBorderWindow; + + if ( !(pBorderWindow->GetStyle() & (WB_MOVEABLE | WB_POPUP)) || + (pData->mnTitleType == BorderWindowTitleType::NONE) ) + { + pData->mnTitleType = BorderWindowTitleType::NONE; + pData->mnTitleHeight = 0; + } + else + { + const StyleSettings& rStyleSettings = pData->mpOutDev->GetSettings().GetStyleSettings(); + if (pData->mnTitleType == BorderWindowTitleType::Tearoff) + pData->mnTitleHeight = ToolBox::ImplGetDragWidth(*pData->mpBorderWindow, false) + 2; + else + { + if (pData->mnTitleType == BorderWindowTitleType::Small) + { + pBorderWindow->SetPointFont(*pBorderWindow->GetOutDev(), rStyleSettings.GetFloatTitleFont() ); + pData->mnTitleHeight = rStyleSettings.GetFloatTitleHeight(); + } + else // pData->mnTitleType == BorderWindowTitleType::Normal + { + // FIXME RenderContext + pBorderWindow->SetPointFont(*pBorderWindow->GetOutDev(), rStyleSettings.GetTitleFont()); + pData->mnTitleHeight = rStyleSettings.GetTitleHeight(); + } + tools::Long nTextHeight = pBorderWindow->GetTextHeight(); + if (nTextHeight > pData->mnTitleHeight) + pData->mnTitleHeight = nTextHeight; + } + } +} + +BorderWindowHitTest ImplBorderWindowView::ImplHitTest( ImplBorderFrameData const * pData, const Point& rPos ) +{ + ImplBorderWindow* pBorderWindow = pData->mpBorderWindow; + + if ( pData->maTitleRect.Contains( rPos ) ) + { + if ( pData->maCloseRect.Contains( rPos ) ) + return BorderWindowHitTest::Close; + else if ( pData->maMenuRect.Contains( rPos ) ) + return BorderWindowHitTest::Menu; + else if ( pData->maDockRect.Contains( rPos ) ) + return BorderWindowHitTest::Dock; + else if ( pData->maHideRect.Contains( rPos ) ) + return BorderWindowHitTest::Hide; + else if ( pData->maHelpRect.Contains( rPos ) ) + return BorderWindowHitTest::Help; + else + return BorderWindowHitTest::Title; + } + + if (pBorderWindow->GetStyle() & WB_SIZEABLE) + { + tools::Long nSizeWidth = pData->mnNoTitleTop+pData->mnTitleHeight; + if ( nSizeWidth < 16 ) + nSizeWidth = 16; + + // no corner resize for floating toolbars, which would lead to jumps while formatting + // setting nSizeWidth = 0 will only return pure left,top,right,bottom + if( pBorderWindow->GetStyle() & (WB_OWNERDRAWDECORATION | WB_POPUP) ) + nSizeWidth = 0; + + if ( rPos.X() < pData->mnLeftBorder ) + { + if ( rPos.Y() < nSizeWidth ) + return BorderWindowHitTest::TopLeft; + else if ( rPos.Y() >= pData->mnHeight-nSizeWidth ) + return BorderWindowHitTest::BottomLeft; + else + return BorderWindowHitTest::Left; + } + else if ( rPos.X() >= pData->mnWidth-pData->mnRightBorder ) + { + if ( rPos.Y() < nSizeWidth ) + return BorderWindowHitTest::TopRight; + else if ( rPos.Y() >= pData->mnHeight-nSizeWidth ) + return BorderWindowHitTest::BottomRight; + else + return BorderWindowHitTest::Right; + } + else if ( rPos.Y() < pData->mnNoTitleTop ) + { + if ( rPos.X() < nSizeWidth ) + return BorderWindowHitTest::TopLeft; + else if ( rPos.X() >= pData->mnWidth-nSizeWidth ) + return BorderWindowHitTest::TopRight; + else + return BorderWindowHitTest::Top; + } + else if ( rPos.Y() >= pData->mnHeight-pData->mnBottomBorder ) + { + if ( rPos.X() < nSizeWidth ) + return BorderWindowHitTest::BottomLeft; + else if ( rPos.X() >= pData->mnWidth-nSizeWidth ) + return BorderWindowHitTest::BottomRight; + else + return BorderWindowHitTest::Bottom; + } + } + + return BorderWindowHitTest::NONE; +} + +void ImplBorderWindowView::ImplMouseMove( ImplBorderFrameData* pData, const MouseEvent& rMEvt ) +{ + DrawButtonFlags oldCloseState = pData->mnCloseState; + DrawButtonFlags oldMenuState = pData->mnMenuState; + pData->mnCloseState &= ~DrawButtonFlags::Highlight; + pData->mnMenuState &= ~DrawButtonFlags::Highlight; + + Point aMousePos = rMEvt.GetPosPixel(); + BorderWindowHitTest nHitTest = ImplHitTest( pData, aMousePos ); + PointerStyle ePtrStyle = PointerStyle::Arrow; + if ( nHitTest & BorderWindowHitTest::Left ) + ePtrStyle = PointerStyle::WindowWSize; + else if ( nHitTest & BorderWindowHitTest::Right ) + ePtrStyle = PointerStyle::WindowESize; + else if ( nHitTest & BorderWindowHitTest::Top ) + ePtrStyle = PointerStyle::WindowNSize; + else if ( nHitTest & BorderWindowHitTest::Bottom ) + ePtrStyle = PointerStyle::WindowSSize; + else if ( nHitTest & BorderWindowHitTest::TopLeft ) + ePtrStyle = PointerStyle::WindowNWSize; + else if ( nHitTest & BorderWindowHitTest::BottomRight ) + ePtrStyle = PointerStyle::WindowSESize; + else if ( nHitTest & BorderWindowHitTest::TopRight ) + ePtrStyle = PointerStyle::WindowNESize; + else if ( nHitTest & BorderWindowHitTest::BottomLeft ) + ePtrStyle = PointerStyle::WindowSWSize; + else if ( nHitTest & BorderWindowHitTest::Close ) + pData->mnCloseState |= DrawButtonFlags::Highlight; + else if ( nHitTest & BorderWindowHitTest::Menu ) + pData->mnMenuState |= DrawButtonFlags::Highlight; + else if ( nHitTest & BorderWindowHitTest::Title && + pData->mnTitleType == BorderWindowTitleType::Tearoff && !rMEvt.IsLeaveWindow() ) + ePtrStyle = PointerStyle::Move; + pData->mpBorderWindow->SetPointer( ePtrStyle ); + + if( pData->mnCloseState != oldCloseState ) + pData->mpBorderWindow->Invalidate( pData->maCloseRect ); + if( pData->mnMenuState != oldMenuState ) + pData->mpBorderWindow->Invalidate( pData->maMenuRect ); +} + +OUString ImplBorderWindowView::ImplRequestHelp( ImplBorderFrameData const * pData, + const Point& rPos, + tools::Rectangle& rHelpRect ) +{ + TranslateId pHelpId; + OUString aHelpStr; + BorderWindowHitTest nHitTest = ImplHitTest( pData, rPos ); + if ( nHitTest != BorderWindowHitTest::NONE ) + { + if ( nHitTest & BorderWindowHitTest::Close ) + { + pHelpId = SV_HELPTEXT_CLOSE; + rHelpRect = pData->maCloseRect; + } + else if ( nHitTest & BorderWindowHitTest::Dock ) + { + pHelpId = SV_HELPTEXT_MAXIMIZE; + rHelpRect = pData->maDockRect; + } + else if ( nHitTest & BorderWindowHitTest::Hide ) + { + pHelpId = SV_HELPTEXT_MINIMIZE; + rHelpRect = pData->maHideRect; + } + else if ( nHitTest & BorderWindowHitTest::Help ) + { + pHelpId = SV_HELPTEXT_HELP; + rHelpRect = pData->maHelpRect; + } + else if ( nHitTest & BorderWindowHitTest::Title ) + { + if( !pData->maTitleRect.IsEmpty() ) + { + // tooltip only if title truncated + if( pData->mbTitleClipped ) + { + rHelpRect = pData->maTitleRect; + // no help id, use window title as help string + aHelpStr = pData->mpBorderWindow->GetText(); + } + } + } + } + + if (pHelpId) + aHelpStr = VclResId(pHelpId); + + return aHelpStr; +} + +tools::Long ImplBorderWindowView::ImplCalcTitleWidth( const ImplBorderFrameData* pData ) +{ + // title is not visible therefore no width + if ( !pData->mnTitleHeight ) + return 0; + + ImplBorderWindow* pBorderWindow = pData->mpBorderWindow; + tools::Long nTitleWidth = pBorderWindow->GetTextWidth( pBorderWindow->GetText() )+6; + nTitleWidth += pData->maCloseRect.GetWidth(); + nTitleWidth += pData->maDockRect.GetWidth(); + nTitleWidth += pData->maMenuRect.GetWidth(); + nTitleWidth += pData->maHideRect.GetWidth(); + nTitleWidth += pData->maHelpRect.GetWidth(); + nTitleWidth += pData->mnLeftBorder+pData->mnRightBorder; + return nTitleWidth; +} + + +ImplNoBorderWindowView::ImplNoBorderWindowView() +{ +} + +void ImplNoBorderWindowView::Init( OutputDevice*, tools::Long, tools::Long ) +{ +} + +void ImplNoBorderWindowView::GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder, + sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const +{ + rLeftBorder = 0; + rTopBorder = 0; + rRightBorder = 0; + rBottomBorder = 0; +} + +tools::Long ImplNoBorderWindowView::CalcTitleWidth() const +{ + return 0; +} + +void ImplNoBorderWindowView::DrawWindow(vcl::RenderContext&, const Point*) +{ +} + +ImplSmallBorderWindowView::ImplSmallBorderWindowView( ImplBorderWindow* pBorderWindow ) + : mpBorderWindow(pBorderWindow) + , mpOutDev(nullptr) + , mnWidth(0) + , mnHeight(0) + , mnLeftBorder(0) + , mnTopBorder(0) + , mnRightBorder(0) + , mnBottomBorder(0) + , mbNWFBorder(false) +{ +} + +void ImplSmallBorderWindowView::Init( OutputDevice* pDev, tools::Long nWidth, tools::Long nHeight ) +{ + mpOutDev = pDev; + mnWidth = nWidth; + mnHeight = nHeight; + mbNWFBorder = false; + + vcl::Window *pWin = mpOutDev->GetOwnerWindow(); + vcl::Window *pCtrl = nullptr; + if (pWin) + pCtrl = mpBorderWindow->GetWindow(GetWindowType::Client); + + tools::Long nOrigLeftBorder = mnLeftBorder; + tools::Long nOrigTopBorder = mnTopBorder; + tools::Long nOrigRightBorder = mnRightBorder; + tools::Long nOrigBottomBorder = mnBottomBorder; + + WindowBorderStyle nBorderStyle = mpBorderWindow->GetBorderStyle(); + if ( nBorderStyle & WindowBorderStyle::NOBORDER ) + { + mnLeftBorder = 0; + mnTopBorder = 0; + mnRightBorder = 0; + mnBottomBorder = 0; + } + else + { + // FIXME: this is currently only on macOS, check with other + // platforms + if( ImplGetSVData()->maNWFData.mbNoFocusRects && !( nBorderStyle & WindowBorderStyle::NWF ) ) + { + // for native widget drawing we must find out what + // control this border belongs to + ControlType aCtrlType = ControlType::Generic; + ControlPart aCtrlPart = ControlPart::Entire; + if (pCtrl) + { + switch( pCtrl->GetType() ) + { + case WindowType::LISTBOX: + if( pCtrl->GetStyle() & WB_DROPDOWN ) + { + aCtrlType = ControlType::Listbox; + mbNWFBorder = true; + } + break; + case WindowType::LISTBOXWINDOW: + aCtrlType = ControlType::Listbox; + aCtrlPart = ControlPart::ListboxWindow; + mbNWFBorder = true; + break; + case WindowType::COMBOBOX: + if( pCtrl->GetStyle() & WB_DROPDOWN ) + { + aCtrlType = ControlType::Combobox; + mbNWFBorder = true; + } + break; + case WindowType::MULTILINEEDIT: + aCtrlType = ControlType::MultilineEditbox; + mbNWFBorder = true; + break; + case WindowType::EDIT: + case WindowType::PATTERNFIELD: + case WindowType::METRICFIELD: + case WindowType::CURRENCYFIELD: + case WindowType::DATEFIELD: + case WindowType::TIMEFIELD: + case WindowType::SPINFIELD: + case WindowType::FORMATTEDFIELD: + mbNWFBorder = true; + if (pCtrl->GetStyle() & WB_SPIN) + aCtrlType = ControlType::Spinbox; + else + aCtrlType = ControlType::Editbox; + break; + default: + break; + } + } + if( mbNWFBorder ) + { + ImplControlValue aControlValue; + Size aMinSize( mnWidth, mnHeight ); + if( aMinSize.Width() < 10 ) aMinSize.setWidth( 10 ); + if( aMinSize.Height() < 10 ) aMinSize.setHeight( 10 ); + tools::Rectangle aCtrlRegion( Point(), aMinSize ); + tools::Rectangle aBounds, aContent; + if( pWin->GetNativeControlRegion( aCtrlType, aCtrlPart, aCtrlRegion, + ControlState::ENABLED, aControlValue, + aBounds, aContent ) ) + { + aBounds.AdjustLeft(mnLeftBorder); + aBounds.AdjustRight(-mnRightBorder); + aBounds.AdjustTop(mnTopBorder); + aBounds.AdjustBottom(-mnBottomBorder); + aContent.AdjustLeft(mnLeftBorder); + aContent.AdjustRight(-mnRightBorder); + aContent.AdjustTop(mnTopBorder); + aContent.AdjustBottom(-mnBottomBorder); + mnLeftBorder = aContent.Left() - aBounds.Left(); + mnRightBorder = aBounds.Right() - aContent.Right(); + mnTopBorder = aContent.Top() - aBounds.Top(); + mnBottomBorder = aBounds.Bottom() - aContent.Bottom(); + if( mnWidth && mnHeight ) + { + + mpBorderWindow->SetPaintTransparent( true ); + mpBorderWindow->SetBackground(); + if (!pCtrl->IsControlBackground()) + pCtrl->SetPaintTransparent(true); + + vcl::Window* pCompoundParent = nullptr; + if( pWin->GetParent() && pWin->GetParent()->IsCompoundControl() ) + pCompoundParent = pWin->GetParent(); + + if( pCompoundParent ) + pCompoundParent->SetPaintTransparent( true ); + + if( mnWidth < aBounds.GetWidth() || mnHeight < aBounds.GetHeight() ) + { + if( ! pCompoundParent ) // compound controls have to fix themselves + { + Point aPos( mpBorderWindow->GetPosPixel() ); + if( mnWidth < aBounds.GetWidth() ) + aPos.AdjustX( -((aBounds.GetWidth() - mnWidth) / 2) ); + if( mnHeight < aBounds.GetHeight() ) + aPos.AdjustY( -((aBounds.GetHeight() - mnHeight) / 2) ); + mpBorderWindow->SetPosSizePixel( aPos, aBounds.GetSize() ); + } + } + } + } + else + mbNWFBorder = false; + } + } + + if( ! mbNWFBorder ) + { + DrawFrameStyle nStyle = DrawFrameStyle::NONE; + DrawFrameFlags nFlags = DrawFrameFlags::NoDraw; + // move border outside if border was converted or if the BorderWindow is a frame window, + if ( mpBorderWindow->mbSmallOutBorder ) + nStyle = DrawFrameStyle::DoubleOut; + else if ( nBorderStyle & WindowBorderStyle::NWF ) + nStyle = DrawFrameStyle::NWF; + else + nStyle = DrawFrameStyle::DoubleIn; + if ( nBorderStyle & WindowBorderStyle::MONO ) + nFlags |= DrawFrameFlags::Mono; + + DecorationView aDecoView( mpOutDev ); + tools::Rectangle aRect( 0, 0, 10, 10 ); + tools::Rectangle aCalcRect = aDecoView.DrawFrame( aRect, nStyle, nFlags ); + mnLeftBorder = aCalcRect.Left(); + mnTopBorder = aCalcRect.Top(); + mnRightBorder = aRect.Right()-aCalcRect.Right(); + mnBottomBorder = aRect.Bottom()-aCalcRect.Bottom(); + } + } + + if (pCtrl) + { + //fdo#57090 If the borders have changed, then trigger a queue_resize on + //the bordered window, which will resync its borders at that point + if (nOrigLeftBorder != mnLeftBorder || + nOrigTopBorder != mnTopBorder || + nOrigRightBorder != mnRightBorder || + nOrigBottomBorder != mnBottomBorder) + { + pCtrl->queue_resize(); + } + } +} + +void ImplSmallBorderWindowView::GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder, + sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const +{ + rLeftBorder = mnLeftBorder; + rTopBorder = mnTopBorder; + rRightBorder = mnRightBorder; + rBottomBorder = mnBottomBorder; +} + +tools::Long ImplSmallBorderWindowView::CalcTitleWidth() const +{ + return 0; +} + +void ImplSmallBorderWindowView::DrawWindow(vcl::RenderContext& rRenderContext, const Point*) +{ + WindowBorderStyle nBorderStyle = mpBorderWindow->GetBorderStyle(); + if (nBorderStyle & WindowBorderStyle::NOBORDER) + return; + + bool bNativeOK = false; + // for native widget drawing we must find out what + // control this border belongs to + vcl::Window* pCtrl = mpBorderWindow->GetWindow(GetWindowType::Client); + + ControlType aCtrlType = ControlType::Generic; + ControlPart aCtrlPart = ControlPart::Entire; + if (pCtrl) + { + switch (pCtrl->GetType()) + { + case WindowType::MULTILINEEDIT: + aCtrlType = ControlType::MultilineEditbox; + break; + case WindowType::EDIT: + case WindowType::PATTERNFIELD: + case WindowType::METRICFIELD: + case WindowType::CURRENCYFIELD: + case WindowType::DATEFIELD: + case WindowType::TIMEFIELD: + case WindowType::SPINFIELD: + case WindowType::FORMATTEDFIELD: + if (pCtrl->GetStyle() & WB_SPIN) + aCtrlType = ControlType::Spinbox; + else + aCtrlType = ControlType::Editbox; + break; + + case WindowType::LISTBOX: + case WindowType::MULTILISTBOX: + case WindowType::TREELISTBOX: + aCtrlType = ControlType::Listbox; + if (pCtrl->GetStyle() & WB_DROPDOWN) + aCtrlPart = ControlPart::Entire; + else + aCtrlPart = ControlPart::ListboxWindow; + break; + + case WindowType::LISTBOXWINDOW: + aCtrlType = ControlType::Listbox; + aCtrlPart = ControlPart::ListboxWindow; + break; + + case WindowType::COMBOBOX: + case WindowType::PATTERNBOX: + case WindowType::NUMERICBOX: + case WindowType::METRICBOX: + case WindowType::CURRENCYBOX: + case WindowType::DATEBOX: + case WindowType::TIMEBOX: + case WindowType::LONGCURRENCYBOX: + if (pCtrl->GetStyle() & WB_DROPDOWN) + { + aCtrlType = ControlType::Combobox; + aCtrlPart = ControlPart::Entire; + } + else + { + aCtrlType = ControlType::Listbox; + aCtrlPart = ControlPart::ListboxWindow; + } + break; + + default: + break; + } + } + + if (aCtrlType != ControlType::Generic && pCtrl->IsNativeControlSupported(aCtrlType, aCtrlPart)) + { + ImplControlValue aControlValue; + ControlState nState = ControlState::ENABLED; + + if (!mpBorderWindow->IsEnabled()) + nState &= ~ControlState::ENABLED; + if (mpBorderWindow->HasFocus()) + nState |= ControlState::FOCUSED; + else if(mbNWFBorder) + { + // FIXME: this is currently only on macOS, see if other platforms can profit + + // FIXME: for macOS focus rings all controls need to support GetNativeControlRegion + // for the dropdown style + if (pCtrl->HasFocus() || pCtrl->HasChildPathFocus()) + nState |= ControlState::FOCUSED; + } + + bool bMouseOver = false; + vcl::Window *pCtrlChild = pCtrl->GetWindow(GetWindowType::FirstChild); + while(pCtrlChild) + { + bMouseOver = pCtrlChild->IsMouseOver(); + if (bMouseOver) + break; + pCtrlChild = pCtrlChild->GetWindow(GetWindowType::Next); + } + + if (bMouseOver) + nState |= ControlState::ROLLOVER; + + Point aPoint; + tools::Rectangle aCtrlRegion(aPoint, Size(mnWidth, mnHeight)); + + tools::Rectangle aBoundingRgn(aPoint, Size(mnWidth, mnHeight)); + tools::Rectangle aContentRgn(aCtrlRegion); + if (!ImplGetSVData()->maNWFData.mbCanDrawWidgetAnySize && + rRenderContext.GetNativeControlRegion(aCtrlType, aCtrlPart, aCtrlRegion, + nState, aControlValue, + aBoundingRgn, aContentRgn)) + { + aCtrlRegion=aContentRgn; + } + + Color aBackgroundColor = COL_AUTO; + if (pCtrl->IsControlBackground()) + aBackgroundColor = pCtrl->GetBackgroundColor(); + bNativeOK = rRenderContext.DrawNativeControl(aCtrlType, aCtrlPart, aCtrlRegion, nState, aControlValue, OUString(), aBackgroundColor); + + // if the native theme draws the spinbuttons in one call, make sure the proper settings + // are passed, this might force a redraw though... (TODO: improve) + if ((aCtrlType == ControlType::Spinbox) && !pCtrl->IsNativeControlSupported(ControlType::Spinbox, ControlPart::ButtonUp)) + { + Edit* pEdit = static_cast<Edit*>(pCtrl)->GetSubEdit(); + if (pEdit && !pEdit->SupportsDoubleBuffering()) + pCtrl->Paint(*pCtrl->GetOutDev(), tools::Rectangle()); // make sure the buttons are also drawn as they might overwrite the border + } + } + + if (bNativeOK) + return; + + DrawFrameStyle nStyle = DrawFrameStyle::NONE; + DrawFrameFlags nFlags = DrawFrameFlags::NONE; + // move border outside if border was converted or if the border window is a frame window, + if (mpBorderWindow->mbSmallOutBorder) + nStyle = DrawFrameStyle::DoubleOut; + else if (nBorderStyle & WindowBorderStyle::NWF) + nStyle = DrawFrameStyle::NWF; + else + nStyle = DrawFrameStyle::DoubleIn; + if (nBorderStyle & WindowBorderStyle::MONO) + nFlags |= DrawFrameFlags::Mono; + if (nBorderStyle & WindowBorderStyle::MENU) + nFlags |= DrawFrameFlags::Menu; + // tell DrawFrame that we're drawing a window border of a frame window to avoid round corners + if (mpBorderWindow == mpBorderWindow->ImplGetFrameWindow()) + nFlags |= DrawFrameFlags::WindowBorder; + + DecorationView aDecoView(&rRenderContext); + tools::Rectangle aInRect(Point(), Size(mnWidth, mnHeight)); + aDecoView.DrawFrame(aInRect, nStyle, nFlags); +} + + +ImplStdBorderWindowView::ImplStdBorderWindowView( ImplBorderWindow* pBorderWindow ) +{ + maFrameData.mpBorderWindow = pBorderWindow; + maFrameData.mbDragFull = false; + maFrameData.mnHitTest = BorderWindowHitTest::NONE; + maFrameData.mnCloseState = DrawButtonFlags::NONE; + maFrameData.mnDockState = DrawButtonFlags::NONE; + maFrameData.mnMenuState = DrawButtonFlags::NONE; + maFrameData.mnHideState = DrawButtonFlags::NONE; + maFrameData.mnHelpState = DrawButtonFlags::NONE; + maFrameData.mbTitleClipped = false; +} + +ImplStdBorderWindowView::~ImplStdBorderWindowView() +{ +} + +bool ImplStdBorderWindowView::MouseMove( const MouseEvent& rMEvt ) +{ + ImplMouseMove( &maFrameData, rMEvt ); + return true; +} + +bool ImplStdBorderWindowView::MouseButtonDown( const MouseEvent& rMEvt ) +{ + ImplBorderWindow* pBorderWindow = maFrameData.mpBorderWindow; + + if ( rMEvt.IsLeft() || rMEvt.IsRight() ) + { + maFrameData.maMouseOff = rMEvt.GetPosPixel(); + maFrameData.mnHitTest = ImplHitTest( &maFrameData, maFrameData.maMouseOff ); + if ( maFrameData.mnHitTest != BorderWindowHitTest::NONE ) + { + DragFullOptions nDragFullTest = DragFullOptions::NONE; + bool bTracking = true; + bool bHitTest = true; + + if ( maFrameData.mnHitTest & BorderWindowHitTest::Close ) + { + maFrameData.mnCloseState |= DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + else if ( maFrameData.mnHitTest & BorderWindowHitTest::Dock ) + { + maFrameData.mnDockState |= DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + else if ( maFrameData.mnHitTest & BorderWindowHitTest::Menu ) + { + maFrameData.mnMenuState |= DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + + // call handler already on mouse down + if ( pBorderWindow->ImplGetClientWindow()->IsSystemWindow() ) + { + SystemWindow* pClientWindow = static_cast<SystemWindow*>(pBorderWindow->ImplGetClientWindow()); + pClientWindow->TitleButtonClick( TitleButton::Menu ); + } + + maFrameData.mnMenuState &= ~DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + + bTracking = false; + } + else if ( maFrameData.mnHitTest & BorderWindowHitTest::Hide ) + { + maFrameData.mnHideState |= DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + else if ( maFrameData.mnHitTest & BorderWindowHitTest::Help ) + { + maFrameData.mnHelpState |= DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + else + { + if ( rMEvt.GetClicks() == 1 ) + { + Point aPos = pBorderWindow->GetPosPixel(); + Size aSize = pBorderWindow->GetOutputSizePixel(); + maFrameData.mnTrackX = aPos.X(); + maFrameData.mnTrackY = aPos.Y(); + maFrameData.mnTrackWidth = aSize.Width(); + maFrameData.mnTrackHeight = aSize.Height(); + + if (maFrameData.mnHitTest & BorderWindowHitTest::Title) + nDragFullTest = DragFullOptions::WindowMove; + else + nDragFullTest = DragFullOptions::WindowSize; + } + else + { + bTracking = false; + + if ( (maFrameData.mnHitTest & BorderWindowHitTest::Title) && + ((rMEvt.GetClicks() % 2) == 0) ) + { + maFrameData.mnHitTest = BorderWindowHitTest::NONE; + bHitTest = false; + + if ( pBorderWindow->ImplGetClientWindow()->IsSystemWindow() ) + { + SystemWindow* pClientWindow = static_cast<SystemWindow*>(pBorderWindow->ImplGetClientWindow()); + // always perform docking on double click, no button required + pClientWindow->TitleButtonClick( TitleButton::Docking ); + } + } + } + } + + if ( bTracking ) + { + maFrameData.mbDragFull = false; + if ( nDragFullTest != DragFullOptions::NONE ) + maFrameData.mbDragFull = true; // always fulldrag for proper docking, ignore system settings + pBorderWindow->StartTracking(); + } + else if ( bHitTest ) + maFrameData.mnHitTest = BorderWindowHitTest::NONE; + } + } + + return true; +} + +bool ImplStdBorderWindowView::Tracking( const TrackingEvent& rTEvt ) +{ + ImplBorderWindow* pBorderWindow = maFrameData.mpBorderWindow; + + if ( rTEvt.IsTrackingEnded() ) + { + BorderWindowHitTest nHitTest = maFrameData.mnHitTest; + maFrameData.mnHitTest = BorderWindowHitTest::NONE; + + if ( nHitTest & BorderWindowHitTest::Close ) + { + if ( maFrameData.mnCloseState & DrawButtonFlags::Pressed ) + { + maFrameData.mnCloseState &= ~DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + + // do not call a Click-Handler when aborting + if ( !rTEvt.IsTrackingCanceled() ) + { + // dispatch to correct window type (why is Close() not virtual ??? ) + // TODO: make Close() virtual + VclPtr<vcl::Window> pWin = pBorderWindow->ImplGetClientWindow()->ImplGetWindow(); + SystemWindow *pSysWin = dynamic_cast<SystemWindow* >(pWin.get()); + DockingWindow *pDockWin = dynamic_cast<DockingWindow*>(pWin.get()); + if ( pSysWin ) + pSysWin->Close(); + else if ( pDockWin ) + pDockWin->Close(); + } + } + } + else if ( nHitTest & BorderWindowHitTest::Dock ) + { + if ( maFrameData.mnDockState & DrawButtonFlags::Pressed ) + { + maFrameData.mnDockState &= ~DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + + // do not call a Click-Handler when aborting + if ( !rTEvt.IsTrackingCanceled() ) + { + if ( pBorderWindow->ImplGetClientWindow()->IsSystemWindow() ) + { + SystemWindow* pClientWindow = static_cast<SystemWindow*>(pBorderWindow->ImplGetClientWindow()); + pClientWindow->TitleButtonClick( TitleButton::Docking ); + } + } + } + } + else if ( nHitTest & BorderWindowHitTest::Menu ) + { + if ( maFrameData.mnMenuState & DrawButtonFlags::Pressed ) + { + maFrameData.mnMenuState &= ~DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + + // handler already called on mouse down + } + } + else if ( nHitTest & BorderWindowHitTest::Hide ) + { + if ( maFrameData.mnHideState & DrawButtonFlags::Pressed ) + { + maFrameData.mnHideState &= ~DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + + // do not call a Click-Handler when aborting + if ( !rTEvt.IsTrackingCanceled() ) + { + if ( pBorderWindow->ImplGetClientWindow()->IsSystemWindow() ) + { + SystemWindow* pClientWindow = static_cast<SystemWindow*>(pBorderWindow->ImplGetClientWindow()); + pClientWindow->TitleButtonClick( TitleButton::Hide ); + } + } + } + } + else if ( nHitTest & BorderWindowHitTest::Help ) + { + if ( maFrameData.mnHelpState & DrawButtonFlags::Pressed ) + { + maFrameData.mnHelpState &= ~DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + } + else + { + if ( maFrameData.mbDragFull ) + { + // restore old state when aborting + if ( rTEvt.IsTrackingCanceled() ) + pBorderWindow->SetPosSizePixel( Point( maFrameData.mnTrackX, maFrameData.mnTrackY ), Size( maFrameData.mnTrackWidth, maFrameData.mnTrackHeight ) ); + } + else + { + pBorderWindow->HideTracking(); + if ( !rTEvt.IsTrackingCanceled() ) + pBorderWindow->SetPosSizePixel( Point( maFrameData.mnTrackX, maFrameData.mnTrackY ), Size( maFrameData.mnTrackWidth, maFrameData.mnTrackHeight ) ); + } + + if ( !rTEvt.IsTrackingCanceled() ) + { + if ( pBorderWindow->ImplGetClientWindow()->ImplIsFloatingWindow() ) + { + if ( static_cast<FloatingWindow*>(pBorderWindow->ImplGetClientWindow())->IsInPopupMode() ) + static_cast<FloatingWindow*>(pBorderWindow->ImplGetClientWindow())->EndPopupMode( FloatWinPopupEndFlags::TearOff ); + } + } + } + } + else if ( !rTEvt.GetMouseEvent().IsSynthetic() ) + { + Point aMousePos = rTEvt.GetMouseEvent().GetPosPixel(); + + if ( maFrameData.mnHitTest & BorderWindowHitTest::Close ) + { + if ( maFrameData.maCloseRect.Contains( aMousePos ) ) + { + if ( !(maFrameData.mnCloseState & DrawButtonFlags::Pressed) ) + { + maFrameData.mnCloseState |= DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + } + else + { + if ( maFrameData.mnCloseState & DrawButtonFlags::Pressed ) + { + maFrameData.mnCloseState &= ~DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + } + } + else if ( maFrameData.mnHitTest & BorderWindowHitTest::Dock ) + { + if ( maFrameData.maDockRect.Contains( aMousePos ) ) + { + if ( !(maFrameData.mnDockState & DrawButtonFlags::Pressed) ) + { + maFrameData.mnDockState |= DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + } + else + { + if ( maFrameData.mnDockState & DrawButtonFlags::Pressed ) + { + maFrameData.mnDockState &= ~DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + } + } + else if ( maFrameData.mnHitTest & BorderWindowHitTest::Menu ) + { + if ( maFrameData.maMenuRect.Contains( aMousePos ) ) + { + if ( !(maFrameData.mnMenuState & DrawButtonFlags::Pressed) ) + { + maFrameData.mnMenuState |= DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + } + else + { + if ( maFrameData.mnMenuState & DrawButtonFlags::Pressed ) + { + maFrameData.mnMenuState &= ~DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + } + } + else if ( maFrameData.mnHitTest & BorderWindowHitTest::Hide ) + { + if ( maFrameData.maHideRect.Contains( aMousePos ) ) + { + if ( !(maFrameData.mnHideState & DrawButtonFlags::Pressed) ) + { + maFrameData.mnHideState |= DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + } + else + { + if ( maFrameData.mnHideState & DrawButtonFlags::Pressed ) + { + maFrameData.mnHideState &= ~DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + } + } + else if ( maFrameData.mnHitTest & BorderWindowHitTest::Help ) + { + if ( maFrameData.maHelpRect.Contains( aMousePos ) ) + { + if ( !(maFrameData.mnHelpState & DrawButtonFlags::Pressed) ) + { + maFrameData.mnHelpState |= DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + } + else + { + if ( maFrameData.mnHelpState & DrawButtonFlags::Pressed ) + { + maFrameData.mnHelpState &= ~DrawButtonFlags::Pressed; + pBorderWindow->InvalidateBorder(); + } + } + } + else + { + aMousePos.AdjustX( -(maFrameData.maMouseOff.X()) ); + aMousePos.AdjustY( -(maFrameData.maMouseOff.Y()) ); + + if ( maFrameData.mnHitTest & BorderWindowHitTest::Title ) + { + maFrameData.mpBorderWindow->SetPointer( PointerStyle::Move ); + + Point aPos = pBorderWindow->GetPosPixel(); + aPos.AdjustX(aMousePos.X() ); + aPos.AdjustY(aMousePos.Y() ); + if ( maFrameData.mbDragFull ) + { + pBorderWindow->SetPosPixel( aPos ); + pBorderWindow->ImplUpdateAll(); + pBorderWindow->ImplGetFrameWindow()->ImplUpdateAll(); + } + else + { + maFrameData.mnTrackX = aPos.X(); + maFrameData.mnTrackY = aPos.Y(); + pBorderWindow->ShowTracking( tools::Rectangle( pBorderWindow->ScreenToOutputPixel( aPos ), pBorderWindow->GetOutputSizePixel() ), ShowTrackFlags::Big ); + } + } + else + { + Point aOldPos = pBorderWindow->GetPosPixel(); + Size aSize = pBorderWindow->GetSizePixel(); + tools::Rectangle aNewRect( aOldPos, aSize ); + tools::Long nOldWidth = aSize.Width(); + tools::Long nOldHeight = aSize.Height(); + tools::Long nBorderWidth = maFrameData.mnLeftBorder+maFrameData.mnRightBorder; + tools::Long nBorderHeight = maFrameData.mnTopBorder+maFrameData.mnBottomBorder; + tools::Long nMinWidth = pBorderWindow->mnMinWidth+nBorderWidth; + tools::Long nMinHeight = pBorderWindow->mnMinHeight+nBorderHeight; + tools::Long nMinWidth2 = nBorderWidth; + tools::Long nMaxWidth = pBorderWindow->mnMaxWidth+nBorderWidth; + tools::Long nMaxHeight = pBorderWindow->mnMaxHeight+nBorderHeight; + + if ( maFrameData.mnTitleHeight ) + { + nMinWidth2 += 4; + + if ( pBorderWindow->GetStyle() & WB_CLOSEABLE ) + nMinWidth2 += maFrameData.maCloseRect.GetWidth(); + } + if ( nMinWidth2 > nMinWidth ) + nMinWidth = nMinWidth2; + if ( maFrameData.mnHitTest & (BorderWindowHitTest::Left | BorderWindowHitTest::TopLeft | BorderWindowHitTest::BottomLeft) ) + { + aNewRect.AdjustLeft(aMousePos.X() ); + if ( aNewRect.GetWidth() < nMinWidth ) + aNewRect.SetLeft( aNewRect.Right()-nMinWidth+1 ); + else if ( aNewRect.GetWidth() > nMaxWidth ) + aNewRect.SetLeft( aNewRect.Right()-nMaxWidth+1 ); + } + else if ( maFrameData.mnHitTest & (BorderWindowHitTest::Right | BorderWindowHitTest::TopRight | BorderWindowHitTest::BottomRight) ) + { + aNewRect.AdjustRight(aMousePos.X() ); + if ( aNewRect.GetWidth() < nMinWidth ) + aNewRect.SetRight( aNewRect.Left()+nMinWidth+1 ); + else if ( aNewRect.GetWidth() > nMaxWidth ) + aNewRect.SetRight( aNewRect.Left()+nMaxWidth+1 ); + } + if ( maFrameData.mnHitTest & (BorderWindowHitTest::Top | BorderWindowHitTest::TopLeft | BorderWindowHitTest::TopRight) ) + { + aNewRect.AdjustTop(aMousePos.Y() ); + if ( aNewRect.GetHeight() < nMinHeight ) + aNewRect.SetTop( aNewRect.Bottom()-nMinHeight+1 ); + else if ( aNewRect.GetHeight() > nMaxHeight ) + aNewRect.SetTop( aNewRect.Bottom()-nMaxHeight+1 ); + } + else if ( maFrameData.mnHitTest & (BorderWindowHitTest::Bottom | BorderWindowHitTest::BottomLeft | BorderWindowHitTest::BottomRight) ) + { + aNewRect.AdjustBottom(aMousePos.Y() ); + if ( aNewRect.GetHeight() < nMinHeight ) + aNewRect.SetBottom( aNewRect.Top()+nMinHeight+1 ); + else if ( aNewRect.GetHeight() > nMaxHeight ) + aNewRect.SetBottom( aNewRect.Top()+nMaxHeight+1 ); + } + + // call Resizing-Handler for SystemWindows + if ( pBorderWindow->ImplGetClientWindow()->IsSystemWindow() ) + { + // adjust size for Resizing-call + aSize = aNewRect.GetSize(); + aSize.AdjustWidth( -nBorderWidth ); + aSize.AdjustHeight( -nBorderHeight ); + static_cast<SystemWindow*>(pBorderWindow->ImplGetClientWindow())->Resizing( aSize ); + aSize.AdjustWidth(nBorderWidth ); + aSize.AdjustHeight(nBorderHeight ); + if ( aSize.Width() < nMinWidth ) + aSize.setWidth( nMinWidth ); + if ( aSize.Height() < nMinHeight ) + aSize.setHeight( nMinHeight ); + if ( aSize.Width() > nMaxWidth ) + aSize.setWidth( nMaxWidth ); + if ( aSize.Height() > nMaxHeight ) + aSize.setHeight( nMaxHeight ); + if ( maFrameData.mnHitTest & (BorderWindowHitTest::Left | BorderWindowHitTest::TopLeft | BorderWindowHitTest::BottomLeft) ) + aNewRect.SetLeft( aNewRect.Right()-aSize.Width()+1 ); + else + aNewRect.SetRight( aNewRect.Left()+aSize.Width()-1 ); + if ( maFrameData.mnHitTest & (BorderWindowHitTest::Top | BorderWindowHitTest::TopLeft | BorderWindowHitTest::TopRight) ) + aNewRect.SetTop( aNewRect.Bottom()-aSize.Height()+1 ); + else + aNewRect.SetBottom( aNewRect.Top()+aSize.Height()-1 ); + } + + if ( maFrameData.mbDragFull ) + { + // no move (only resize) if position did not change + if( aOldPos != aNewRect.TopLeft() ) + pBorderWindow->setPosSizePixel( aNewRect.Left(), aNewRect.Top(), + aNewRect.GetWidth(), aNewRect.GetHeight() ); + else + pBorderWindow->setPosSizePixel( aNewRect.Left(), aNewRect.Top(), + aNewRect.GetWidth(), aNewRect.GetHeight(), PosSizeFlags::Size ); + + pBorderWindow->ImplUpdateAll(); + pBorderWindow->ImplGetFrameWindow()->ImplUpdateAll(); + if ( maFrameData.mnHitTest & (BorderWindowHitTest::Right | BorderWindowHitTest::TopRight | BorderWindowHitTest::BottomRight) ) + maFrameData.maMouseOff.AdjustX(aNewRect.GetWidth()-nOldWidth ); + if ( maFrameData.mnHitTest & (BorderWindowHitTest::Bottom | BorderWindowHitTest::BottomLeft | BorderWindowHitTest::BottomRight) ) + maFrameData.maMouseOff.AdjustY(aNewRect.GetHeight()-nOldHeight ); + } + else + { + maFrameData.mnTrackX = aNewRect.Left(); + maFrameData.mnTrackY = aNewRect.Top(); + maFrameData.mnTrackWidth = aNewRect.GetWidth(); + maFrameData.mnTrackHeight = aNewRect.GetHeight(); + pBorderWindow->ShowTracking( tools::Rectangle( pBorderWindow->ScreenToOutputPixel( aNewRect.TopLeft() ), aNewRect.GetSize() ), ShowTrackFlags::Big ); + } + } + } + } + + return true; +} + +OUString ImplStdBorderWindowView::RequestHelp( const Point& rPos, tools::Rectangle& rHelpRect ) +{ + return ImplRequestHelp( &maFrameData, rPos, rHelpRect ); +} + +tools::Rectangle ImplStdBorderWindowView::GetMenuRect() const +{ + return maFrameData.maMenuRect; +} + +void ImplStdBorderWindowView::Init( OutputDevice* pDev, tools::Long nWidth, tools::Long nHeight ) +{ + ImplBorderFrameData* pData = &maFrameData; + ImplBorderWindow* pBorderWindow = maFrameData.mpBorderWindow; + const StyleSettings& rStyleSettings = pDev->GetSettings().GetStyleSettings(); + DecorationView aDecoView( pDev ); + tools::Rectangle aRect( 0, 0, 10, 10 ); + tools::Rectangle aCalcRect = aDecoView.DrawFrame( aRect, DrawFrameStyle::DoubleOut, DrawFrameFlags::NoDraw ); + + pData->mpOutDev = pDev; + pData->mnWidth = nWidth; + pData->mnHeight = nHeight; + + pData->mnTitleType = pBorderWindow->mnTitleType; + + if ( !(pBorderWindow->GetStyle() & (WB_MOVEABLE | WB_POPUP)) || (pData->mnTitleType == BorderWindowTitleType::NONE) ) + pData->mnBorderSize = 0; + else if ( pData->mnTitleType == BorderWindowTitleType::Tearoff ) + pData->mnBorderSize = 0; + else + pData->mnBorderSize = StyleSettings::GetBorderSize(); + pData->mnLeftBorder = aCalcRect.Left(); + pData->mnTopBorder = aCalcRect.Top(); + pData->mnRightBorder = aRect.Right()-aCalcRect.Right(); + pData->mnBottomBorder = aRect.Bottom()-aCalcRect.Bottom(); + pData->mnLeftBorder += pData->mnBorderSize; + pData->mnTopBorder += pData->mnBorderSize; + pData->mnRightBorder += pData->mnBorderSize; + pData->mnBottomBorder += pData->mnBorderSize; + pData->mnNoTitleTop = pData->mnTopBorder; + + ImplInitTitle(&maFrameData); + if (pData->mnTitleHeight) + { + // to improve symbol display force a minimum title height + if (pData->mnTitleType != BorderWindowTitleType::Tearoff && + pData->mnTitleHeight < MIN_CAPTION_HEIGHT) + pData->mnTitleHeight = MIN_CAPTION_HEIGHT; + + // set a proper background for drawing + // highlighted buttons in the title + pBorderWindow->SetBackground( rStyleSettings.GetFaceColor() ); + + pData->maTitleRect.SetLeft( pData->mnLeftBorder ); + pData->maTitleRect.SetRight( nWidth-pData->mnRightBorder-1 ); + pData->maTitleRect.SetTop( pData->mnTopBorder ); + pData->maTitleRect.SetBottom( pData->maTitleRect.Top()+pData->mnTitleHeight-1 ); + + if ( pData->mnTitleType & (BorderWindowTitleType::Normal | BorderWindowTitleType::Small) ) + { + tools::Long nRight = pData->maTitleRect.Right() - 3; + tools::Long const nItemTop = pData->maTitleRect.Top() + 2; + tools::Long const nItemBottom = pData->maTitleRect.Bottom() - 2; + + auto addSquareOnRight = [&nRight, nItemTop, nItemBottom]( + tools::Rectangle & rect, tools::Long gap) + { + rect.SetTop( nItemTop ); + rect.SetBottom( nItemBottom ); + rect.SetRight( nRight ); + rect.SetLeft( rect.Right() - rect.GetHeight() + 1 ); + nRight -= rect.GetWidth() + gap; + }; + + if ( pBorderWindow->GetStyle() & WB_CLOSEABLE ) + { + addSquareOnRight(pData->maCloseRect, 3); + } + + if ( pBorderWindow->mbMenuBtn ) + { + addSquareOnRight(pData->maMenuRect, 0); + } + + if ( pBorderWindow->mbDockBtn ) + { + addSquareOnRight(pData->maDockRect, 0); + } + + if ( pBorderWindow->mbHideBtn ) + { + addSquareOnRight(pData->maHideRect, 0); + } + } + else + { + pData->maCloseRect.SetEmpty(); + pData->maDockRect.SetEmpty(); + pData->maMenuRect.SetEmpty(); + pData->maHideRect.SetEmpty(); + pData->maHelpRect.SetEmpty(); + } + + pData->mnTopBorder += pData->mnTitleHeight; + } + else + { + pData->maTitleRect.SetEmpty(); + pData->maCloseRect.SetEmpty(); + pData->maDockRect.SetEmpty(); + pData->maMenuRect.SetEmpty(); + pData->maHideRect.SetEmpty(); + pData->maHelpRect.SetEmpty(); + } +} + +void ImplStdBorderWindowView::GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder, + sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const +{ + rLeftBorder = maFrameData.mnLeftBorder; + rTopBorder = maFrameData.mnTopBorder; + rRightBorder = maFrameData.mnRightBorder; + rBottomBorder = maFrameData.mnBottomBorder; +} + +tools::Long ImplStdBorderWindowView::CalcTitleWidth() const +{ + return ImplCalcTitleWidth( &maFrameData ); +} + +void ImplStdBorderWindowView::DrawWindow(vcl::RenderContext& rRenderContext, const Point* pOffset) +{ + ImplBorderFrameData* pData = &maFrameData; + ImplBorderWindow* pBorderWindow = pData->mpBorderWindow; + Point aTmpPoint = pOffset ? *pOffset : Point(); + tools::Rectangle aInRect( aTmpPoint, Size( pData->mnWidth, pData->mnHeight ) ); + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + Color aFaceColor(rStyleSettings.GetFaceColor()); + Color aFrameColor(aFaceColor); + + aFrameColor.DecreaseContrast(sal_uInt8(0.5 * 255)); + + // Draw Frame + vcl::Region oldClipRgn(rRenderContext.GetClipRegion()); + + // for popups, don't draw part of the frame + if (!(pData->mnTitleType & (BorderWindowTitleType::Normal | BorderWindowTitleType::Small))) + { + FloatingWindow* pWin = dynamic_cast<FloatingWindow*>(pData->mpBorderWindow->GetWindow(GetWindowType::Client)); + if (pWin) + { + vcl::Region aClipRgn(aInRect); + tools::Rectangle aItemClipRect(pWin->ImplGetItemEdgeClipRect()); + if (!aItemClipRect.IsEmpty()) + { + aItemClipRect.SetPos(pData->mpBorderWindow->AbsoluteScreenToOutputPixel(aItemClipRect.TopLeft())); + aClipRgn.Exclude(aItemClipRect); + rRenderContext.SetClipRegion(aClipRgn); + } + } + } + + // single line frame + rRenderContext.SetLineColor(aFrameColor); + rRenderContext.SetFillColor(); + rRenderContext.DrawRect(aInRect); + aInRect.AdjustLeft( 1 ); + aInRect.AdjustRight( -1 ); + aInRect.AdjustTop( 1 ); + aInRect.AdjustBottom( -1 ); + + // restore + if (!(pData->mnTitleType & (BorderWindowTitleType::Normal | BorderWindowTitleType::Small))) + rRenderContext.SetClipRegion(oldClipRgn); + + // Draw Border + rRenderContext.SetLineColor(); + tools::Long nBorderSize = pData->mnBorderSize; + if (nBorderSize) + { + rRenderContext.SetFillColor(rStyleSettings.GetFaceColor()); + rRenderContext.DrawRect(tools::Rectangle(Point(aInRect.Left(), aInRect.Top()), + Size(aInRect.GetWidth(), nBorderSize))); + rRenderContext.DrawRect(tools::Rectangle(Point(aInRect.Left(), aInRect.Top() + nBorderSize), + Size(nBorderSize, aInRect.GetHeight() - nBorderSize))); + rRenderContext.DrawRect(tools::Rectangle(Point(aInRect.Left(), aInRect.Bottom() - nBorderSize + 1), + Size(aInRect.GetWidth(), nBorderSize))); + rRenderContext.DrawRect(tools::Rectangle(Point(aInRect.Right()-nBorderSize + 1, aInRect.Top() + nBorderSize), + Size(nBorderSize, aInRect.GetHeight() - nBorderSize))); + } + + // Draw Title + if (!pData->maTitleRect.IsEmpty()) + { + aInRect = pData->maTitleRect; + + // use no gradient anymore, just a static titlecolor + if (pData->mnTitleType == BorderWindowTitleType::Tearoff) + rRenderContext.SetFillColor(rStyleSettings.GetFaceGradientColor()); + else if (pData->mnTitleType == BorderWindowTitleType::Popup) + rRenderContext.SetFillColor(aFaceColor); + else + rRenderContext.SetFillColor(aFrameColor); + + rRenderContext.SetTextColor(rStyleSettings.GetButtonTextColor()); + tools::Rectangle aTitleRect(pData->maTitleRect); + if(pOffset) + aTitleRect.Move(pOffset->X(), pOffset->Y()); + rRenderContext.DrawRect(aTitleRect); + + if (pData->mnTitleType != BorderWindowTitleType::Tearoff) + { + aInRect.AdjustLeft(2 ); + aInRect.AdjustRight( -2 ); + + if (!pData->maHelpRect.IsEmpty()) + aInRect.SetRight( pData->maHelpRect.Left() - 2 ); + else if (!pData->maHideRect.IsEmpty()) + aInRect.SetRight( pData->maHideRect.Left() - 2 ); + else if (!pData->maDockRect.IsEmpty()) + aInRect.SetRight( pData->maDockRect.Left() - 2 ); + else if (!pData->maMenuRect.IsEmpty()) + aInRect.SetRight( pData->maMenuRect.Left() - 2 ); + else if (!pData->maCloseRect.IsEmpty()) + aInRect.SetRight( pData->maCloseRect.Left() - 2 ); + + if (pOffset) + aInRect.Move(pOffset->X(), pOffset->Y()); + + DrawTextFlags nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter | DrawTextFlags::EndEllipsis | DrawTextFlags::Clip; + + // must show tooltip ? + TextRectInfo aInfo; + rRenderContext.GetTextRect(aInRect, pBorderWindow->GetText(), nTextStyle, &aInfo); + pData->mbTitleClipped = aInfo.IsEllipses(); + + rRenderContext.DrawText(aInRect, pBorderWindow->GetText(), nTextStyle); + } + else + { + ToolBox::ImplDrawGrip(rRenderContext, aTitleRect, ToolBox::ImplGetDragWidth(rRenderContext, false), + WindowAlign::Left, false); + } + } + + if (!pData->maCloseRect.IsEmpty()) + { + tools::Rectangle aSymbolRect(pData->maCloseRect); + if (pOffset) + aSymbolRect.Move(pOffset->X(), pOffset->Y()); + ImplDrawBrdWinSymbolButton(&rRenderContext, aSymbolRect, SymbolType::CLOSE, pData->mnCloseState); + } + if (!pData->maDockRect.IsEmpty()) + { + tools::Rectangle aSymbolRect(pData->maDockRect); + if (pOffset) + aSymbolRect.Move(pOffset->X(), pOffset->Y()); + ImplDrawBrdWinSymbolButton(&rRenderContext, aSymbolRect, SymbolType::DOCK, pData->mnDockState); + } + if (!pData->maMenuRect.IsEmpty()) + { + tools::Rectangle aSymbolRect(pData->maMenuRect); + if (pOffset) + aSymbolRect.Move(pOffset->X(), pOffset->Y()); + ImplDrawBrdWinSymbolButton(&rRenderContext, aSymbolRect, SymbolType::MENU, pData->mnMenuState); + } + if (!pData->maHideRect.IsEmpty()) + { + tools::Rectangle aSymbolRect(pData->maHideRect); + if (pOffset) + aSymbolRect.Move(pOffset->X(), pOffset->Y()); + ImplDrawBrdWinSymbolButton(&rRenderContext, aSymbolRect, SymbolType::HIDE, pData->mnHideState); + } + + if (!pData->maHelpRect.IsEmpty()) + { + tools::Rectangle aSymbolRect(pData->maHelpRect); + if (pOffset) + aSymbolRect.Move(pOffset->X(), pOffset->Y()); + ImplDrawBrdWinSymbolButton(&rRenderContext, aSymbolRect, SymbolType::HELP, pData->mnHelpState); + } +} + +void ImplBorderWindow::ImplInit( vcl::Window* pParent, + WinBits nStyle, BorderWindowStyle nTypeStyle, + SystemParentData* pSystemParentData + ) +{ + // remove all unwanted WindowBits + WinBits nOrgStyle = nStyle; + WinBits nTestStyle = (WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE | WB_STANDALONE | WB_DIALOGCONTROL | WB_NODIALOGCONTROL | WB_SYSTEMFLOATWIN | WB_INTROWIN | WB_DEFAULTWIN | WB_TOOLTIPWIN | WB_NOSHADOW | WB_OWNERDRAWDECORATION | WB_SYSTEMCHILDWINDOW | WB_POPUP); + if ( nTypeStyle & BorderWindowStyle::App ) + nTestStyle |= WB_APP; + nStyle &= nTestStyle; + + mpWindowImpl->mbBorderWin = true; + mbSmallOutBorder = false; + if ( nTypeStyle & BorderWindowStyle::Frame ) + { + if( nStyle & WB_SYSTEMCHILDWINDOW ) + { + mpWindowImpl->mbOverlapWin = true; + mpWindowImpl->mbFrame = true; + mbFrameBorder = false; + } + else if( nStyle & (WB_OWNERDRAWDECORATION | WB_POPUP) ) + { + mpWindowImpl->mbOverlapWin = true; + mpWindowImpl->mbFrame = true; + mbFrameBorder = (nOrgStyle & WB_NOBORDER) == 0; + } + else + { + mpWindowImpl->mbOverlapWin = true; + mpWindowImpl->mbFrame = true; + mbFrameBorder = false; + // closeable windows may have a border as well, eg. system floating windows without caption + if ( (nOrgStyle & (WB_BORDER | WB_NOBORDER | WB_MOVEABLE | WB_SIZEABLE/* | WB_CLOSEABLE*/)) == WB_BORDER ) + mbSmallOutBorder = true; + } + } + else if ( nTypeStyle & BorderWindowStyle::Overlap ) + { + mpWindowImpl->mbOverlapWin = true; + mbFrameBorder = true; + } + else + mbFrameBorder = false; + + if ( nTypeStyle & BorderWindowStyle::Float ) + mbFloatWindow = true; + else + mbFloatWindow = false; + + Window::ImplInit( pParent, nStyle, pSystemParentData ); + SetBackground(); + SetTextFillColor(); + + mpMenuBarWindow = nullptr; + mnMinWidth = 0; + mnMinHeight = 0; + mnMaxWidth = SHRT_MAX; + mnMaxHeight = SHRT_MAX; + mnOrgMenuHeight = 0; + mbMenuHide = false; + mbDockBtn = false; + mbMenuBtn = false; + mbHideBtn = false; + mbDisplayActive = IsActive(); + + if ( nTypeStyle & BorderWindowStyle::Float ) + mnTitleType = BorderWindowTitleType::Small; + else + mnTitleType = BorderWindowTitleType::Normal; + mnBorderStyle = WindowBorderStyle::NORMAL; + InitView(); +} + +ImplBorderWindow::ImplBorderWindow( vcl::Window* pParent, + SystemParentData* pSystemParentData, + WinBits nStyle, BorderWindowStyle nTypeStyle + ) : Window( WindowType::BORDERWINDOW ) +{ + ImplInit( pParent, nStyle, nTypeStyle, pSystemParentData ); +} + +ImplBorderWindow::ImplBorderWindow( vcl::Window* pParent, WinBits nStyle , + BorderWindowStyle nTypeStyle ) : + Window( WindowType::BORDERWINDOW ) +{ + ImplInit( pParent, nStyle, nTypeStyle, nullptr ); +} + +ImplBorderWindow::~ImplBorderWindow() +{ + disposeOnce(); +} + +void ImplBorderWindow::dispose() +{ + mpBorderView.reset(); + mpMenuBarWindow.clear(); + mpNotebookBar.disposeAndClear(); + vcl::Window::dispose(); +} + +void ImplBorderWindow::MouseMove( const MouseEvent& rMEvt ) +{ + if (mpBorderView) + mpBorderView->MouseMove( rMEvt ); +} + +void ImplBorderWindow::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if (mpBorderView) + mpBorderView->MouseButtonDown( rMEvt ); +} + +void ImplBorderWindow::Tracking( const TrackingEvent& rTEvt ) +{ + if (mpBorderView) + mpBorderView->Tracking( rTEvt ); +} + +void ImplBorderWindow::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& ) +{ + if (mpBorderView) + mpBorderView->DrawWindow(rRenderContext); +} + +void ImplBorderWindow::Draw( OutputDevice* pOutDev, const Point& rPos ) +{ + if (mpBorderView) + mpBorderView->DrawWindow(*pOutDev, &rPos); +} + +void ImplBorderWindow::Activate() +{ + SetDisplayActive( true ); + Window::Activate(); +} + +void ImplBorderWindow::Deactivate() +{ + // remove active windows from the ruler, also ignore the Deactivate + // if a menu becomes active + if (GetActivateMode() != ActivateModeFlags::NONE && !ImplGetSVData()->mpWinData->mbNoDeactivate) + SetDisplayActive( false ); + Window::Deactivate(); +} + +void ImplBorderWindow::RequestHelp( const HelpEvent& rHEvt ) +{ + // no keyboard help for border window + if ( rHEvt.GetMode() & (HelpEventMode::BALLOON | HelpEventMode::QUICK) && !rHEvt.KeyboardActivated() ) + { + Point aMousePosPixel = ScreenToOutputPixel( rHEvt.GetMousePosPixel() ); + tools::Rectangle aHelpRect; + OUString aHelpStr( mpBorderView->RequestHelp( aMousePosPixel, aHelpRect ) ); + + // retrieve rectangle + if ( !aHelpStr.isEmpty() ) + { + aHelpRect.SetPos( OutputToScreenPixel( aHelpRect.TopLeft() ) ); + if ( rHEvt.GetMode() & HelpEventMode::BALLOON ) + Help::ShowBalloon( this, aHelpRect.Center(), aHelpRect, aHelpStr ); + else + Help::ShowQuickHelp( this, aHelpRect, aHelpStr ); + return; + } + } + + Window::RequestHelp( rHEvt ); +} + +void ImplBorderWindow::Resize() +{ + Size aSize = GetOutputSizePixel(); + + vcl::Window* pClientWindow = ImplGetClientWindow(); + + sal_Int32 nLeftBorder; + sal_Int32 nTopBorder; + sal_Int32 nRightBorder; + sal_Int32 nBottomBorder; + mpBorderView->GetBorder( nLeftBorder, nTopBorder, nRightBorder, nBottomBorder ); + + if (mpMenuBarWindow) + { + tools::Long nMenuHeight = mpMenuBarWindow->GetSizePixel().Height(); + if ( mbMenuHide ) + { + if ( nMenuHeight ) + mnOrgMenuHeight = nMenuHeight; + nMenuHeight = 0; + } + else + { + if ( !nMenuHeight ) + nMenuHeight = mnOrgMenuHeight; + } + mpMenuBarWindow->setPosSizePixel( + nLeftBorder, nTopBorder, + aSize.Width()-nLeftBorder-nRightBorder, + nMenuHeight); + + // shift the notebookbar down accordingly + nTopBorder += nMenuHeight; + } + + if (mpNotebookBar) + { + tools::Long nNotebookBarHeight = mpNotebookBar->GetSizePixel().Height(); + + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + const BitmapEx& aPersona = rStyleSettings.GetPersonaHeader(); + // since size of notebookbar changes, to make common persona for menubar + // and notebookbar persona should be set again with changed coordinates + if (!aPersona.IsEmpty()) + { + Wallpaper aWallpaper(aPersona); + aWallpaper.SetStyle(WallpaperStyle::TopRight); + aWallpaper.SetRect(tools::Rectangle(Point(0, -nTopBorder), + Size(aSize.Width() - nLeftBorder - nRightBorder, + nNotebookBarHeight + nTopBorder))); + mpNotebookBar->SetBackground(aWallpaper); + } + + mpNotebookBar->setPosSizePixel( + nLeftBorder, nTopBorder, + aSize.Width() - nLeftBorder - nRightBorder, + nNotebookBarHeight); + } + + GetBorder( pClientWindow->mpWindowImpl->mnLeftBorder, pClientWindow->mpWindowImpl->mnTopBorder, + pClientWindow->mpWindowImpl->mnRightBorder, pClientWindow->mpWindowImpl->mnBottomBorder ); + pClientWindow->ImplPosSizeWindow( pClientWindow->mpWindowImpl->mnLeftBorder, + pClientWindow->mpWindowImpl->mnTopBorder, + aSize.Width()-pClientWindow->mpWindowImpl->mnLeftBorder-pClientWindow->mpWindowImpl->mnRightBorder, + aSize.Height()-pClientWindow->mpWindowImpl->mnTopBorder-pClientWindow->mpWindowImpl->mnBottomBorder, + PosSizeFlags::X | PosSizeFlags::Y | + PosSizeFlags::Width | PosSizeFlags::Height ); + + // UpdateView + mpBorderView->Init( GetOutDev(), aSize.Width(), aSize.Height() ); + InvalidateBorder(); + + Window::Resize(); +} + +void ImplBorderWindow::StateChanged( StateChangedType nType ) +{ + if ( (nType == StateChangedType::Text) || + (nType == StateChangedType::Data) ) + { + if (IsReallyVisible() && mbFrameBorder) + InvalidateBorder(); + } + + Window::StateChanged( nType ); +} + +void ImplBorderWindow::DataChanged( const DataChangedEvent& rDCEvt ) +{ + if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) ) + { + if ( !mpWindowImpl->mbFrame || (GetStyle() & (WB_OWNERDRAWDECORATION | WB_POPUP)) ) + UpdateView( true, ImplGetWindow()->GetOutputSizePixel() ); + } + + Window::DataChanged( rDCEvt ); +} + +void ImplBorderWindow::InitView() +{ + if ( mbSmallOutBorder ) + mpBorderView.reset(new ImplSmallBorderWindowView( this )); + else if ( mpWindowImpl->mbFrame ) + { + if( mbFrameBorder ) + mpBorderView.reset(new ImplStdBorderWindowView( this )); + else + mpBorderView.reset(new ImplNoBorderWindowView); + } + else if ( !mbFrameBorder ) + mpBorderView.reset(new ImplSmallBorderWindowView( this )); + else + mpBorderView.reset(new ImplStdBorderWindowView( this )); + Size aSize = GetOutputSizePixel(); + mpBorderView->Init( GetOutDev(), aSize.Width(), aSize.Height() ); +} + +void ImplBorderWindow::UpdateView( bool bNewView, const Size& rNewOutSize ) +{ + sal_Int32 nLeftBorder; + sal_Int32 nTopBorder; + sal_Int32 nRightBorder; + sal_Int32 nBottomBorder; + Size aOldSize = GetSizePixel(); + Size aOutputSize = rNewOutSize; + + if ( bNewView ) + { + mpBorderView.reset(); + InitView(); + } + else + { + Size aSize = aOutputSize; + mpBorderView->GetBorder( nLeftBorder, nTopBorder, nRightBorder, nBottomBorder ); + aSize.AdjustWidth(nLeftBorder+nRightBorder ); + aSize.AdjustHeight(nTopBorder+nBottomBorder ); + mpBorderView->Init( GetOutDev(), aSize.Width(), aSize.Height() ); + } + + vcl::Window* pClientWindow = ImplGetClientWindow(); + if ( pClientWindow ) + { + GetBorder( pClientWindow->mpWindowImpl->mnLeftBorder, pClientWindow->mpWindowImpl->mnTopBorder, + pClientWindow->mpWindowImpl->mnRightBorder, pClientWindow->mpWindowImpl->mnBottomBorder ); + } + GetBorder( nLeftBorder, nTopBorder, nRightBorder, nBottomBorder ); + if ( aOldSize.Width() || aOldSize.Height() ) + { + aOutputSize.AdjustWidth(nLeftBorder+nRightBorder ); + aOutputSize.AdjustHeight(nTopBorder+nBottomBorder ); + if ( aOutputSize == GetSizePixel() ) + InvalidateBorder(); + else + SetSizePixel( aOutputSize ); + } +} + +void ImplBorderWindow::InvalidateBorder() +{ + if ( !IsReallyVisible() ) + return; + + // invalidate only if we have a border + sal_Int32 nLeftBorder; + sal_Int32 nTopBorder; + sal_Int32 nRightBorder; + sal_Int32 nBottomBorder; + mpBorderView->GetBorder( nLeftBorder, nTopBorder, nRightBorder, nBottomBorder ); + if ( !(nLeftBorder || nTopBorder || nRightBorder || nBottomBorder) ) + return; + + tools::Rectangle aWinRect( Point( 0, 0 ), GetOutputSizePixel() ); + vcl::Region aRegion( aWinRect ); + aWinRect.AdjustLeft(nLeftBorder ); + aWinRect.AdjustTop(nTopBorder ); + aWinRect.AdjustRight( -nRightBorder ); + aWinRect.AdjustBottom( -nBottomBorder ); + // no output area anymore, now invalidate all + if ( (aWinRect.Right() < aWinRect.Left()) || + (aWinRect.Bottom() < aWinRect.Top()) ) + Invalidate( InvalidateFlags::NoChildren ); + else + { + aRegion.Exclude( aWinRect ); + Invalidate( aRegion, InvalidateFlags::NoChildren ); + } +} + +void ImplBorderWindow::SetDisplayActive( bool bActive ) +{ + if ( mbDisplayActive != bActive ) + { + mbDisplayActive = bActive; + if ( mbFrameBorder ) + InvalidateBorder(); + } +} + +void ImplBorderWindow::SetTitleType( BorderWindowTitleType nTitleType, const Size& rSize ) +{ + mnTitleType = nTitleType; + UpdateView( false, rSize ); +} + +void ImplBorderWindow::SetBorderStyle( WindowBorderStyle nStyle ) +{ + if ( !mbFrameBorder && (mnBorderStyle != nStyle) ) + { + mnBorderStyle = nStyle; + UpdateView( false, ImplGetWindow()->GetOutputSizePixel() ); + } +} + +void ImplBorderWindow::SetCloseButton() +{ + SetStyle( GetStyle() | WB_CLOSEABLE ); + Size aSize = GetOutputSizePixel(); + mpBorderView->Init( GetOutDev(), aSize.Width(), aSize.Height() ); + InvalidateBorder(); +} + +void ImplBorderWindow::SetDockButton( bool bDockButton ) +{ + mbDockBtn = bDockButton; + Size aSize = GetOutputSizePixel(); + mpBorderView->Init( GetOutDev(), aSize.Width(), aSize.Height() ); + InvalidateBorder(); +} + +void ImplBorderWindow::SetHideButton( bool bHideButton ) +{ + mbHideBtn = bHideButton; + Size aSize = GetOutputSizePixel(); + mpBorderView->Init( GetOutDev(), aSize.Width(), aSize.Height() ); + InvalidateBorder(); +} + +void ImplBorderWindow::SetMenuButton( bool bMenuButton ) +{ + mbMenuBtn = bMenuButton; + Size aSize = GetOutputSizePixel(); + mpBorderView->Init( GetOutDev(), aSize.Width(), aSize.Height() ); + InvalidateBorder(); +} + +void ImplBorderWindow::UpdateMenuHeight() +{ + Resize(); +} + +void ImplBorderWindow::SetMenuBarWindow( vcl::Window* pWindow ) +{ + mpMenuBarWindow = pWindow; + UpdateMenuHeight(); + if ( pWindow ) + pWindow->Show(); +} + +void ImplBorderWindow::SetMenuBarMode( bool bHide ) +{ + mbMenuHide = bHide; + UpdateMenuHeight(); +} + +void ImplBorderWindow::SetNotebookBar(const OUString& rUIXMLDescription, + const css::uno::Reference<css::frame::XFrame>& rFrame, + const NotebookBarAddonsItem& aNotebookBarAddonsItem) +{ + if (mpNotebookBar) + mpNotebookBar.disposeAndClear(); + mpNotebookBar = VclPtr<NotebookBar>::Create(this, "NotebookBar", rUIXMLDescription, rFrame, + aNotebookBarAddonsItem); + Resize(); +} + +void ImplBorderWindow::CloseNotebookBar() +{ + if (mpNotebookBar) + mpNotebookBar.disposeAndClear(); + mpNotebookBar = nullptr; + Resize(); +} + +void ImplBorderWindow::GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder, + sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const +{ + mpBorderView->GetBorder(rLeftBorder, rTopBorder, rRightBorder, rBottomBorder); + + if (mpMenuBarWindow && !mbMenuHide) + rTopBorder += mpMenuBarWindow->GetSizePixel().Height(); + + if (mpNotebookBar && mpNotebookBar->IsVisible()) + rTopBorder += mpNotebookBar->GetSizePixel().Height(); +} + +tools::Long ImplBorderWindow::CalcTitleWidth() const +{ + return mpBorderView->CalcTitleWidth(); +} + +tools::Rectangle ImplBorderWindow::GetMenuRect() const +{ + return mpBorderView->GetMenuRect(); +} + +Size ImplBorderWindow::GetOptimalSize() const +{ + const vcl::Window* pClientWindow = ImplGetClientWindow(); + if (pClientWindow) + return pClientWindow->GetOptimalSize(); + return Size(mnMinWidth, mnMinHeight); +} + +void ImplBorderWindow::queue_resize(StateChangedType eReason) +{ + //if we are floating, then we don't want to inform our parent that it needs + //to calculate a new layout allocation. Because while we are a child + //of our parent we are not embedded into the parent so it doesn't care + //about us. + if (mbFloatWindow) + return; + vcl::Window::queue_resize(eReason); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/bubblewindow.cxx b/vcl/source/window/bubblewindow.cxx new file mode 100644 index 000000000..1a4a46a3f --- /dev/null +++ b/vcl/source/window/bubblewindow.cxx @@ -0,0 +1,557 @@ +/* -*- 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/ustrbuf.hxx> +#include <vcl/menubarupdateicon.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <unotools/resmgr.hxx> +#include <bubblewindow.hxx> +#include <bitmaps.hlst> + +#define TIP_HEIGHT 15 +#define TIP_WIDTH 7 +#define TIP_RIGHT_OFFSET 18 +#define BUBBLE_BORDER 10 +#define TEXT_MAX_WIDTH 300 +#define TEXT_MAX_HEIGHT 200 + +BubbleWindow::BubbleWindow( vcl::Window* pParent, const OUString& rTitle, + const OUString& rText, const Image& rImage ) + : FloatingWindow( pParent, WB_SYSTEMWINDOW + | WB_OWNERDRAWDECORATION + | WB_NOBORDER + ) + , maBubbleTitle( rTitle ) + , maBubbleText( rText ) + , maBubbleImage( rImage ) + , maMaxTextSize( TEXT_MAX_WIDTH, TEXT_MAX_HEIGHT ) + , mnTipOffset( 0 ) +{ + SetBackground( Wallpaper( GetSettings().GetStyleSettings().GetHelpColor() ) ); +} + +void BubbleWindow::Resize() +{ + FloatingWindow::Resize(); + + Size aSize = GetSizePixel(); + + if ( ( aSize.Height() < 20 ) || ( aSize.Width() < 60 ) ) + return; + + tools::Rectangle aRect( 0, TIP_HEIGHT, aSize.Width(), aSize.Height() - TIP_HEIGHT ); + maRectPoly = tools::Polygon( aRect, 6, 6 ); + vcl::Region aRegion( maRectPoly ); + tools::Long nTipOffset = aSize.Width() - TIP_RIGHT_OFFSET + mnTipOffset; + + Point aPointArr[4]; + aPointArr[0] = Point( nTipOffset, TIP_HEIGHT ); + aPointArr[1] = Point( nTipOffset, 0 ); + aPointArr[2] = Point( nTipOffset + TIP_WIDTH , TIP_HEIGHT ); + aPointArr[3] = Point( nTipOffset, TIP_HEIGHT ); + maTriPoly = tools::Polygon( 4, aPointArr ); + vcl::Region aTriRegion( maTriPoly ); + + aRegion.Union( aTriRegion); + maBounds = aRegion; + + SetWindowRegionPixel( maBounds ); +} + +void BubbleWindow::SetTitleAndText( const OUString& rTitle, + const OUString& rText, + const Image& rImage ) +{ + maBubbleTitle = rTitle; + maBubbleText = rText; + maBubbleImage = rImage; + + Resize(); +} + +void BubbleWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/) +{ + LineInfo aThickLine( LineStyle::Solid, 2 ); + + rRenderContext.DrawPolyLine( maRectPoly, aThickLine ); + rRenderContext.DrawPolyLine( maTriPoly ); + + Color aOldLine = rRenderContext.GetLineColor(); + Size aSize = GetSizePixel(); + tools::Long nTipOffset = aSize.Width() - TIP_RIGHT_OFFSET + mnTipOffset; + + rRenderContext.SetLineColor( GetSettings().GetStyleSettings().GetHelpColor() ); + rRenderContext.DrawLine( Point( nTipOffset+2, TIP_HEIGHT ), + Point( nTipOffset + TIP_WIDTH -1 , TIP_HEIGHT ), + aThickLine ); + rRenderContext.SetLineColor( aOldLine ); + + Size aImgSize = maBubbleImage.GetSizePixel(); + + rRenderContext.DrawImage( Point( BUBBLE_BORDER, BUBBLE_BORDER + TIP_HEIGHT ), maBubbleImage ); + + vcl::Font aOldFont = GetFont(); + vcl::Font aBoldFont = aOldFont; + aBoldFont.SetWeight( WEIGHT_BOLD ); + + SetFont( aBoldFont ); + tools::Rectangle aTitleRect = maTitleRect; + aTitleRect.Move( aImgSize.Width(), 0 ); + rRenderContext.DrawText( aTitleRect, maBubbleTitle, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak ); + + SetFont( aOldFont ); + tools::Rectangle aTextRect = maTextRect; + aTextRect.Move( aImgSize.Width(), 0 ); + rRenderContext.DrawText( aTextRect, maBubbleText, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak ); +} + +void BubbleWindow::MouseButtonDown( const MouseEvent& ) +{ + Show( false ); +} + +void BubbleWindow::Show( bool bVisible ) +{ + if ( !bVisible ) + { + FloatingWindow::Show( bVisible ); + return; + } + + // don't show bubbles without a text + if ( ( maBubbleTitle.isEmpty() ) && ( maBubbleText.isEmpty() ) ) + return; + + Size aWindowSize = GetSizePixel(); + + Size aImgSize = maBubbleImage.GetSizePixel(); + + RecalcTextRects(); + + aWindowSize.setHeight( maTitleRect.GetHeight() * 7 / 4+ maTextRect.GetHeight() + + 3 * BUBBLE_BORDER + TIP_HEIGHT ); + + if ( maTitleRect.GetWidth() > maTextRect.GetWidth() ) + aWindowSize.setWidth( maTitleRect.GetWidth() ); + else + aWindowSize.setWidth( maTextRect.GetWidth() ); + + aWindowSize.setWidth( aWindowSize.Width() + 3 * BUBBLE_BORDER + aImgSize.Width() ); + + if ( aWindowSize.Height() < aImgSize.Height() + TIP_HEIGHT + 2 * BUBBLE_BORDER ) + aWindowSize.setHeight( aImgSize.Height() + TIP_HEIGHT + 2 * BUBBLE_BORDER ); + + Point aPos; + aPos.setX( maTipPos.X() - aWindowSize.Width() + TIP_RIGHT_OFFSET ); + aPos.setY( maTipPos.Y() ); + Point aScreenPos = GetParent()->OutputToAbsoluteScreenPixel( aPos ); + if ( aScreenPos.X() < 0 ) + { + mnTipOffset = aScreenPos.X(); + aPos.AdjustX( -mnTipOffset ); + } + SetPosSizePixel( aPos, aWindowSize ); + + FloatingWindow::Show( bVisible, ShowFlags::NoActivate ); +} + +void BubbleWindow::RecalcTextRects() +{ + Size aTotalSize; + bool bFinished = false; + vcl::Font aOldFont = GetFont(); + vcl::Font aBoldFont = aOldFont; + + aBoldFont.SetWeight( WEIGHT_BOLD ); + + while ( !bFinished ) + { + SetFont( aBoldFont ); + + maTitleRect = GetTextRect( tools::Rectangle( Point( 0, 0 ), maMaxTextSize ), + maBubbleTitle, + DrawTextFlags::MultiLine | DrawTextFlags::WordBreak ); + + SetFont( aOldFont ); + maTextRect = GetTextRect( tools::Rectangle( Point( 0, 0 ), maMaxTextSize ), + maBubbleText, + DrawTextFlags::MultiLine | DrawTextFlags::WordBreak ); + + if ( maTextRect.GetHeight() < 10 ) + maTextRect.setHeight( 10 ); + + aTotalSize.setHeight( maTitleRect.GetHeight() + + aBoldFont.GetFontHeight() * 3 / 4 + + maTextRect.GetHeight() + + 3 * BUBBLE_BORDER + TIP_HEIGHT ); + if ( aTotalSize.Height() > maMaxTextSize.Height() ) + { + maMaxTextSize.setWidth( maMaxTextSize.Width() * 3 / 2 ); + maMaxTextSize.setHeight( maMaxTextSize.Height() * 3 / 2 ); + } + else + bFinished = true; + } + maTitleRect.Move( 2*BUBBLE_BORDER, BUBBLE_BORDER + TIP_HEIGHT ); + maTextRect.Move( 2*BUBBLE_BORDER, BUBBLE_BORDER + TIP_HEIGHT + maTitleRect.GetHeight() + aBoldFont.GetFontHeight() * 3 / 4 ); +} + +MenuBarUpdateIconManager::MenuBarUpdateIconManager() + : maTimeoutTimer("MenuBarUpdateIconManager") + , maWaitIdle("vcl MenuBarUpdateIconManager maWaitIdle") + , mnIconID (0) + , mbShowMenuIcon(false) + , mbShowBubble(false) + , mbBubbleChanged( false ) +{ + maTimeoutTimer.SetTimeout( 10000 ); + maTimeoutTimer.SetInvokeHandler(LINK(this, MenuBarUpdateIconManager, TimeOutHdl)); + + maWaitIdle.SetPriority( TaskPriority::LOWEST ); + maWaitIdle.SetInvokeHandler(LINK(this, MenuBarUpdateIconManager, WaitTimeOutHdl)); + + maApplicationEventHdl = LINK(this, MenuBarUpdateIconManager, ApplicationEventHdl); + Application::AddEventListener( maApplicationEventHdl ); + + maWindowEventHdl = LINK(this, MenuBarUpdateIconManager, WindowEventHdl); +} + +VclPtr<BubbleWindow> MenuBarUpdateIconManager::GetBubbleWindow() +{ + if ( !mpIconSysWin ) + return nullptr; + + tools::Rectangle aIconRect = mpIconMBar->GetMenuBarButtonRectPixel( mnIconID ); + if( aIconRect.IsEmpty() ) + return nullptr; + + auto pBubbleWin = mpBubbleWin; + + if ( !pBubbleWin ) { + pBubbleWin = VclPtr<BubbleWindow>::Create( mpIconSysWin, maBubbleTitle, + maBubbleText, maBubbleImage ); + mbBubbleChanged = false; + } + else if ( mbBubbleChanged ) { + pBubbleWin->SetTitleAndText( maBubbleTitle, maBubbleText, + maBubbleImage ); + mbBubbleChanged = false; + } + + Point aWinPos = aIconRect.BottomCenter(); + + pBubbleWin->SetTipPosPixel( aWinPos ); + + return pBubbleWin; +} + +IMPL_LINK_NOARG(MenuBarUpdateIconManager, TimeOutHdl, Timer *, void) +{ + RemoveBubbleWindow( false ); +} + +IMPL_LINK(MenuBarUpdateIconManager, WindowEventHdl, VclWindowEvent&, rEvent, void) +{ + VclEventId nEventID = rEvent.GetId(); + + if ( VclEventId::ObjectDying == nEventID ) + { + if ( mpIconSysWin == rEvent.GetWindow() ) + { + mpIconSysWin->RemoveEventListener( maWindowEventHdl ); + RemoveBubbleWindow( true ); + } + } + else if ( VclEventId::WindowMenubarAdded == nEventID ) + { + vcl::Window *pWindow = rEvent.GetWindow(); + if ( pWindow ) + { + SystemWindow *pSysWin = pWindow->GetSystemWindow(); + if ( pSysWin ) + { + AddMenuBarIcon( pSysWin, false ); + } + } + } + else if ( VclEventId::WindowMenubarRemoved == nEventID ) + { + MenuBar *pMBar = static_cast<MenuBar*>(rEvent.GetData()); + if ( pMBar && ( pMBar == mpIconMBar ) ) + RemoveBubbleWindow( true ); + } + else if ( ( nEventID == VclEventId::WindowMove ) || + ( nEventID == VclEventId::WindowResize ) ) + { + if ( ( mpIconSysWin == rEvent.GetWindow() ) && + mpBubbleWin && ( mpIconMBar != nullptr ) ) + { + tools::Rectangle aIconRect = mpIconMBar->GetMenuBarButtonRectPixel( mnIconID ); + Point aWinPos = aIconRect.BottomCenter(); + mpBubbleWin->SetTipPosPixel( aWinPos ); + if ( mpBubbleWin->IsVisible() ) + mpBubbleWin->Show(); // This will recalc the screen position of the bubble + } + } +} + +IMPL_LINK(MenuBarUpdateIconManager, ApplicationEventHdl, VclSimpleEvent&, rEvent, void) +{ + switch (rEvent.GetId()) + { + case VclEventId::WindowShow: + case VclEventId::WindowActivate: + case VclEventId::WindowGetFocus: { + + vcl::Window *pWindow = static_cast< VclWindowEvent * >(&rEvent)->GetWindow(); + if ( pWindow && pWindow->IsTopWindow() ) + { + SystemWindow *pSysWin = pWindow->GetSystemWindow(); + MenuBar *pMBar = pSysWin ? pSysWin->GetMenuBar() : nullptr; + if (pMBar) + { + AddMenuBarIcon( pSysWin, true ); + } + } + break; + } + default: break; + } +} + +IMPL_LINK_NOARG(MenuBarUpdateIconManager, UserEventHdl, void*, void) +{ + vcl::Window *pTopWin = Application::GetFirstTopLevelWindow(); + vcl::Window *pActiveWin = Application::GetActiveTopWindow(); + SystemWindow *pActiveSysWin = nullptr; + + vcl::Window *pBubbleWin = nullptr; + if ( mpBubbleWin ) + pBubbleWin = mpBubbleWin; + + if ( pActiveWin && ( pActiveWin != pBubbleWin ) && pActiveWin->IsTopWindow() ) + pActiveSysWin = pActiveWin->GetSystemWindow(); + + if ( pActiveWin == pBubbleWin ) + pActiveSysWin = nullptr; + + while ( !pActiveSysWin && pTopWin ) + { + if ( ( pTopWin != pBubbleWin ) && pTopWin->IsTopWindow() ) + pActiveSysWin = pTopWin->GetSystemWindow(); + if ( !pActiveSysWin ) + pTopWin = Application::GetNextTopLevelWindow( pTopWin ); + } + + if ( pActiveSysWin ) + AddMenuBarIcon( pActiveSysWin, true ); +} + +IMPL_LINK_NOARG(MenuBarUpdateIconManager, ClickHdl, MenuBarButtonCallbackArg&, bool) +{ + maWaitIdle.Stop(); + if ( mpBubbleWin ) + mpBubbleWin->Show( false ); + + maClickHdl.Call(nullptr); + + return false; +} + +IMPL_LINK(MenuBarUpdateIconManager, HighlightHdl, MenuBarButtonCallbackArg&, rData, bool) +{ + if ( rData.bHighlight ) + maWaitIdle.Start(); + else + RemoveBubbleWindow(false); + + return false; +} + +IMPL_LINK_NOARG(MenuBarUpdateIconManager, WaitTimeOutHdl, Timer *, void) +{ + mpBubbleWin = GetBubbleWindow(); + + if ( mpBubbleWin ) + { + mpBubbleWin->Show(); + } +} + +MenuBarUpdateIconManager::~MenuBarUpdateIconManager() +{ + Application::RemoveEventListener( maApplicationEventHdl ); + + RemoveBubbleWindow(true); +} + +void MenuBarUpdateIconManager::SetShowMenuIcon(bool bShowMenuIcon) +{ + if ( bShowMenuIcon != mbShowMenuIcon ) + { + mbShowMenuIcon = bShowMenuIcon; + if ( bShowMenuIcon ) + Application::PostUserEvent(LINK(this, MenuBarUpdateIconManager, UserEventHdl)); + else + RemoveBubbleWindow( true ); + } +} + +void MenuBarUpdateIconManager::SetShowBubble(bool bShowBubble) +{ + mbShowBubble = bShowBubble; + if ( mbShowBubble ) + Application::PostUserEvent(LINK(this, MenuBarUpdateIconManager, UserEventHdl)); + else if ( mpBubbleWin ) + mpBubbleWin->Show( false ); +} + +void MenuBarUpdateIconManager::SetBubbleChanged() +{ + mbBubbleChanged = true; + if (mbBubbleChanged && mpBubbleWin) + mpBubbleWin->Show( false ); +} + +void MenuBarUpdateIconManager::SetBubbleImage(const Image& rImage) +{ + maBubbleImage = rImage; + SetBubbleChanged(); +} + +void MenuBarUpdateIconManager::SetBubbleTitle(const OUString& rTitle) +{ + if (rTitle != maBubbleTitle) + { + maBubbleTitle = rTitle; + SetBubbleChanged(); + } +} + +void MenuBarUpdateIconManager::SetBubbleText(const OUString& rText) +{ + if (rText != maBubbleText) + { + maBubbleText = rText; + SetBubbleChanged(); + } +} + +namespace { +Image GetMenuBarIcon( MenuBar const * pMBar ) +{ + OUString sResID; + vcl::Window *pMBarWin = pMBar->GetWindow(); + sal_uInt32 nMBarHeight = 20; + + if ( pMBarWin ) + nMBarHeight = pMBarWin->GetOutputSizePixel().getHeight(); + + if (nMBarHeight >= 35) + sResID = RID_UPDATE_AVAILABLE_26; + else + sResID = RID_UPDATE_AVAILABLE_16; + + return Image(StockImage::Yes, sResID); +} +} + +void MenuBarUpdateIconManager::AddMenuBarIcon(SystemWindow *pSysWin, bool bAddEventHdl) +{ + if ( ! mbShowMenuIcon ) + return; + + MenuBar *pActiveMBar = pSysWin->GetMenuBar(); + if ( ( pSysWin != mpIconSysWin ) || ( pActiveMBar != mpIconMBar ) ) + { + if ( bAddEventHdl && mpIconSysWin ) + mpIconSysWin->RemoveEventListener( maWindowEventHdl ); + + RemoveBubbleWindow( true ); + + if ( pActiveMBar ) + { + OUStringBuffer aBuf; + if( !maBubbleTitle.isEmpty() ) + aBuf.append( maBubbleTitle ); + if( !maBubbleText.isEmpty() ) + { + if( !maBubbleTitle.isEmpty() ) + aBuf.append( "\n\n" ); + aBuf.append( maBubbleText ); + } + + Image aImage = GetMenuBarIcon( pActiveMBar ); + mnIconID = pActiveMBar->AddMenuBarButton( aImage, + LINK( this, MenuBarUpdateIconManager, ClickHdl ), + aBuf.makeStringAndClear() + ); + pActiveMBar->SetMenuBarButtonHighlightHdl( mnIconID, + LINK( this, MenuBarUpdateIconManager, HighlightHdl ) ); + } + mpIconMBar = pActiveMBar; + mpIconSysWin = pSysWin; + if ( bAddEventHdl && mpIconSysWin ) + mpIconSysWin->AddEventListener( maWindowEventHdl ); + } + + if ( mbShowBubble && pActiveMBar ) + { + mpBubbleWin = GetBubbleWindow(); + if ( mpBubbleWin ) + { + mpBubbleWin->Show(); + maTimeoutTimer.Start(); + } + mbShowBubble = false; + } +} + +void MenuBarUpdateIconManager::RemoveBubbleWindow( bool bRemoveIcon ) +{ + maWaitIdle.Stop(); + maTimeoutTimer.Stop(); + + if ( mpBubbleWin ) + { + mpBubbleWin.disposeAndClear(); + } + + if ( !bRemoveIcon ) + return; + + try { + if ( mpIconMBar && ( mnIconID != 0 ) ) + { + mpIconMBar->RemoveMenuBarButton( mnIconID ); + mpIconMBar = nullptr; + mnIconID = 0; + } + } + catch ( ... ) { + mpIconMBar = nullptr; + mnIconID = 0; + } + + mpIconSysWin = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/bufferdevice.cxx b/vcl/source/window/bufferdevice.cxx new file mode 100644 index 000000000..188fbb1ac --- /dev/null +++ b/vcl/source/window/bufferdevice.cxx @@ -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/. + */ + +#include "bufferdevice.hxx" + +namespace vcl +{ +BufferDevice::BufferDevice(const VclPtr<vcl::Window>& pWindow, vcl::RenderContext& rRenderContext) + : m_pBuffer(VclPtr<VirtualDevice>::Create(rRenderContext)) + , m_pWindow(pWindow) + , m_rRenderContext(rRenderContext) +{ + m_pBuffer->SetOutputSizePixel(pWindow->GetOutputSizePixel(), false); + m_pBuffer->SetTextColor(rRenderContext.GetTextColor()); + m_pBuffer->DrawOutDev(Point(0, 0), pWindow->GetOutputSizePixel(), Point(0, 0), + pWindow->GetOutputSizePixel(), rRenderContext); + m_pBuffer->EnableRTL(rRenderContext.IsRTLEnabled()); +} + +void BufferDevice::Dispose() +{ + if (m_bDisposed) + { + return; + } + + m_rRenderContext.DrawOutDev(Point(0, 0), m_pWindow->GetOutputSizePixel(), Point(0, 0), + m_pWindow->GetOutputSizePixel(), *m_pBuffer); + m_bDisposed = true; +} + +BufferDevice::~BufferDevice() { Dispose(); } + +vcl::RenderContext* BufferDevice::operator->() { return m_pBuffer.get(); } + +vcl::RenderContext& BufferDevice::operator*() { return *m_pBuffer; } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/bufferdevice.hxx b/vcl/source/window/bufferdevice.hxx new file mode 100644 index 000000000..eafc829e5 --- /dev/null +++ b/vcl/source/window/bufferdevice.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/. + */ + +#pragma once + +#include <vcl/virdev.hxx> +#include <vcl/window.hxx> + +namespace vcl +{ +/// Buffers drawing on a vcl::RenderContext using a VirtualDevice. +class VCL_DLLPUBLIC BufferDevice +{ + ScopedVclPtr<VirtualDevice> m_pBuffer; + VclPtr<vcl::Window> m_pWindow; + vcl::RenderContext& m_rRenderContext; + bool m_bDisposed = false; + +public: + BufferDevice(const VclPtr<vcl::Window>& pWindow, vcl::RenderContext& rRenderContext); + ~BufferDevice(); + void Dispose(); + + vcl::RenderContext* operator->(); + vcl::RenderContext& operator*(); +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/builder.cxx b/vcl/source/window/builder.cxx new file mode 100644 index 000000000..d25426ced --- /dev/null +++ b/vcl/source/window/builder.cxx @@ -0,0 +1,4402 @@ +/* -*- 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_feature_desktop.h> +#include <config_options.h> + +#include <memory> +#include <string_view> +#include <unordered_map> +#include <com/sun/star/accessibility/AccessibleRole.hpp> + +#include <comphelper/lok.hxx> +#include <i18nutil/unicode.hxx> +#include <jsdialog/enabled.hxx> +#include <o3tl/string_view.hxx> +#include <officecfg/Office/Common.hxx> +#include <osl/module.hxx> +#include <sal/log.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/resmgr.hxx> +#include <vcl/builder.hxx> +#include <vcl/dialoghelper.hxx> +#include <vcl/menu.hxx> +#include <vcl/toolkit/button.hxx> +#include <vcl/toolkit/dialog.hxx> +#include <vcl/toolkit/edit.hxx> +#include <vcl/toolkit/field.hxx> +#include <vcl/fieldvalues.hxx> +#include <vcl/toolkit/fmtfield.hxx> +#include <vcl/toolkit/fixed.hxx> +#include <vcl/toolkit/fixedhyper.hxx> +#include <vcl/headbar.hxx> +#include <vcl/notebookbar/NotebookBarAddonsMerger.hxx> +#include <vcl/toolkit/ivctrl.hxx> +#include <vcl/layout.hxx> +#include <vcl/toolkit/lstbox.hxx> +#include <vcl/toolkit/menubtn.hxx> +#include <vcl/mnemonic.hxx> +#include <vcl/toolkit/prgsbar.hxx> +#include <vcl/scrbar.hxx> +#include <vcl/split.hxx> +#include <vcl/svapp.hxx> +#include <vcl/toolkit/svtabbx.hxx> +#include <vcl/tabctrl.hxx> +#include <vcl/tabpage.hxx> +#include <vcl/toolkit/throbber.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/toolkit/treelistentry.hxx> +#include <vcl/toolkit/vclmedit.hxx> +#include <vcl/settings.hxx> +#include <slider.hxx> +#include <vcl/weld.hxx> +#include <vcl/weldutils.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <iconview.hxx> +#include <svdata.hxx> +#include <bitmaps.hlst> +#include <managedmenubutton.hxx> +#include <messagedialog.hxx> +#include <ContextVBox.hxx> +#include <DropdownBox.hxx> +#include <IPrioritable.hxx> +#include <OptionalBox.hxx> +#include <PriorityMergedHBox.hxx> +#include <PriorityHBox.hxx> +#include <window.h> +#include <xmlreader/xmlreader.hxx> +#include <desktop/crashreport.hxx> +#include <calendar.hxx> +#include <menutogglebutton.hxx> +#include <salinst.hxx> +#include <strings.hrc> +#include <treeglue.hxx> +#include <tools/diagnose_ex.h> +#include <verticaltabctrl.hxx> +#include <wizdlg.hxx> +#include <tools/svlibrary.h> +#include <jsdialog/jsdialogbuilder.hxx> + +#if defined(DISABLE_DYNLOADING) || defined(LINUX) +#include <dlfcn.h> +#endif + +static bool toBool(std::string_view rValue) +{ + return (!rValue.empty() && (rValue[0] == 't' || rValue[0] == 'T' || rValue[0] == '1')); +} + +namespace +{ + OUString mapStockToImageResource(std::u16string_view sType) + { + if (sType == u"view-refresh") + return SV_RESID_BITMAP_REFRESH; + else if (sType == u"dialog-error") + return IMG_ERROR; + else if (sType == u"list-add") + return IMG_ADD; + else if (sType == u"list-remove") + return IMG_REMOVE; + else if (sType == u"edit-copy") + return IMG_COPY; + else if (sType == u"edit-paste") + return IMG_PASTE; + else if (sType == u"document-open") + return IMG_OPEN; + else if (sType == u"open-menu-symbolic") + return IMG_MENU; + else if (sType == u"window-close-symbolic") + return SV_RESID_BITMAP_CLOSEDOC; + else if (sType == u"x-office-calendar") + return IMG_CALENDAR; + return OUString(); + } + +} + +SymbolType VclBuilder::mapStockToSymbol(std::u16string_view sType) +{ + SymbolType eRet = SymbolType::DONTKNOW; + if (sType == u"media-skip-forward") + eRet = SymbolType::NEXT; + else if (sType == u"media-skip-backward") + eRet = SymbolType::PREV; + else if (sType == u"media-playback-start") + eRet = SymbolType::PLAY; + else if (sType == u"media-playback-stop") + eRet = SymbolType::STOP; + else if (sType == u"go-first") + eRet = SymbolType::FIRST; + else if (sType == u"go-last") + eRet = SymbolType::LAST; + else if (sType == u"go-previous") + eRet = SymbolType::ARROW_LEFT; + else if (sType == u"go-next") + eRet = SymbolType::ARROW_RIGHT; + else if (sType == u"go-up") + eRet = SymbolType::ARROW_UP; + else if (sType == u"go-down") + eRet = SymbolType::ARROW_DOWN; + else if (sType == u"missing-image") + eRet = SymbolType::IMAGE; + else if (sType == u"help-browser" || sType == u"help-browser-symbolic") + eRet = SymbolType::HELP; + else if (sType == u"window-close") + eRet = SymbolType::CLOSE; + else if (sType == u"document-new") + eRet = SymbolType::PLUS; + else if (sType == u"pan-down-symbolic") + eRet = SymbolType::SPIN_DOWN; + else if (sType == u"pan-up-symbolic") + eRet = SymbolType::SPIN_UP; + else if (!mapStockToImageResource(sType).isEmpty()) + eRet = SymbolType::IMAGE; + return eRet; +} + +namespace +{ + void setupFromActionName(Button *pButton, VclBuilder::stringmap &rMap, const css::uno::Reference<css::frame::XFrame>& rFrame); + +#if defined SAL_LOG_WARN + bool isButtonType(WindowType nType) + { + return nType == WindowType::PUSHBUTTON || + nType == WindowType::OKBUTTON || + nType == WindowType::CANCELBUTTON || + nType == WindowType::HELPBUTTON || + nType == WindowType::IMAGEBUTTON || + nType == WindowType::MENUBUTTON || + nType == WindowType::MOREBUTTON || + nType == WindowType::SPINBUTTON; + } +#endif + +} + +std::unique_ptr<weld::Builder> Application::CreateBuilder(weld::Widget* pParent, const OUString &rUIFile, bool bMobile, sal_uInt64 nLOKWindowId) +{ + if (comphelper::LibreOfficeKit::isActive()) + { + if (jsdialog::isBuilderEnabledForSidebar(rUIFile)) + return JSInstanceBuilder::CreateSidebarBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile, nLOKWindowId); + else if (jsdialog::isBuilderEnabledForPopup(rUIFile)) + return JSInstanceBuilder::CreatePopupBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile); + else if (jsdialog::isBuilderEnabled(rUIFile, bMobile)) + return JSInstanceBuilder::CreateDialogBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile); + } + + return ImplGetSVData()->mpDefInst->CreateBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile); +} + +std::unique_ptr<weld::Builder> Application::CreateInterimBuilder(vcl::Window* pParent, const OUString &rUIFile, bool bAllowCycleFocusOut, sal_uInt64 nLOKWindowId) +{ + if (comphelper::LibreOfficeKit::isActive()) + { + // Notebookbar sub controls + if (jsdialog::isInterimBuilderEnabledForNotebookbar(rUIFile)) + return JSInstanceBuilder::CreateNotebookbarBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile, css::uno::Reference<css::frame::XFrame>(), nLOKWindowId); + else if (rUIFile == u"modules/scalc/ui/inputbar.ui") + return JSInstanceBuilder::CreateFormulabarBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile, nLOKWindowId); + } + + return ImplGetSVData()->mpDefInst->CreateInterimBuilder(pParent, AllSettings::GetUIRootDir(), rUIFile, bAllowCycleFocusOut, nLOKWindowId); +} + +weld::MessageDialog* Application::CreateMessageDialog(weld::Widget* pParent, VclMessageType eMessageType, + VclButtonsType eButtonType, const OUString& rPrimaryMessage, + bool /*bMobile*/) +{ + if (comphelper::LibreOfficeKit::isActive()) + return JSInstanceBuilder::CreateMessageDialog(pParent, eMessageType, eButtonType, rPrimaryMessage); + else + return ImplGetSVData()->mpDefInst->CreateMessageDialog(pParent, eMessageType, eButtonType, rPrimaryMessage); +} + +weld::Window* Application::GetFrameWeld(const css::uno::Reference<css::awt::XWindow>& rWindow) +{ + return ImplGetSVData()->mpDefInst->GetFrameWeld(rWindow); +} + +namespace weld +{ + OUString MetricSpinButton::MetricToString(FieldUnit rUnit) + { + const FieldUnitStringList& rList = ImplGetFieldUnits(); + // return unit's default string (ie, the first one ) + auto it = std::find_if( + rList.begin(), rList.end(), + [&rUnit](const std::pair<OUString, FieldUnit>& rItem) { return rItem.second == rUnit; }); + if (it != rList.end()) + return it->first; + + return OUString(); + } + + IMPL_LINK_NOARG(MetricSpinButton, spin_button_value_changed, SpinButton&, void) + { + signal_value_changed(); + } + + IMPL_LINK(MetricSpinButton, spin_button_output, SpinButton&, rSpinButton, void) + { + OUString sNewText(format_number(rSpinButton.get_value())); + if (sNewText != rSpinButton.get_text()) + rSpinButton.set_text(sNewText); + } + + void MetricSpinButton::update_width_chars() + { + sal_Int64 min, max; + m_xSpinButton->get_range(min, max); + auto width = std::max(m_xSpinButton->get_pixel_size(format_number(min)).Width(), + m_xSpinButton->get_pixel_size(format_number(max)).Width()); + int chars = ceil(width / m_xSpinButton->get_approximate_digit_width()); + m_xSpinButton->set_width_chars(chars); + } + + unsigned int SpinButton::Power10(unsigned int n) + { + unsigned int nValue = 1; + for (unsigned int i = 0; i < n; ++i) + nValue *= 10; + return nValue; + } + + sal_Int64 SpinButton::denormalize(sal_Int64 nValue) const + { + const int nFactor = Power10(get_digits()); + + if ((nValue < (std::numeric_limits<sal_Int64>::min() + nFactor)) || + (nValue > (std::numeric_limits<sal_Int64>::max() - nFactor))) + { + return nValue / nFactor; + } + + const int nHalf = nFactor / 2; + + if (nValue < 0) + return (nValue - nHalf) / nFactor; + return (nValue + nHalf) / nFactor; + } + + OUString MetricSpinButton::format_number(sal_Int64 nValue) const + { + OUString aStr; + + const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper(); + + unsigned int nDecimalDigits = m_xSpinButton->get_digits(); + //pawn percent off to icu to decide whether percent is separated from its number for this locale + if (m_eSrcUnit == FieldUnit::PERCENT) + { + double fValue = nValue; + fValue /= SpinButton::Power10(nDecimalDigits); + aStr = unicode::formatPercent(fValue, rLocaleData.getLanguageTag()); + } + else + { + aStr = rLocaleData.getNum(nValue, nDecimalDigits, true, true); + OUString aSuffix = MetricToString(m_eSrcUnit); + if (m_eSrcUnit != FieldUnit::NONE && m_eSrcUnit != FieldUnit::DEGREE && m_eSrcUnit != FieldUnit::INCH && m_eSrcUnit != FieldUnit::FOOT) + aStr += " "; + if (m_eSrcUnit == FieldUnit::INCH) + { + OUString sDoublePrime = u"\u2033"; + if (aSuffix != "\"" && aSuffix != sDoublePrime) + aStr += " "; + else + aSuffix = sDoublePrime; + } + else if (m_eSrcUnit == FieldUnit::FOOT) + { + OUString sPrime = u"\u2032"; + if (aSuffix != "'" && aSuffix != sPrime) + aStr += " "; + else + aSuffix = sPrime; + } + + assert(m_eSrcUnit != FieldUnit::PERCENT); + aStr += aSuffix; + } + + return aStr; + } + + void MetricSpinButton::set_digits(unsigned int digits) + { + int step, page; + get_increments(step, page, m_eSrcUnit); + sal_Int64 value = get_value(m_eSrcUnit); + m_xSpinButton->set_digits(digits); + set_increments(step, page, m_eSrcUnit); + set_value(value, m_eSrcUnit); + update_width_chars(); + } + + void MetricSpinButton::set_unit(FieldUnit eUnit) + { + if (eUnit != m_eSrcUnit) + { + int step, page; + get_increments(step, page, m_eSrcUnit); + sal_Int64 value = get_value(m_eSrcUnit); + m_eSrcUnit = eUnit; + set_increments(step, page, m_eSrcUnit); + set_value(value, m_eSrcUnit); + spin_button_output(*m_xSpinButton); + update_width_chars(); + } + } + + sal_Int64 MetricSpinButton::ConvertValue(sal_Int64 nValue, FieldUnit eInUnit, FieldUnit eOutUnit) const + { + return vcl::ConvertValue(nValue, 0, m_xSpinButton->get_digits(), eInUnit, eOutUnit); + } + + IMPL_LINK(MetricSpinButton, spin_button_input, int*, result, bool) + { + const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper(); + double fResult(0.0); + bool bRet = vcl::TextToValue(get_text(), fResult, 0, m_xSpinButton->get_digits(), rLocaleData, m_eSrcUnit); + if (bRet) + { + if (fResult > SAL_MAX_INT32) + fResult = SAL_MAX_INT32; + else if (fResult < SAL_MIN_INT32) + fResult = SAL_MIN_INT32; + *result = fResult; + } + return bRet; + } + + EntryTreeView::EntryTreeView(std::unique_ptr<Entry> xEntry, std::unique_ptr<TreeView> xTreeView) + : m_xEntry(std::move(xEntry)) + , m_xTreeView(std::move(xTreeView)) + { + m_xTreeView->connect_changed(LINK(this, EntryTreeView, ClickHdl)); + m_xEntry->connect_changed(LINK(this, EntryTreeView, ModifyHdl)); + } + + IMPL_LINK(EntryTreeView, ClickHdl, weld::TreeView&, rView, void) + { + m_xEntry->set_text(rView.get_selected_text()); + m_aChangeHdl.Call(*this); + } + + IMPL_LINK_NOARG(EntryTreeView, ModifyHdl, weld::Entry&, void) + { + m_aChangeHdl.Call(*this); + } + + void EntryTreeView::set_height_request_by_rows(int nRows) + { + int nHeight = nRows == -1 ? -1 : m_xTreeView->get_height_rows(nRows); + m_xTreeView->set_size_request(m_xTreeView->get_size_request().Width(), nHeight); + } + + size_t GetAbsPos(const weld::TreeView& rTreeView, const weld::TreeIter& rIter) + { + size_t nAbsPos = 0; + + std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator(&rIter)); + if (!rTreeView.get_iter_first(*xEntry)) + xEntry.reset(); + + while (xEntry && rTreeView.iter_compare(*xEntry, rIter) != 0) + { + if (!rTreeView.iter_next(*xEntry)) + xEntry.reset(); + nAbsPos++; + } + + return nAbsPos; + } + + bool IsEntryVisible(const weld::TreeView& rTreeView, const weld::TreeIter& rIter) + { + // short circuit for the common case + if (rTreeView.get_iter_depth(rIter) == 0) + return true; + + std::unique_ptr<weld::TreeIter> xEntry(rTreeView.make_iterator(&rIter)); + bool bRetVal = false; + do + { + if (rTreeView.get_iter_depth(*xEntry) == 0) + { + bRetVal = true; + break; + } + } while (rTreeView.iter_parent(*xEntry) && rTreeView.get_row_expanded(*xEntry)); + return bRetVal; + } +} + +VclBuilder::VclBuilder(vcl::Window* pParent, const OUString& sUIDir, const OUString& sUIFile, + const OString& sID, const css::uno::Reference<css::frame::XFrame>& rFrame, + bool bLegacy, const NotebookBarAddonsItem* pNotebookBarAddonsItem) + : m_pNotebookBarAddonsItem(pNotebookBarAddonsItem + ? new NotebookBarAddonsItem(*pNotebookBarAddonsItem) + : new NotebookBarAddonsItem{}) + , m_sID(sID) + , m_sHelpRoot(OUStringToOString(sUIFile, RTL_TEXTENCODING_UTF8)) + , m_pStringReplace(Translate::GetReadStringHook()) + , m_pParent(pParent) + , m_bToplevelParentFound(false) + , m_bLegacy(bLegacy) + , m_pParserState(new ParserState) + , m_xFrame(rFrame) +{ + m_bToplevelHasDeferredInit = pParent && + ((pParent->IsSystemWindow() && static_cast<SystemWindow*>(pParent)->isDeferredInit()) || + (pParent->IsDockingWindow() && static_cast<DockingWindow*>(pParent)->isDeferredInit())); + m_bToplevelHasDeferredProperties = m_bToplevelHasDeferredInit; + + sal_Int32 nIdx = m_sHelpRoot.lastIndexOf('.'); + if (nIdx != -1) + m_sHelpRoot = m_sHelpRoot.copy(0, nIdx); + m_sHelpRoot += "/"; + + OUString sUri = sUIDir + sUIFile; + + try + { + xmlreader::XmlReader reader(sUri); + + handleChild(pParent, nullptr, reader); + } + catch (const css::uno::Exception &rExcept) + { + DBG_UNHANDLED_EXCEPTION("vcl.builder", "Unable to read .ui file"); + CrashReporter::addKeyValue("VclBuilderException", "Unable to read .ui file: " + rExcept.Message, CrashReporter::Write); + throw; + } + + //Set Mnemonic widgets when everything has been imported + for (auto const& mnemonicWidget : m_pParserState->m_aMnemonicWidgetMaps) + { + FixedText *pOne = get<FixedText>(mnemonicWidget.m_sID); + vcl::Window *pOther = get(mnemonicWidget.m_sValue.toUtf8()); + SAL_WARN_IF(!pOne || !pOther, "vcl", "missing either source " << mnemonicWidget.m_sID + << " or target " << mnemonicWidget.m_sValue << " member of Mnemonic Widget Mapping"); + if (pOne && pOther) + pOne->set_mnemonic_widget(pOther); + } + + //Set a11y relations and role when everything has been imported + for (auto const& elemAtk : m_pParserState->m_aAtkInfo) + { + vcl::Window *pSource = elemAtk.first; + const stringmap &rMap = elemAtk.second; + + for (auto const& elemMap : rMap) + { + const OString &rType = elemMap.first; + const OUString &rParam = elemMap.second; + if (rType == "role") + { + sal_Int16 role = BuilderUtils::getRoleFromName(rParam.toUtf8()); + if (role != com::sun::star::accessibility::AccessibleRole::UNKNOWN) + pSource->SetAccessibleRole(role); + } + else + { + vcl::Window *pTarget = get(rParam.toUtf8()); + SAL_WARN_IF(!pTarget, "vcl", "missing parameter of a11y relation: " << rParam); + if (!pTarget) + continue; + if (rType == "labelled-by") + pSource->SetAccessibleRelationLabeledBy(pTarget); + else if (rType == "label-for") + pSource->SetAccessibleRelationLabelFor(pTarget); + else + { + SAL_WARN("vcl.builder", "unhandled a11y relation :" << rType); + } + } + } + } + + //Set radiobutton groups when everything has been imported + for (auto const& elem : m_pParserState->m_aGroupMaps) + { + RadioButton *pOne = get<RadioButton>(elem.m_sID); + RadioButton *pOther = get<RadioButton>(elem.m_sValue); + SAL_WARN_IF(!pOne || !pOther, "vcl", "missing member of radiobutton group"); + if (pOne && pOther) + { + if (m_bLegacy) + pOne->group(*pOther); + else + { + pOther->group(*pOne); + std::stable_sort(pOther->m_xGroup->begin(), pOther->m_xGroup->end(), sortIntoBestTabTraversalOrder(this)); + } + } + } + +#ifndef NDEBUG + o3tl::sorted_vector<OUString> models; +#endif + //Set ComboBox models when everything has been imported + for (auto const& elem : m_pParserState->m_aModelMaps) + { + assert(models.insert(elem.m_sValue).second && "a liststore or treestore is used in duplicate widgets"); + vcl::Window* pTarget = get(elem.m_sID); + ListBox *pListBoxTarget = dynamic_cast<ListBox*>(pTarget); + ComboBox *pComboBoxTarget = dynamic_cast<ComboBox*>(pTarget); + SvTabListBox *pTreeBoxTarget = dynamic_cast<SvTabListBox*>(pTarget); + // pStore may be empty + const ListStore *pStore = get_model_by_name(elem.m_sValue.toUtf8()); + SAL_WARN_IF(!pListBoxTarget && !pComboBoxTarget && !pTreeBoxTarget && !dynamic_cast<IconView*>(pTarget), "vcl", "missing elements of combobox"); + if (pListBoxTarget && pStore) + mungeModel(*pListBoxTarget, *pStore, elem.m_nActiveId); + else if (pComboBoxTarget && pStore) + mungeModel(*pComboBoxTarget, *pStore, elem.m_nActiveId); + else if (pTreeBoxTarget && pStore) + mungeModel(*pTreeBoxTarget, *pStore, elem.m_nActiveId); + } + + //Set TextView buffers when everything has been imported + for (auto const& elem : m_pParserState->m_aTextBufferMaps) + { + VclMultiLineEdit *pTarget = get<VclMultiLineEdit>(elem.m_sID); + const TextBuffer *pBuffer = get_buffer_by_name(elem.m_sValue.toUtf8()); + SAL_WARN_IF(!pTarget || !pBuffer, "vcl", "missing elements of textview/textbuffer"); + if (pTarget && pBuffer) + mungeTextBuffer(*pTarget, *pBuffer); + } + + //Set SpinButton adjustments when everything has been imported + for (auto const& elem : m_pParserState->m_aNumericFormatterAdjustmentMaps) + { + NumericFormatter *pTarget = dynamic_cast<NumericFormatter*>(get(elem.m_sID)); + const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8()); + SAL_WARN_IF(!pTarget, "vcl", "missing NumericFormatter element of spinbutton/adjustment"); + SAL_WARN_IF(!pAdjustment, "vcl", "missing Adjustment element of spinbutton/adjustment"); + if (pTarget && pAdjustment) + mungeAdjustment(*pTarget, *pAdjustment); + } + + for (auto const& elem : m_pParserState->m_aFormattedFormatterAdjustmentMaps) + { + FormattedField *pTarget = dynamic_cast<FormattedField*>(get(elem.m_sID)); + const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8()); + SAL_WARN_IF(!pTarget, "vcl", "missing FormattedField element of spinbutton/adjustment"); + SAL_WARN_IF(!pAdjustment, "vcl", "missing Adjustment element of spinbutton/adjustment"); + if (pTarget && pAdjustment) + mungeAdjustment(*pTarget, *pAdjustment); + } + + //Set ScrollBar adjustments when everything has been imported + for (auto const& elem : m_pParserState->m_aScrollAdjustmentMaps) + { + ScrollBar *pTarget = get<ScrollBar>(elem.m_sID); + const Adjustment *pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8()); + SAL_WARN_IF(!pTarget || !pAdjustment, "vcl", "missing elements of scrollbar/adjustment"); + if (pTarget && pAdjustment) + mungeAdjustment(*pTarget, *pAdjustment); + } + + //Set Scale(Slider) adjustments + for (auto const& elem : m_pParserState->m_aSliderAdjustmentMaps) + { + Slider* pTarget = dynamic_cast<Slider*>(get(elem.m_sID)); + const Adjustment* pAdjustment = get_adjustment_by_name(elem.m_sValue.toUtf8()); + SAL_WARN_IF(!pTarget || !pAdjustment, "vcl", "missing elements of scale(slider)/adjustment"); + if (pTarget && pAdjustment) + { + mungeAdjustment(*pTarget, *pAdjustment); + } + } + + //Set size-groups when all widgets have been imported + for (auto const& sizeGroup : m_pParserState->m_aSizeGroups) + { + std::shared_ptr<VclSizeGroup> xGroup(std::make_shared<VclSizeGroup>()); + + for (auto const& elem : sizeGroup.m_aProperties) + { + const OString &rKey = elem.first; + const OUString &rValue = elem.second; + xGroup->set_property(rKey, rValue); + } + + for (auto const& elem : sizeGroup.m_aWidgets) + { + vcl::Window* pWindow = get(elem.getStr()); + pWindow->add_to_size_group(xGroup); + } + } + + //Set button images when everything has been imported + std::set<OUString> aImagesToBeRemoved; + for (auto const& elem : m_pParserState->m_aButtonImageWidgetMaps) + { + PushButton *pTargetButton = nullptr; + RadioButton *pTargetRadio = nullptr; + Button *pTarget = nullptr; + + if (!elem.m_bRadio) + { + pTargetButton = get<PushButton>(elem.m_sID); + pTarget = pTargetButton; + } + else + { + pTargetRadio = get<RadioButton>(elem.m_sID); + pTarget = pTargetRadio; + } + + FixedImage *pImage = get<FixedImage>(elem.m_sValue.toUtf8()); + SAL_WARN_IF(!pTarget || !pImage, + "vcl", "missing elements of button/image/stock"); + if (!pTarget || !pImage) + continue; + aImagesToBeRemoved.insert(elem.m_sValue); + + if (!elem.m_bRadio) + { + const Image& rImage = pImage->GetImage(); + SymbolType eSymbol = mapStockToSymbol(rImage.GetStock()); + if (eSymbol != SymbolType::IMAGE && eSymbol != SymbolType::DONTKNOW) + { + pTargetButton->SetSymbol(eSymbol); + //fdo#76457 keep symbol images small e.g. tools->customize->menu + //but images the right size. Really the PushButton::CalcMinimumSize + //and PushButton::ImplDrawPushButton are the better place to handle + //this, but its such a train-wreck + pTargetButton->SetStyle(pTargetButton->GetStyle() | WB_SMALLSTYLE); + } + else + { + pTargetButton->SetModeImage(rImage); + if (pImage->GetStyle() & WB_SMALLSTYLE) + { + Size aSz(rImage.GetSizePixel()); + aSz.AdjustWidth(6); + aSz.AdjustHeight(6); + if (pTargetButton->get_width_request() == -1) + pTargetButton->set_width_request(aSz.Width()); + if (pTargetButton->get_height_request() == -1) + pTargetButton->set_height_request(aSz.Height()); + } + } + } + else + pTargetRadio->SetModeRadioImage(pImage->GetImage()); + + auto aFind = m_pParserState->m_aImageSizeMap.find(elem.m_sValue.toUtf8()); + if (aFind != m_pParserState->m_aImageSizeMap.end()) + { + switch (aFind->second) + { + case 1: + pTarget->SetSmallSymbol(); + break; + case 2: + assert(pImage->GetStyle() & WB_SMALLSTYLE); + pTarget->SetStyle(pTarget->GetStyle() | WB_SMALLSTYLE); + break; + case 3: + pTarget->SetStyle(pTarget->GetStyle() | WB_SMALLSTYLE); + // large toolbar, make bigger than normal (4) + pTarget->set_width_request(pTarget->GetOptimalSize().Width() * 1.5); + pTarget->set_height_request(pTarget->GetOptimalSize().Height() * 1.5); + break; + case 4: + break; + default: + SAL_WARN("vcl.builder", "unsupported image size " << aFind->second); + break; + } + m_pParserState->m_aImageSizeMap.erase(aFind); + } + } + + //There may be duplicate use of an Image, so we used a set to collect and + //now we can remove them from the tree after their final munge + for (auto const& elem : aImagesToBeRemoved) + { + delete_by_name(elem.toUtf8()); + } + + //Set button menus when everything has been imported + for (auto const& elem : m_pParserState->m_aButtonMenuMaps) + { + MenuButton *pTarget = get<MenuButton>(elem.m_sID); + PopupMenu *pMenu = get_menu(elem.m_sValue.toUtf8()); + SAL_WARN_IF(!pTarget || !pMenu, + "vcl", "missing elements of button/menu"); + if (!pTarget || !pMenu) + continue; + pTarget->SetPopupMenu(pMenu); + } + + //Remove ScrollWindow parent widgets whose children in vcl implement scrolling + //internally. + for (auto const& elem : m_pParserState->m_aRedundantParentWidgets) + { + delete_by_window(elem.first); + } + + //fdo#67378 merge the label into the disclosure button + for (auto const& elem : m_pParserState->m_aExpanderWidgets) + { + vcl::Window *pChild = elem->get_child(); + vcl::Window* pLabel = elem->GetWindow(GetWindowType::LastChild); + if (pLabel && pLabel != pChild && pLabel->GetType() == WindowType::FIXEDTEXT) + { + FixedText *pLabelWidget = static_cast<FixedText*>(pLabel); + elem->set_label(pLabelWidget->GetText()); + if (pLabelWidget->IsControlFont()) + elem->get_label_widget()->SetControlFont(pLabelWidget->GetControlFont()); + delete_by_window(pLabel); + } + } + + // create message dialog message area now + for (auto const& elem : m_pParserState->m_aMessageDialogs) + elem->create_message_area(); + + //drop maps, etc. that we don't need again + m_pParserState.reset(); + + SAL_WARN_IF(!m_sID.isEmpty() && (!m_bToplevelParentFound && !get_by_name(m_sID)), "vcl.builder", + "Requested top level widget \"" << m_sID << "\" not found in " << sUIFile); + +#if defined SAL_LOG_WARN + if (m_bToplevelParentFound && m_pParent->IsDialog()) + { + int nButtons = 0; + bool bHasDefButton = false; + for (auto const& child : m_aChildren) + { + if (isButtonType(child.m_pWindow->GetType())) + { + ++nButtons; + if (child.m_pWindow->GetStyle() & WB_DEFBUTTON) + { + bHasDefButton = true; + break; + } + } + } + SAL_WARN_IF(nButtons && !bHasDefButton, "vcl.builder", "No default button defined in " << sUIFile); + } +#endif + + const bool bHideHelp = comphelper::LibreOfficeKit::isActive() && + officecfg::Office::Common::Help::HelpRootURL::get().isEmpty(); + if (bHideHelp) + { + if (vcl::Window *pHelpButton = get("help")) + pHelpButton->Hide(); + } +} + +VclBuilder::~VclBuilder() +{ + disposeBuilder(); +} + +void VclBuilder::disposeBuilder() +{ + for (std::vector<WinAndId>::reverse_iterator aI = m_aChildren.rbegin(), + aEnd = m_aChildren.rend(); aI != aEnd; ++aI) + { + aI->m_pWindow.disposeAndClear(); + } + m_aChildren.clear(); + + for (std::vector<MenuAndId>::reverse_iterator aI = m_aMenus.rbegin(), + aEnd = m_aMenus.rend(); aI != aEnd; ++aI) + { + aI->m_pMenu.disposeAndClear(); + } + m_aMenus.clear(); + m_pParent.clear(); +} + +namespace +{ + bool extractHasFrame(VclBuilder::stringmap& rMap) + { + bool bHasFrame = true; + VclBuilder::stringmap::iterator aFind = rMap.find("has-frame"); + if (aFind != rMap.end()) + { + bHasFrame = toBool(aFind->second); + rMap.erase(aFind); + } + return bHasFrame; + } + + bool extractDrawValue(VclBuilder::stringmap& rMap) + { + bool bDrawValue = true; + VclBuilder::stringmap::iterator aFind = rMap.find("draw-value"); + if (aFind != rMap.end()) + { + bDrawValue = toBool(aFind->second); + rMap.erase(aFind); + } + return bDrawValue; + } + + OUString extractPopupMenu(VclBuilder::stringmap& rMap) + { + OUString sRet; + VclBuilder::stringmap::iterator aFind = rMap.find("popup"); + if (aFind != rMap.end()) + { + sRet = aFind->second; + rMap.erase(aFind); + } + return sRet; + } + + OUString extractWidgetName(VclBuilder::stringmap& rMap) + { + OUString sRet; + VclBuilder::stringmap::iterator aFind = rMap.find("name"); + if (aFind != rMap.end()) + { + sRet = aFind->second; + rMap.erase(aFind); + } + return sRet; + } + + OUString extractValuePos(VclBuilder::stringmap& rMap) + { + OUString sRet("top"); + VclBuilder::stringmap::iterator aFind = rMap.find("value-pos"); + if (aFind != rMap.end()) + { + sRet = aFind->second; + rMap.erase(aFind); + } + return sRet; + } + + OUString extractTypeHint(VclBuilder::stringmap &rMap) + { + OUString sRet("normal"); + VclBuilder::stringmap::iterator aFind = rMap.find("type-hint"); + if (aFind != rMap.end()) + { + sRet = aFind->second; + rMap.erase(aFind); + } + return sRet; + } + + bool extractResizable(VclBuilder::stringmap &rMap) + { + bool bResizable = true; + VclBuilder::stringmap::iterator aFind = rMap.find("resizable"); + if (aFind != rMap.end()) + { + bResizable = toBool(aFind->second); + rMap.erase(aFind); + } + return bResizable; + } + +#if HAVE_FEATURE_DESKTOP + bool extractModal(VclBuilder::stringmap &rMap) + { + bool bModal = false; + VclBuilder::stringmap::iterator aFind = rMap.find("modal"); + if (aFind != rMap.end()) + { + bModal = toBool(aFind->second); + rMap.erase(aFind); + } + return bModal; + } +#endif + + bool extractDecorated(VclBuilder::stringmap &rMap) + { + bool bDecorated = true; + VclBuilder::stringmap::iterator aFind = rMap.find("decorated"); + if (aFind != rMap.end()) + { + bDecorated = toBool(aFind->second); + rMap.erase(aFind); + } + return bDecorated; + } + + bool extractCloseable(VclBuilder::stringmap &rMap) + { + bool bCloseable = true; + VclBuilder::stringmap::iterator aFind = rMap.find("deletable"); + if (aFind != rMap.end()) + { + bCloseable = toBool(aFind->second); + rMap.erase(aFind); + } + return bCloseable; + } + + bool extractEntry(VclBuilder::stringmap &rMap) + { + bool bHasEntry = false; + VclBuilder::stringmap::iterator aFind = rMap.find("has-entry"); + if (aFind != rMap.end()) + { + bHasEntry = toBool(aFind->second); + rMap.erase(aFind); + } + return bHasEntry; + } + + bool extractOrientation(VclBuilder::stringmap &rMap) + { + bool bVertical = false; + VclBuilder::stringmap::iterator aFind = rMap.find("orientation"); + if (aFind != rMap.end()) + { + bVertical = aFind->second.equalsIgnoreAsciiCase("vertical"); + rMap.erase(aFind); + } + return bVertical; + } + + bool extractVerticalTabPos(VclBuilder::stringmap &rMap) + { + bool bVertical = false; + VclBuilder::stringmap::iterator aFind = rMap.find("tab-pos"); + if (aFind != rMap.end()) + { + bVertical = aFind->second.equalsIgnoreAsciiCase("left") || + aFind->second.equalsIgnoreAsciiCase("right"); + rMap.erase(aFind); + } + return bVertical; + } + + bool extractInconsistent(VclBuilder::stringmap &rMap) + { + bool bInconsistent = false; + VclBuilder::stringmap::iterator aFind = rMap.find("inconsistent"); + if (aFind != rMap.end()) + { + bInconsistent = toBool(aFind->second); + rMap.erase(aFind); + } + return bInconsistent; + } + + OUString extractIconName(VclBuilder::stringmap &rMap) + { + OUString sIconName; + // allow pixbuf, but prefer icon-name + { + VclBuilder::stringmap::iterator aFind = rMap.find(OString("pixbuf")); + if (aFind != rMap.end()) + { + sIconName = aFind->second; + rMap.erase(aFind); + } + } + { + VclBuilder::stringmap::iterator aFind = rMap.find(OString("icon-name")); + if (aFind != rMap.end()) + { + sIconName = aFind->second; + rMap.erase(aFind); + } + } + if (sIconName == "missing-image") + return OUString(); + OUString sReplace = mapStockToImageResource(sIconName); + return !sReplace.isEmpty() ? sReplace : sIconName; + } + + WinBits extractRelief(VclBuilder::stringmap &rMap) + { + WinBits nBits = WB_3DLOOK; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("relief")); + if (aFind != rMap.end()) + { + if (aFind->second == "half") + nBits = WB_FLATBUTTON | WB_BEVELBUTTON; + else if (aFind->second == "none") + nBits = WB_FLATBUTTON; + rMap.erase(aFind); + } + return nBits; + } + + OUString extractLabel(VclBuilder::stringmap &rMap) + { + OUString sType; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("label")); + if (aFind != rMap.end()) + { + sType = aFind->second; + rMap.erase(aFind); + } + return sType; + } + + OUString extractActionName(VclBuilder::stringmap &rMap) + { + OUString sActionName; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("action-name")); + if (aFind != rMap.end()) + { + sActionName = aFind->second; + rMap.erase(aFind); + } + return sActionName; + } + + bool extractVisible(VclBuilder::stringmap &rMap) + { + bool bRet = false; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("visible")); + if (aFind != rMap.end()) + { + bRet = toBool(aFind->second); + rMap.erase(aFind); + } + return bRet; + } + + Size extractSizeRequest(VclBuilder::stringmap &rMap) + { + OUString sWidthRequest("0"); + OUString sHeightRequest("0"); + VclBuilder::stringmap::iterator aFind = rMap.find(OString("width-request")); + if (aFind != rMap.end()) + { + sWidthRequest = aFind->second; + rMap.erase(aFind); + } + aFind = rMap.find("height-request"); + if (aFind != rMap.end()) + { + sHeightRequest = aFind->second; + rMap.erase(aFind); + } + return Size(sWidthRequest.toInt32(), sHeightRequest.toInt32()); + } + + OUString extractTooltipText(VclBuilder::stringmap &rMap) + { + OUString sTooltipText; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("tooltip-text")); + if (aFind == rMap.end()) + aFind = rMap.find(OString("tooltip-markup")); + if (aFind != rMap.end()) + { + sTooltipText = aFind->second; + rMap.erase(aFind); + } + return sTooltipText; + } + + float extractAlignment(VclBuilder::stringmap &rMap) + { + float f = 0.0; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("alignment")); + if (aFind != rMap.end()) + { + f = aFind->second.toFloat(); + rMap.erase(aFind); + } + return f; + } + + OUString extractTitle(VclBuilder::stringmap &rMap) + { + OUString sTitle; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("title")); + if (aFind != rMap.end()) + { + sTitle = aFind->second; + rMap.erase(aFind); + } + return sTitle; + } + + bool extractHeadersVisible(VclBuilder::stringmap &rMap) + { + bool bHeadersVisible = true; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("headers-visible")); + if (aFind != rMap.end()) + { + bHeadersVisible = toBool(aFind->second); + rMap.erase(aFind); + } + return bHeadersVisible; + } + + bool extractSortIndicator(VclBuilder::stringmap &rMap) + { + bool bSortIndicator = false; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("sort-indicator")); + if (aFind != rMap.end()) + { + bSortIndicator = toBool(aFind->second); + rMap.erase(aFind); + } + return bSortIndicator; + } + + bool extractClickable(VclBuilder::stringmap &rMap) + { + bool bClickable = false; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("clickable")); + if (aFind != rMap.end()) + { + bClickable = toBool(aFind->second); + rMap.erase(aFind); + } + return bClickable; + } + + void setupFromActionName(Button *pButton, VclBuilder::stringmap &rMap, const css::uno::Reference<css::frame::XFrame>& rFrame) + { + if (!rFrame.is()) + return; + + OUString aCommand(extractActionName(rMap)); + if (aCommand.isEmpty()) + return; + + OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(rFrame)); + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommand, aModuleName); + OUString aLabel(vcl::CommandInfoProvider::GetLabelForCommand(aProperties)); + if (!aLabel.isEmpty()) + pButton->SetText(aLabel); + + OUString aTooltip(vcl::CommandInfoProvider::GetTooltipForCommand(aCommand, aProperties, rFrame)); + if (!aTooltip.isEmpty()) + pButton->SetQuickHelpText(aTooltip); + + Image aImage(vcl::CommandInfoProvider::GetImageForCommand(aCommand, rFrame)); + pButton->SetModeImage(aImage); + + pButton->SetCommandHandler(aCommand); + } + + VclPtr<Button> extractStockAndBuildPushButton(vcl::Window *pParent, VclBuilder::stringmap &rMap, bool bToggle) + { + WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER; + if (bToggle) + nBits |= WB_TOGGLE; + + nBits |= extractRelief(rMap); + + VclPtr<Button> xWindow = VclPtr<PushButton>::Create(pParent, nBits); + return xWindow; + } + + VclPtr<MenuButton> extractStockAndBuildMenuButton(vcl::Window *pParent, VclBuilder::stringmap &rMap) + { + WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_3DLOOK; + + nBits |= extractRelief(rMap); + + VclPtr<MenuButton> xWindow = VclPtr<MenuButton>::Create(pParent, nBits); + return xWindow; + } + + VclPtr<MenuButton> extractStockAndBuildMenuToggleButton(vcl::Window *pParent, VclBuilder::stringmap &rMap) + { + WinBits nBits = WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_3DLOOK; + + nBits |= extractRelief(rMap); + + VclPtr<MenuButton> xWindow = VclPtr<MenuToggleButton>::Create(pParent, nBits); + return xWindow; + } + + WinBits extractDeferredBits(VclBuilder::stringmap &rMap) + { + WinBits nBits = WB_3DLOOK|WB_HIDE; + if (extractResizable(rMap)) + nBits |= WB_SIZEABLE; + if (extractCloseable(rMap)) + nBits |= WB_CLOSEABLE; + if (!extractDecorated(rMap)) + nBits |= WB_OWNERDRAWDECORATION; + OUString sType(extractTypeHint(rMap)); + if (sType == "utility") + nBits |= WB_SYSTEMWINDOW | WB_DIALOGCONTROL | WB_MOVEABLE; + else if (sType == "popup-menu") + nBits |= WB_SYSTEMWINDOW | WB_DIALOGCONTROL | WB_POPUP; + else if (sType == "dock") + nBits |= WB_DOCKABLE | WB_MOVEABLE; + else + nBits |= WB_MOVEABLE; + return nBits; + } +} + +void VclBuilder::extractGroup(const OString &id, stringmap &rMap) +{ + VclBuilder::stringmap::iterator aFind = rMap.find(OString("group")); + if (aFind != rMap.end()) + { + OUString sID = aFind->second; + sal_Int32 nDelim = sID.indexOf(':'); + if (nDelim != -1) + sID = sID.copy(0, nDelim); + m_pParserState->m_aGroupMaps.emplace_back(id, sID.toUtf8()); + rMap.erase(aFind); + } +} + +void VclBuilder::connectNumericFormatterAdjustment(const OString &id, const OUString &rAdjustment) +{ + if (!rAdjustment.isEmpty()) + m_pParserState->m_aNumericFormatterAdjustmentMaps.emplace_back(id, rAdjustment); +} + +void VclBuilder::connectFormattedFormatterAdjustment(const OString &id, const OUString &rAdjustment) +{ + if (!rAdjustment.isEmpty()) + m_pParserState->m_aFormattedFormatterAdjustmentMaps.emplace_back(id, rAdjustment); +} + +bool VclBuilder::extractAdjustmentToMap(const OString& id, VclBuilder::stringmap& rMap, std::vector<WidgetAdjustmentMap>& rAdjustmentMap) +{ + VclBuilder::stringmap::iterator aFind = rMap.find(OString("adjustment")); + if (aFind != rMap.end()) + { + rAdjustmentMap.emplace_back(id, aFind->second); + rMap.erase(aFind); + return true; + } + return false; +} + +namespace +{ + sal_Int32 extractActive(VclBuilder::stringmap &rMap) + { + sal_Int32 nActiveId = 0; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("active")); + if (aFind != rMap.end()) + { + nActiveId = aFind->second.toInt32(); + rMap.erase(aFind); + } + return nActiveId; + } + + bool extractSelectable(VclBuilder::stringmap &rMap) + { + bool bSelectable = false; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("selectable")); + if (aFind != rMap.end()) + { + bSelectable = toBool(aFind->second); + rMap.erase(aFind); + } + return bSelectable; + } + + OUString extractAdjustment(VclBuilder::stringmap &rMap) + { + OUString sAdjustment; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("adjustment")); + if (aFind != rMap.end()) + { + sAdjustment= aFind->second; + rMap.erase(aFind); + return sAdjustment; + } + return sAdjustment; + } + + bool extractDrawIndicator(VclBuilder::stringmap &rMap) + { + bool bDrawIndicator = false; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("draw-indicator")); + if (aFind != rMap.end()) + { + bDrawIndicator = toBool(aFind->second); + rMap.erase(aFind); + } + return bDrawIndicator; + } +} + +void VclBuilder::extractModel(const OString &id, stringmap &rMap) +{ + VclBuilder::stringmap::iterator aFind = rMap.find(OString("model")); + if (aFind != rMap.end()) + { + m_pParserState->m_aModelMaps.emplace_back(id, aFind->second, + extractActive(rMap)); + rMap.erase(aFind); + } +} + +void VclBuilder::extractBuffer(const OString &id, stringmap &rMap) +{ + VclBuilder::stringmap::iterator aFind = rMap.find(OString("buffer")); + if (aFind != rMap.end()) + { + m_pParserState->m_aTextBufferMaps.emplace_back(id, aFind->second); + rMap.erase(aFind); + } +} + +int VclBuilder::getImageSize(const stringmap &rMap) +{ + int nSize = 4; + auto aFind = rMap.find(OString("icon-size")); + if (aFind != rMap.end()) + nSize = aFind->second.toInt32(); + return nSize; +} + +void VclBuilder::extractButtonImage(const OString &id, stringmap &rMap, bool bRadio) +{ + VclBuilder::stringmap::iterator aFind = rMap.find(OString("image")); + if (aFind != rMap.end()) + { + m_pParserState->m_aButtonImageWidgetMaps.emplace_back(id, aFind->second, bRadio); + rMap.erase(aFind); + } +} + +void VclBuilder::extractMnemonicWidget(const OString &rLabelID, stringmap &rMap) +{ + VclBuilder::stringmap::iterator aFind = rMap.find(OString("mnemonic-widget")); + if (aFind != rMap.end()) + { + OUString sID = aFind->second; + sal_Int32 nDelim = sID.indexOf(':'); + if (nDelim != -1) + sID = sID.copy(0, nDelim); + m_pParserState->m_aMnemonicWidgetMaps.emplace_back(rLabelID, sID); + rMap.erase(aFind); + } +} + +vcl::Window* VclBuilder::prepareWidgetOwnScrolling(vcl::Window *pParent, WinBits &rWinStyle) +{ + //For Widgets that manage their own scrolling, if one appears as a child of + //a scrolling window shoehorn that scrolling settings to this widget and + //return the real parent to use + if (pParent && pParent->GetType() == WindowType::SCROLLWINDOW) + { + WinBits nScrollBits = pParent->GetStyle(); + nScrollBits &= (WB_AUTOHSCROLL|WB_HSCROLL|WB_AUTOVSCROLL|WB_VSCROLL); + rWinStyle |= nScrollBits; + if (static_cast<VclScrolledWindow*>(pParent)->HasVisibleBorder()) + rWinStyle |= WB_BORDER; + pParent = pParent->GetParent(); + } + + return pParent; +} + +void VclBuilder::cleanupWidgetOwnScrolling(vcl::Window *pScrollParent, vcl::Window *pWindow, stringmap &rMap) +{ + //remove the redundant scrolling parent + sal_Int32 nWidthReq = pScrollParent->get_width_request(); + rMap[OString("width-request")] = OUString::number(nWidthReq); + sal_Int32 nHeightReq = pScrollParent->get_height_request(); + rMap[OString("height-request")] = OUString::number(nHeightReq); + + m_pParserState->m_aRedundantParentWidgets[pScrollParent] = pWindow; +} + +#ifndef DISABLE_DYNLOADING + +extern "C" { static void thisModule() {} } + +namespace { + +// Don't unload the module on destruction +class NoAutoUnloadModule : public osl::Module +{ +public: + ~NoAutoUnloadModule() { release(); } +}; + +} + +typedef std::map<OUString, std::shared_ptr<NoAutoUnloadModule>> ModuleMap; +static ModuleMap g_aModuleMap; + +#if ENABLE_MERGELIBS +static std::shared_ptr<NoAutoUnloadModule> g_pMergedLib = std::make_shared<NoAutoUnloadModule>(); +#endif + +#ifndef SAL_DLLPREFIX +# define SAL_DLLPREFIX "" +#endif + +#endif + +namespace vcl { + +void VclBuilderPreload() +{ +#ifndef DISABLE_DYNLOADING + +#if ENABLE_MERGELIBS + g_pMergedLib->loadRelative(&thisModule, SVLIBRARY("merged")); +#else +// find -name '*ui*' | xargs grep 'class=".*lo-' | +// sed 's/.*class="//' | sed 's/-.*$//' | sort | uniq + static const char *aWidgetLibs[] = { + "sfxlo", "svtlo" + }; + for (const auto & lib : aWidgetLibs) + { + std::unique_ptr<NoAutoUnloadModule> pModule(new NoAutoUnloadModule); + OUString sModule = SAL_DLLPREFIX + OUString::createFromAscii(lib) + SAL_DLLEXTENSION; + if (pModule->loadRelative(&thisModule, sModule)) + g_aModuleMap.insert(std::make_pair(sModule, std::move(pModule))); + } +#endif // ENABLE_MERGELIBS +#endif // DISABLE_DYNLOADING +} + +} + +#if defined DISABLE_DYNLOADING && !HAVE_FEATURE_DESKTOP +extern "C" VclBuilder::customMakeWidget lo_get_custom_widget_func(const char* name); +#endif + +namespace +{ +// Takes a string like "sfxlo-NotebookbarToolBox" +VclBuilder::customMakeWidget GetCustomMakeWidget(const OString& rName) +{ + const OString name = rName == "sfxlo-SidebarToolBox" ? "sfxlo-NotebookbarToolBox" : rName; + VclBuilder::customMakeWidget pFunction = nullptr; + if (sal_Int32 nDelim = name.indexOf('-'); nDelim != -1) + { + const OString aFunction(OString::Concat("make") + name.subView(nDelim + 1)); + const OUString sFunction(OStringToOUString(aFunction, RTL_TEXTENCODING_UTF8)); + +#ifndef DISABLE_DYNLOADING + const OUString sModule = SAL_DLLPREFIX + + OStringToOUString(name.subView(0, nDelim), RTL_TEXTENCODING_UTF8) + + SAL_DLLEXTENSION; + ModuleMap::iterator aI = g_aModuleMap.find(sModule); + if (aI == g_aModuleMap.end()) + { + std::shared_ptr<NoAutoUnloadModule> pModule; +#if ENABLE_MERGELIBS + if (!g_pMergedLib->is()) + g_pMergedLib->loadRelative(&thisModule, SVLIBRARY("merged")); + if ((pFunction = reinterpret_cast<VclBuilder::customMakeWidget>( + g_pMergedLib->getFunctionSymbol(sFunction)))) + pModule = g_pMergedLib; +#endif + if (!pFunction) + { + pModule = std::make_shared<NoAutoUnloadModule>(); + bool ok = pModule->loadRelative(&thisModule, sModule); + if (!ok) + { +#ifdef LINUX + // in the case of preloading, we don't have eg. the + // libcuilo.so, but still need to dlsym the symbols - + // which are already in-process + if (comphelper::LibreOfficeKit::isActive()) + { + pFunction = reinterpret_cast<VclBuilder::customMakeWidget>(dlsym(RTLD_DEFAULT, aFunction.getStr())); + ok = !!pFunction; + assert(ok && "couldn't even directly dlsym the sFunction (available via preload)"); + } +#endif + assert(ok && "bad module name in .ui"); + } + else + { + pFunction = reinterpret_cast<VclBuilder::customMakeWidget>( + pModule->getFunctionSymbol(sFunction)); + } + } + g_aModuleMap.insert(std::make_pair(sModule, pModule)); + } + else + pFunction = reinterpret_cast<VclBuilder::customMakeWidget>( + aI->second->getFunctionSymbol(sFunction)); +#elif !HAVE_FEATURE_DESKTOP + pFunction = lo_get_custom_widget_func(sFunction.toUtf8().getStr()); + SAL_WARN_IF(!pFunction, "vcl.builder", "Could not find " << sFunction); + assert(pFunction); +#else + pFunction = reinterpret_cast<VclBuilder::customMakeWidget>( + osl_getFunctionSymbol((oslModule)RTLD_DEFAULT, sFunction.pData)); +#endif + } + return pFunction; +} +} + +VclPtr<vcl::Window> VclBuilder::makeObject(vcl::Window *pParent, const OString &name, const OString &id, + stringmap &rMap) +{ + bool bIsPlaceHolder = name.isEmpty(); + bool bVertical = false; + + if (pParent && (pParent->GetType() == WindowType::TABCONTROL || + pParent->GetType() == WindowType::VERTICALTABCONTROL)) + { + bool bTopLevel(name == "GtkDialog" || name == "GtkMessageDialog" || + name == "GtkWindow" || name == "GtkPopover" || name == "GtkAssistant"); + if (!bTopLevel) + { + if (pParent->GetType() == WindowType::TABCONTROL) + { + //We have to add a page + //make default pageid == position + TabControl *pTabControl = static_cast<TabControl*>(pParent); + sal_uInt16 nNewPageCount = pTabControl->GetPageCount()+1; + sal_uInt16 nNewPageId = nNewPageCount; + pTabControl->InsertPage(nNewPageId, OUString()); + pTabControl->SetCurPageId(nNewPageId); + SAL_WARN_IF(bIsPlaceHolder, "vcl.builder", "we should have no placeholders for tabpages"); + if (!bIsPlaceHolder) + { + VclPtrInstance<TabPage> pPage(pTabControl); + pPage->Show(); + + //Make up a name for it + OString sTabPageId = get_by_window(pParent) + + "-page" + + OString::number(nNewPageCount); + m_aChildren.emplace_back(sTabPageId, pPage, false); + pPage->SetHelpId(m_sHelpRoot + sTabPageId); + + pParent = pPage; + + pTabControl->SetTabPage(nNewPageId, pPage); + } + } + else + { + VerticalTabControl *pTabControl = static_cast<VerticalTabControl*>(pParent); + SAL_WARN_IF(bIsPlaceHolder, "vcl.builder", "we should have no placeholders for tabpages"); + if (!bIsPlaceHolder) + pParent = pTabControl->GetPageParent(); + } + } + } + + if (bIsPlaceHolder || name == "GtkTreeSelection") + return nullptr; + + ToolBox *pToolBox = (pParent && pParent->GetType() == WindowType::TOOLBOX) ? static_cast<ToolBox*>(pParent) : nullptr; + + extractButtonImage(id, rMap, name == "GtkRadioButton"); + + VclPtr<vcl::Window> xWindow; + if (name == "GtkDialog" || name == "GtkAssistant") + { + // WB_ALLOWMENUBAR because we don't know in advance if we will encounter + // a menubar, and menubars need a BorderWindow in the toplevel, and + // such border windows need to be in created during the dialog ctor + WinBits nBits = WB_MOVEABLE|WB_3DLOOK|WB_ALLOWMENUBAR; + if (extractResizable(rMap)) + nBits |= WB_SIZEABLE; + if (extractCloseable(rMap)) + nBits |= WB_CLOSEABLE; + Dialog::InitFlag eInit = !pParent ? Dialog::InitFlag::NoParent : Dialog::InitFlag::Default; + if (name == "GtkAssistant") + xWindow = VclPtr<vcl::RoadmapWizard>::Create(pParent, nBits, eInit); + else + xWindow = VclPtr<Dialog>::Create(pParent, nBits, eInit); +#if HAVE_FEATURE_DESKTOP + if (!extractModal(rMap)) + xWindow->SetType(WindowType::MODELESSDIALOG); +#endif + } + else if (name == "GtkMessageDialog") + { + WinBits nBits = WB_MOVEABLE|WB_3DLOOK|WB_CLOSEABLE; + if (extractResizable(rMap)) + nBits |= WB_SIZEABLE; + VclPtr<MessageDialog> xDialog(VclPtr<MessageDialog>::Create(pParent, nBits)); + m_pParserState->m_aMessageDialogs.push_back(xDialog); + xWindow = xDialog; +#if defined _WIN32 + xWindow->set_border_width(3); +#else + xWindow->set_border_width(12); +#endif + } + else if (name == "GtkBox" || name == "GtkStatusbar") + { + bVertical = extractOrientation(rMap); + if (bVertical) + xWindow = VclPtr<VclVBox>::Create(pParent); + else + xWindow = VclPtr<VclHBox>::Create(pParent); + } + else if (name == "GtkPaned") + { + bVertical = extractOrientation(rMap); + if (bVertical) + xWindow = VclPtr<VclVPaned>::Create(pParent); + else + xWindow = VclPtr<VclHPaned>::Create(pParent); + } + else if (name == "GtkHBox") + xWindow = VclPtr<VclHBox>::Create(pParent); + else if (name == "GtkVBox") + xWindow = VclPtr<VclVBox>::Create(pParent); + else if (name == "GtkButtonBox") + { + bVertical = extractOrientation(rMap); + if (bVertical) + xWindow = VclPtr<VclVButtonBox>::Create(pParent); + else + xWindow = VclPtr<VclHButtonBox>::Create(pParent); + } + else if (name == "GtkHButtonBox") + xWindow = VclPtr<VclHButtonBox>::Create(pParent); + else if (name == "GtkVButtonBox") + xWindow = VclPtr<VclVButtonBox>::Create(pParent); + else if (name == "GtkGrid") + xWindow = VclPtr<VclGrid>::Create(pParent); + else if (name == "GtkFrame") + xWindow = VclPtr<VclFrame>::Create(pParent); + else if (name == "GtkExpander") + { + VclPtrInstance<VclExpander> pExpander(pParent); + m_pParserState->m_aExpanderWidgets.push_back(pExpander); + xWindow = pExpander; + } + else if (name == "GtkButton" || (!m_bLegacy && name == "GtkToggleButton")) + { + VclPtr<Button> xButton; + OUString sMenu = BuilderUtils::extractCustomProperty(rMap); + if (sMenu.isEmpty()) + xButton = extractStockAndBuildPushButton(pParent, rMap, name == "GtkToggleButton"); + else + { + assert(m_bLegacy && "use GtkMenuButton"); + xButton = extractStockAndBuildMenuButton(pParent, rMap); + m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu); + } + xButton->SetImageAlign(ImageAlign::Left); //default to left + setupFromActionName(xButton, rMap, m_xFrame); + xWindow = xButton; + } + else if (name == "GtkMenuButton") + { + VclPtr<MenuButton> xButton; + + OUString sMenu = extractPopupMenu(rMap); + if (!sMenu.isEmpty()) + m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu); + + OUString sType = extractWidgetName(rMap); + if (sType.isEmpty()) + { + xButton = extractStockAndBuildMenuButton(pParent, rMap); + xButton->SetAccessibleRole(css::accessibility::AccessibleRole::BUTTON_MENU); + } + else + { + xButton = extractStockAndBuildMenuToggleButton(pParent, rMap); + } + + xButton->SetImageAlign(ImageAlign::Left); //default to left + + if (!extractDrawIndicator(rMap)) + xButton->SetDropDown(PushButtonDropdownStyle::NONE); + + setupFromActionName(xButton, rMap, m_xFrame); + xWindow = xButton; + } + else if (name == "GtkToggleButton" && m_bLegacy) + { + VclPtr<Button> xButton; + OUString sMenu = BuilderUtils::extractCustomProperty(rMap); + assert(sMenu.getLength() && "not implemented yet"); + xButton = extractStockAndBuildMenuToggleButton(pParent, rMap); + m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu); + xButton->SetImageAlign(ImageAlign::Left); //default to left + setupFromActionName(xButton, rMap, m_xFrame); + xWindow = xButton; + } + else if (name == "GtkRadioButton") + { + extractGroup(id, rMap); + WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK; + VclPtr<RadioButton> xButton = VclPtr<RadioButton>::Create(pParent, true, nBits); + xButton->SetImageAlign(ImageAlign::Left); //default to left + xWindow = xButton; + } + else if (name == "GtkCheckButton") + { + WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK; + bool bIsTriState = extractInconsistent(rMap); + VclPtr<CheckBox> xCheckBox = VclPtr<CheckBox>::Create(pParent, nBits); + if (bIsTriState) + { + xCheckBox->EnableTriState(true); + xCheckBox->SetState(TRISTATE_INDET); + } + xCheckBox->SetImageAlign(ImageAlign::Left); //default to left + + xWindow = xCheckBox; + } + else if (name == "GtkSpinButton") + { + OUString sAdjustment = extractAdjustment(rMap); + + WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_3DLOOK|WB_SPIN|WB_REPEAT; + if (extractHasFrame(rMap)) + nBits |= WB_BORDER; + + connectFormattedFormatterAdjustment(id, sAdjustment); + VclPtrInstance<FormattedField> xField(pParent, nBits); + xField->GetFormatter().SetMinValue(0); + xWindow = xField; + } + else if (name == "GtkLinkButton") + xWindow = VclPtr<FixedHyperlink>::Create(pParent, WB_CENTER|WB_VCENTER|WB_3DLOOK|WB_NOLABEL); + else if (name == "GtkComboBox" || name == "GtkComboBoxText") + { + extractModel(id, rMap); + + WinBits nBits = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK; + + bool bDropdown = BuilderUtils::extractDropdown(rMap); + + if (bDropdown) + nBits |= WB_DROPDOWN; + + if (extractEntry(rMap)) + { + VclPtrInstance<ComboBox> xComboBox(pParent, nBits); + xComboBox->EnableAutoSize(true); + xWindow = xComboBox; + } + else + { + VclPtrInstance<ListBox> xListBox(pParent, nBits|WB_SIMPLEMODE); + xListBox->EnableAutoSize(true); + xWindow = xListBox; + } + } + else if (name == "VclOptionalBox" || name == "sfxlo-OptionalBox") + { + // tdf#135495 fallback sfxlo-OptionalBox to VclOptionalBox as a stopgap + xWindow = VclPtr<OptionalBox>::Create(pParent); + } + else if (name == "svtlo-ManagedMenuButton") + { + // like tdf#135495 keep the name svtlo-ManagedMenuButton even though it's a misnomer + // and is not dlsymed from the svt library + xWindow = VclPtr<ManagedMenuButton>::Create(pParent, WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_FLATBUTTON); + OUString sMenu = BuilderUtils::extractCustomProperty(rMap); + if (!sMenu.isEmpty()) + m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu); + setupFromActionName(static_cast<Button*>(xWindow.get()), rMap, m_xFrame); + } + else if (name == "sfxlo-PriorityMergedHBox") + { + // like tdf#135495 above, keep the sfxlo-PriorityMergedHBox even though its not in sfx anymore + xWindow = VclPtr<PriorityMergedHBox>::Create(pParent); + } + else if (name == "sfxlo-PriorityHBox") + { + // like tdf#135495 above, keep the sfxlo-PriorityMergedHBox even though its not in sfx anymore + xWindow = VclPtr<PriorityHBox>::Create(pParent); + } + else if (name == "sfxlo-DropdownBox") + { + // like tdf#135495 above, keep the sfxlo-PriorityMergedHBox even though its not in sfx anymore + xWindow = VclPtr<DropdownBox>::Create(pParent); + } + else if (name == "sfxlo-ContextVBox") + { + // like tdf#135495 above, keep the sfxlo-PriorityMergedHBox even though its not in sfx anymore + xWindow = VclPtr<ContextVBox>::Create(pParent); + } + else if (name == "GtkIconView") + { + assert(rMap.find(OString("model")) != rMap.end() && "GtkIconView must have a model"); + + //window we want to apply the packing props for this GtkIconView to + VclPtr<vcl::Window> xWindowForPackingProps; + extractModel(id, rMap); + WinBits nWinStyle = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK; + //IconView manages its own scrolling, + vcl::Window *pRealParent = prepareWidgetOwnScrolling(pParent, nWinStyle); + + VclPtr<IconView> xBox = VclPtr<IconView>::Create(pRealParent, nWinStyle); + xWindowForPackingProps = xBox; + + xWindow = xBox; + xBox->SetNoAutoCurEntry(true); + xBox->SetQuickSearch(true); + + if (pRealParent != pParent) + cleanupWidgetOwnScrolling(pParent, xWindowForPackingProps, rMap); + } + else if (name == "GtkTreeView") + { + if (!m_bLegacy) + { + assert(rMap.find(OString("model")) != rMap.end() && "GtkTreeView must have a model"); + } + + //window we want to apply the packing props for this GtkTreeView to + VclPtr<vcl::Window> xWindowForPackingProps; + //To-Do + //a) make SvHeaderTabListBox/SvTabListBox the default target for GtkTreeView + //b) remove the non-drop down mode of ListBox and convert + // everything over to SvHeaderTabListBox/SvTabListBox + extractModel(id, rMap); + WinBits nWinStyle = WB_CLIPCHILDREN|WB_LEFT|WB_VCENTER|WB_3DLOOK; + if (m_bLegacy) + { + OUString sBorder = BuilderUtils::extractCustomProperty(rMap); + if (!sBorder.isEmpty()) + nWinStyle |= WB_BORDER; + } + else + { + nWinStyle |= WB_HASBUTTONS | WB_HASBUTTONSATROOT; + } + //ListBox/SvHeaderTabListBox manages its own scrolling, + vcl::Window *pRealParent = prepareWidgetOwnScrolling(pParent, nWinStyle); + if (m_bLegacy) + { + xWindow = VclPtr<ListBox>::Create(pRealParent, nWinStyle | WB_SIMPLEMODE); + xWindowForPackingProps = xWindow; + } + else + { + VclPtr<SvTabListBox> xBox; + bool bHeadersVisible = extractHeadersVisible(rMap); + if (bHeadersVisible) + { + VclPtr<VclVBox> xContainer = VclPtr<VclVBox>::Create(pRealParent); + OString containerid(id + "-container"); + xContainer->SetHelpId(m_sHelpRoot + containerid); + m_aChildren.emplace_back(containerid, xContainer, true); + + VclPtrInstance<HeaderBar> xHeader(xContainer, WB_BUTTONSTYLE | WB_BORDER | WB_TABSTOP | WB_3DLOOK); + xHeader->set_width_request(0); // let the headerbar width not affect the size request + OString headerid(id + "-header"); + xHeader->SetHelpId(m_sHelpRoot + headerid); + m_aChildren.emplace_back(headerid, xHeader, true); + + VclPtr<LclHeaderTabListBox> xHeaderBox = VclPtr<LclHeaderTabListBox>::Create(xContainer, nWinStyle); + xHeaderBox->InitHeaderBar(xHeader); + xContainer->set_expand(true); + xHeader->Show(); + xContainer->Show(); + xBox = xHeaderBox; + xWindowForPackingProps = xContainer; + } + else + { + xBox = VclPtr<LclTabListBox>::Create(pRealParent, nWinStyle); + xWindowForPackingProps = xBox; + } + xWindow = xBox; + xBox->SetNoAutoCurEntry(true); + xBox->SetQuickSearch(true); + xBox->SetSpaceBetweenEntries(3); + xBox->SetEntryHeight(16); + xBox->SetHighlightRange(); // select over the whole width + } + if (pRealParent != pParent) + cleanupWidgetOwnScrolling(pParent, xWindowForPackingProps, rMap); + } + else if (name == "GtkTreeViewColumn") + { + if (!m_bLegacy) + { + SvHeaderTabListBox* pTreeView = dynamic_cast<SvHeaderTabListBox*>(pParent); + if (HeaderBar* pHeaderBar = pTreeView ? pTreeView->GetHeaderBar() : nullptr) + { + HeaderBarItemBits nBits = HeaderBarItemBits::LEFTIMAGE; + if (extractClickable(rMap)) + nBits |= HeaderBarItemBits::CLICKABLE; + if (extractSortIndicator(rMap)) + nBits |= HeaderBarItemBits::DOWNARROW; + float fAlign = extractAlignment(rMap); + if (fAlign == 0.0) + nBits |= HeaderBarItemBits::LEFT; + else if (fAlign == 1.0) + nBits |= HeaderBarItemBits::RIGHT; + else if (fAlign == 0.5) + nBits |= HeaderBarItemBits::CENTER; + auto nItemId = pHeaderBar->GetItemCount() + 1; + OUString sTitle(extractTitle(rMap)); + pHeaderBar->InsertItem(nItemId, sTitle, 100, nBits); + } + } + } + else if (name == "GtkLabel") + { + WinBits nWinStyle = WB_CENTER|WB_VCENTER|WB_3DLOOK; + extractMnemonicWidget(id, rMap); + if (extractSelectable(rMap)) + xWindow = VclPtr<SelectableFixedText>::Create(pParent, nWinStyle); + else + xWindow = VclPtr<FixedText>::Create(pParent, nWinStyle); + } + else if (name == "GtkImage") + { + VclPtr<FixedImage> xFixedImage = VclPtr<FixedImage>::Create(pParent, WB_CENTER|WB_VCENTER|WB_3DLOOK|WB_SCALE); + OUString sIconName = extractIconName(rMap); + if (!sIconName.isEmpty()) + xFixedImage->SetImage(FixedImage::loadThemeImage(sIconName)); + m_pParserState->m_aImageSizeMap[id] = getImageSize(rMap); + xWindow = xFixedImage; + //such parentless GtkImages are temps used to set icons on buttons + //default them to hidden to stop e.g. insert->index entry flicking temp + //full screen windows + if (!pParent) + { + rMap["visible"] = "false"; + } + } + else if (name == "GtkSeparator") + { + bVertical = extractOrientation(rMap); + xWindow = VclPtr<FixedLine>::Create(pParent, bVertical ? WB_VERT : WB_HORZ); + } + else if (name == "GtkScrollbar") + { + extractAdjustmentToMap(id, rMap, m_pParserState->m_aScrollAdjustmentMaps); + bVertical = extractOrientation(rMap); + xWindow = VclPtr<ScrollBar>::Create(pParent, bVertical ? WB_VERT : WB_HORZ); + } + else if (name == "GtkProgressBar") + { + extractAdjustmentToMap(id, rMap, m_pParserState->m_aScrollAdjustmentMaps); + bVertical = extractOrientation(rMap); + xWindow = VclPtr<ProgressBar>::Create(pParent, bVertical ? WB_VERT : WB_HORZ); + } + else if (name == "GtkScrolledWindow") + { + xWindow = VclPtr<VclScrolledWindow>::Create(pParent); + } + else if (name == "GtkViewport") + { + xWindow = VclPtr<VclViewport>::Create(pParent); + } + else if (name == "GtkEventBox") + { + xWindow = VclPtr<VclEventBox>::Create(pParent); + } + else if (name == "GtkEntry") + { + WinBits nWinStyle = WB_LEFT|WB_VCENTER|WB_3DLOOK; + if (extractHasFrame(rMap)) + nWinStyle |= WB_BORDER; + xWindow = VclPtr<Edit>::Create(pParent, nWinStyle); + BuilderUtils::ensureDefaultWidthChars(rMap); + } + else if (name == "GtkNotebook") + { + if (!extractVerticalTabPos(rMap)) + xWindow = VclPtr<TabControl>::Create(pParent, WB_STDTABCONTROL|WB_3DLOOK); + else + xWindow = VclPtr<VerticalTabControl>::Create(pParent); + } + else if (name == "GtkDrawingArea") + { + xWindow = VclPtr<VclDrawingArea>::Create(pParent, WB_TABSTOP); + } + else if (name == "GtkTextView") + { + extractBuffer(id, rMap); + + WinBits nWinStyle = WB_CLIPCHILDREN|WB_LEFT; + //VclMultiLineEdit manages its own scrolling, + vcl::Window *pRealParent = prepareWidgetOwnScrolling(pParent, nWinStyle); + xWindow = VclPtr<VclMultiLineEdit>::Create(pRealParent, nWinStyle); + if (pRealParent != pParent) + cleanupWidgetOwnScrolling(pParent, xWindow, rMap); + } + else if (name == "GtkSpinner") + { + xWindow = VclPtr<Throbber>::Create(pParent, WB_3DLOOK); + } + else if (name == "GtkScale") + { + extractAdjustmentToMap(id, rMap, m_pParserState->m_aSliderAdjustmentMaps); + bool bDrawValue = extractDrawValue(rMap); + if (bDrawValue) + { + OUString sValuePos = extractValuePos(rMap); + (void)sValuePos; + } + bVertical = extractOrientation(rMap); + + WinBits nWinStyle = bVertical ? WB_VERT : WB_HORZ; + + xWindow = VclPtr<Slider>::Create(pParent, nWinStyle); + } + else if (name == "GtkToolbar") + { + xWindow = VclPtr<ToolBox>::Create(pParent, WB_3DLOOK | WB_TABSTOP); + } + else if(name == "NotebookBarAddonsToolMergePoint") + { + customMakeWidget pFunction = GetCustomMakeWidget("sfxlo-NotebookbarToolBox"); + if(pFunction != nullptr) + NotebookBarAddonsMerger::MergeNotebookBarAddons(pParent, pFunction, m_xFrame, *m_pNotebookBarAddonsItem, rMap); + return nullptr; + } + else if (name == "GtkToolButton" || name == "GtkMenuToolButton" || + name == "GtkToggleToolButton" || name == "GtkRadioToolButton" || name == "GtkToolItem") + { + if (pToolBox) + { + OUString aCommand(extractActionName(rMap)); + + ToolBoxItemId nItemId(0); + ToolBoxItemBits nBits = ToolBoxItemBits::NONE; + if (name == "GtkMenuToolButton") + nBits |= ToolBoxItemBits::DROPDOWN; + else if (name == "GtkToggleToolButton") + nBits |= ToolBoxItemBits::AUTOCHECK | ToolBoxItemBits::CHECKABLE; + else if (name == "GtkRadioToolButton") + nBits |= ToolBoxItemBits::AUTOCHECK | ToolBoxItemBits::RADIOCHECK; + + if (!aCommand.isEmpty() && m_xFrame.is()) + { + pToolBox->InsertItem(aCommand, m_xFrame, nBits, extractSizeRequest(rMap)); + nItemId = pToolBox->GetItemId(aCommand); + } + else + { + nItemId = ToolBoxItemId(pToolBox->GetItemCount() + 1); + //TODO: ImplToolItems::size_type -> sal_uInt16! + pToolBox->InsertItem(nItemId, extractLabel(rMap), nBits); + if (aCommand.isEmpty() && !m_bLegacy) + aCommand = OUString::fromUtf8(id); + pToolBox->SetItemCommand(nItemId, aCommand); + } + + pToolBox->SetHelpId(nItemId, m_sHelpRoot + id); + OUString sTooltip(extractTooltipText(rMap)); + if (!sTooltip.isEmpty()) + pToolBox->SetQuickHelpText(nItemId, sTooltip); + + OUString sIconName(extractIconName(rMap)); + if (!sIconName.isEmpty()) + pToolBox->SetItemImage(nItemId, FixedImage::loadThemeImage(sIconName)); + + if (!extractVisible(rMap)) + pToolBox->HideItem(nItemId); + + m_pParserState->m_nLastToolbarId = nItemId; + + return nullptr; // no widget to be created + } + } + else if (name == "GtkSeparatorToolItem") + { + if (pToolBox) + { + pToolBox->InsertSeparator(); + return nullptr; // no widget to be created + } + } + else if (name == "GtkWindow") + { + WinBits nBits = extractDeferredBits(rMap); + if (nBits & WB_DOCKABLE) + xWindow = VclPtr<DockingWindow>::Create(pParent, nBits|WB_MOVEABLE); + else + xWindow = VclPtr<FloatingWindow>::Create(pParent, nBits|WB_MOVEABLE); + } + else if (name == "GtkPopover") + { + WinBits nBits = extractDeferredBits(rMap); + xWindow = VclPtr<DockingWindow>::Create(pParent, nBits|WB_DOCKABLE|WB_MOVEABLE); + } + else if (name == "GtkCalendar") + { + WinBits nBits = extractDeferredBits(rMap); + xWindow = VclPtr<Calendar>::Create(pParent, nBits); + } + else + { + if (customMakeWidget pFunction = GetCustomMakeWidget(name)) + { + pFunction(xWindow, pParent, rMap); + if (xWindow->GetType() == WindowType::PUSHBUTTON) + setupFromActionName(static_cast<Button*>(xWindow.get()), rMap, m_xFrame); + else if (xWindow->GetType() == WindowType::MENUBUTTON) + { + OUString sMenu = BuilderUtils::extractCustomProperty(rMap); + if (!sMenu.isEmpty()) + m_pParserState->m_aButtonMenuMaps.emplace_back(id, sMenu); + setupFromActionName(static_cast<Button*>(xWindow.get()), rMap, m_xFrame); + } + } + } + + SAL_INFO_IF(!xWindow, "vcl.builder", "probably need to implement " << name << " or add a make" << name << " function"); + if (xWindow) + { + // child windows of disabled windows are made disabled by vcl by default, we don't want that + WindowImpl *pWindowImpl = xWindow->ImplGetWindowImpl(); + pWindowImpl->mbDisabled = false; + + xWindow->SetHelpId(m_sHelpRoot + id); + SAL_INFO("vcl.builder", "for name '" << name << "' and id '" << id << + "', created " << xWindow.get() << " child of " << + pParent << "(" << xWindow->ImplGetWindowImpl()->mpParent.get() << "/" << + xWindow->ImplGetWindowImpl()->mpRealParent.get() << "/" << + xWindow->ImplGetWindowImpl()->mpBorderWindow.get() << ") with helpid " << + xWindow->GetHelpId()); + m_aChildren.emplace_back(id, xWindow, bVertical); + + // if the parent was a toolbox set it as an itemwindow for the latest itemid + if (pToolBox) + { + Size aSize(xWindow->GetSizePixel()); + aSize.setHeight(xWindow->get_preferred_size().Height()); + xWindow->SetSizePixel(aSize); + pToolBox->SetItemWindow(m_pParserState->m_nLastToolbarId, xWindow); + pToolBox->SetItemExpand(m_pParserState->m_nLastToolbarId, true); + } + } + return xWindow; +} + +namespace +{ + //return true for window types which exist in vcl but are not themselves + //represented in the .ui format, i.e. only their children exist. + bool isConsideredGtkPseudo(vcl::Window const *pWindow) + { + return pWindow->GetType() == WindowType::TABPAGE; + } +} + +//Any properties from .ui load we couldn't set because of potential virtual methods +//during ctor are applied here +void VclBuilder::setDeferredProperties() +{ + if (!m_bToplevelHasDeferredProperties) + return; + stringmap aDeferredProperties; + aDeferredProperties.swap(m_aDeferredProperties); + m_bToplevelHasDeferredProperties = false; + BuilderUtils::set_properties(m_pParent, aDeferredProperties); +} + +namespace BuilderUtils +{ + void set_properties(vcl::Window *pWindow, const VclBuilder::stringmap &rProps) + { + for (auto const& prop : rProps) + { + const OString &rKey = prop.first; + const OUString &rValue = prop.second; + pWindow->set_property(rKey, rValue); + } + } + + OUString convertMnemonicMarkup(std::u16string_view rIn) + { + OUStringBuffer aRet(rIn); + for (sal_Int32 nI = 0; nI < aRet.getLength(); ++nI) + { + if (aRet[nI] == '_' && nI+1 < aRet.getLength()) + { + if (aRet[nI+1] != '_') + aRet[nI] = MNEMONIC_CHAR; + else + aRet.remove(nI, 1); + ++nI; + } + } + return aRet.makeStringAndClear(); + } + + OUString extractCustomProperty(VclBuilder::stringmap &rMap) + { + OUString sCustomProperty; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("customproperty")); + if (aFind != rMap.end()) + { + sCustomProperty = aFind->second; + rMap.erase(aFind); + } + return sCustomProperty; + } + + void ensureDefaultWidthChars(VclBuilder::stringmap &rMap) + { + OString sWidthChars("width-chars"); + VclBuilder::stringmap::iterator aFind = rMap.find(sWidthChars); + if (aFind == rMap.end()) + rMap[sWidthChars] = "20"; + } + + bool extractDropdown(VclBuilder::stringmap &rMap) + { + bool bDropdown = true; + VclBuilder::stringmap::iterator aFind = rMap.find(OString("dropdown")); + if (aFind != rMap.end()) + { + bDropdown = toBool(aFind->second); + rMap.erase(aFind); + } + return bDropdown; + } + + void reorderWithinParent(vcl::Window &rWindow, sal_uInt16 nNewPosition) + { + WindowImpl *pWindowImpl = rWindow.ImplGetWindowImpl(); + if (pWindowImpl->mpParent != pWindowImpl->mpRealParent) + { + assert(pWindowImpl->mpBorderWindow == pWindowImpl->mpParent); + assert(pWindowImpl->mpBorderWindow->ImplGetWindowImpl()->mpParent == pWindowImpl->mpRealParent); + reorderWithinParent(*pWindowImpl->mpBorderWindow, nNewPosition); + return; + } + rWindow.reorderWithinParent(nNewPosition); + } + + void reorderWithinParent(std::vector<vcl::Window*>& rChilds, bool bIsButtonBox) + { + for (size_t i = 0; i < rChilds.size(); ++i) + { + reorderWithinParent(*rChilds[i], i); + + if (!bIsButtonBox) + continue; + + //The first member of the group for legacy code needs WB_GROUP set and the + //others not + WinBits nBits = rChilds[i]->GetStyle(); + nBits &= ~WB_GROUP; + if (i == 0) + nBits |= WB_GROUP; + rChilds[i]->SetStyle(nBits); + } + } + + sal_Int16 getRoleFromName(const OString& roleName) + { + using namespace com::sun::star::accessibility; + + static const std::unordered_map<OString, sal_Int16> aAtkRoleToAccessibleRole = { + /* This is in atkobject.h's AtkRole order */ + { "invalid", AccessibleRole::UNKNOWN }, + { "accelerator label", AccessibleRole::UNKNOWN }, + { "alert", AccessibleRole::ALERT }, + { "animation", AccessibleRole::UNKNOWN }, + { "arrow", AccessibleRole::UNKNOWN }, + { "calendar", AccessibleRole::UNKNOWN }, + { "canvas", AccessibleRole::CANVAS }, + { "check box", AccessibleRole::CHECK_BOX }, + { "check menu item", AccessibleRole::CHECK_MENU_ITEM }, + { "color chooser", AccessibleRole::COLOR_CHOOSER }, + { "column header", AccessibleRole::COLUMN_HEADER }, + { "combo box", AccessibleRole::COMBO_BOX }, + { "date editor", AccessibleRole::DATE_EDITOR }, + { "desktop icon", AccessibleRole::DESKTOP_ICON }, + { "desktop frame", AccessibleRole::DESKTOP_PANE }, // ? + { "dial", AccessibleRole::UNKNOWN }, + { "dialog", AccessibleRole::DIALOG }, + { "directory pane", AccessibleRole::DIRECTORY_PANE }, + { "drawing area", AccessibleRole::UNKNOWN }, + { "file chooser", AccessibleRole::FILE_CHOOSER }, + { "filler", AccessibleRole::FILLER }, + { "font chooser", AccessibleRole::FONT_CHOOSER }, + { "frame", AccessibleRole::FRAME }, + { "glass pane", AccessibleRole::GLASS_PANE }, + { "html container", AccessibleRole::UNKNOWN }, + { "icon", AccessibleRole::ICON }, + { "image", AccessibleRole::GRAPHIC }, + { "internal frame", AccessibleRole::INTERNAL_FRAME }, + { "label", AccessibleRole::LABEL }, + { "layered pane", AccessibleRole::LAYERED_PANE }, + { "list", AccessibleRole::LIST }, + { "list item", AccessibleRole::LIST_ITEM }, + { "menu", AccessibleRole::MENU }, + { "menu bar", AccessibleRole::MENU_BAR }, + { "menu item", AccessibleRole::MENU_ITEM }, + { "option pane", AccessibleRole::OPTION_PANE }, + { "page tab", AccessibleRole::PAGE_TAB }, + { "page tab list", AccessibleRole::PAGE_TAB_LIST }, + { "panel", AccessibleRole::PANEL }, // or SHAPE or TEXT_FRAME ? + { "password text", AccessibleRole::PASSWORD_TEXT }, + { "popup menu", AccessibleRole::POPUP_MENU }, + { "progress bar", AccessibleRole::PROGRESS_BAR }, + { "push button", AccessibleRole::PUSH_BUTTON }, // or BUTTON_DROPDOWN or BUTTON_MENU + { "radio button", AccessibleRole::RADIO_BUTTON }, + { "radio menu item", AccessibleRole::RADIO_MENU_ITEM }, + { "root pane", AccessibleRole::ROOT_PANE }, + { "row header", AccessibleRole::ROW_HEADER }, + { "scroll bar", AccessibleRole::SCROLL_BAR }, + { "scroll pane", AccessibleRole::SCROLL_PANE }, + { "separator", AccessibleRole::SEPARATOR }, + { "slider", AccessibleRole::SLIDER }, + { "split pane", AccessibleRole::SPLIT_PANE }, + { "spin button", AccessibleRole::SPIN_BOX }, // ? + { "statusbar", AccessibleRole::STATUS_BAR }, + { "table", AccessibleRole::TABLE }, + { "table cell", AccessibleRole::TABLE_CELL }, + { "table column header", AccessibleRole::COLUMN_HEADER }, // approximate + { "table row header", AccessibleRole::ROW_HEADER }, // approximate + { "tear off menu item", AccessibleRole::UNKNOWN }, + { "terminal", AccessibleRole::UNKNOWN }, + { "text", AccessibleRole::TEXT }, + { "toggle button", AccessibleRole::TOGGLE_BUTTON }, + { "tool bar", AccessibleRole::TOOL_BAR }, + { "tool tip", AccessibleRole::TOOL_TIP }, + { "tree", AccessibleRole::TREE }, + { "tree table", AccessibleRole::TREE_TABLE }, + { "unknown", AccessibleRole::UNKNOWN }, + { "viewport", AccessibleRole::VIEW_PORT }, + { "window", AccessibleRole::WINDOW }, + { "header", AccessibleRole::HEADER }, + { "footer", AccessibleRole::FOOTER }, + { "paragraph", AccessibleRole::PARAGRAPH }, + { "ruler", AccessibleRole::RULER }, + { "application", AccessibleRole::UNKNOWN }, + { "autocomplete", AccessibleRole::UNKNOWN }, + { "edit bar", AccessibleRole::EDIT_BAR }, + { "embedded", AccessibleRole::EMBEDDED_OBJECT }, + { "entry", AccessibleRole::UNKNOWN }, + { "chart", AccessibleRole::CHART }, + { "caption", AccessibleRole::CAPTION }, + { "document frame", AccessibleRole::DOCUMENT }, + { "heading", AccessibleRole::HEADING }, + { "page", AccessibleRole::PAGE }, + { "section", AccessibleRole::SECTION }, + { "redundant object", AccessibleRole::UNKNOWN }, + { "form", AccessibleRole::FORM }, + { "link", AccessibleRole::HYPER_LINK }, + { "input method window", AccessibleRole::UNKNOWN }, + { "table row", AccessibleRole::UNKNOWN }, + { "tree item", AccessibleRole::TREE_ITEM }, + { "document spreadsheet", AccessibleRole::DOCUMENT_SPREADSHEET }, + { "document presentation", AccessibleRole::DOCUMENT_PRESENTATION }, + { "document text", AccessibleRole::DOCUMENT_TEXT }, + { "document web", AccessibleRole::DOCUMENT }, // approximate + { "document email", AccessibleRole::DOCUMENT }, // approximate + { "comment", AccessibleRole::COMMENT }, // or NOTE or END_NOTE or FOOTNOTE or SCROLL_PANE + { "list box", AccessibleRole::UNKNOWN }, + { "grouping", AccessibleRole::GROUP_BOX }, + { "image map", AccessibleRole::IMAGE_MAP }, + { "notification", AccessibleRole::UNKNOWN }, + { "info bar", AccessibleRole::UNKNOWN }, + { "level bar", AccessibleRole::UNKNOWN }, + { "title bar", AccessibleRole::UNKNOWN }, + { "block quote", AccessibleRole::UNKNOWN }, + { "audio", AccessibleRole::UNKNOWN }, + { "video", AccessibleRole::UNKNOWN }, + { "definition", AccessibleRole::UNKNOWN }, + { "article", AccessibleRole::UNKNOWN }, + { "landmark", AccessibleRole::UNKNOWN }, + { "log", AccessibleRole::UNKNOWN }, + { "marquee", AccessibleRole::UNKNOWN }, + { "math", AccessibleRole::UNKNOWN }, + { "rating", AccessibleRole::UNKNOWN }, + { "timer", AccessibleRole::UNKNOWN }, + { "description list", AccessibleRole::UNKNOWN }, + { "description term", AccessibleRole::UNKNOWN }, + { "description value", AccessibleRole::UNKNOWN }, + { "static", AccessibleRole::STATIC }, + { "math fraction", AccessibleRole::UNKNOWN }, + { "math root", AccessibleRole::UNKNOWN }, + { "subscript", AccessibleRole::UNKNOWN }, + { "superscript", AccessibleRole::UNKNOWN }, + { "footnote", AccessibleRole::FOOTNOTE }, + }; + + auto it = aAtkRoleToAccessibleRole.find(roleName); + if (it == aAtkRoleToAccessibleRole.end()) + return AccessibleRole::UNKNOWN; + return it->second; + } +} + +VclPtr<vcl::Window> VclBuilder::insertObject(vcl::Window *pParent, const OString &rClass, + const OString &rID, stringmap &rProps, stringmap &rPango, stringmap &rAtk) +{ + VclPtr<vcl::Window> pCurrentChild; + + if (m_pParent && !isConsideredGtkPseudo(m_pParent) && !m_sID.isEmpty() && rID == m_sID) + { + pCurrentChild = m_pParent; + + //toplevels default to resizable and apparently you can't change them + //afterwards, so we need to wait until now before we can truly + //initialize the dialog. + if (pParent && pParent->IsSystemWindow()) + { + SystemWindow *pSysWin = static_cast<SystemWindow*>(pCurrentChild.get()); + pSysWin->doDeferredInit(extractDeferredBits(rProps)); + m_bToplevelHasDeferredInit = false; + } + else if (pParent && pParent->IsDockingWindow()) + { + DockingWindow *pDockWin = static_cast<DockingWindow*>(pCurrentChild.get()); + pDockWin->doDeferredInit(extractDeferredBits(rProps)); + m_bToplevelHasDeferredInit = false; + } + + if (pCurrentChild->GetHelpId().isEmpty()) + { + pCurrentChild->SetHelpId(m_sHelpRoot + m_sID); + SAL_INFO("vcl.builder", "for toplevel dialog " << this << " " << + rID << ", set helpid " << pCurrentChild->GetHelpId()); + } + m_bToplevelParentFound = true; + } + else + { + //if we're being inserting under a toplevel dialog whose init is + //deferred due to waiting to encounter it in this .ui, and it hasn't + //been seen yet, then make unattached widgets parent-less toplevels + if (pParent == m_pParent.get() && m_bToplevelHasDeferredInit) + pParent = nullptr; + pCurrentChild = makeObject(pParent, rClass, rID, rProps); + } + + if (pCurrentChild) + { + pCurrentChild->set_id(OStringToOUString(rID, RTL_TEXTENCODING_UTF8)); + if (pCurrentChild == m_pParent.get() && m_bToplevelHasDeferredProperties) + m_aDeferredProperties = rProps; + else + BuilderUtils::set_properties(pCurrentChild, rProps); + + // tdf#119827 handle size before scale so we can trivially + // scale on the current font size whether size is present + // or not. + VclBuilder::stringmap::iterator aSize = rPango.find(OString("size")); + if (aSize != rPango.end()) + { + pCurrentChild->set_font_attribute(aSize->first, aSize->second); + rPango.erase(aSize); + } + for (auto const& elem : rPango) + { + const OString &rKey = elem.first; + const OUString &rValue = elem.second; + pCurrentChild->set_font_attribute(rKey, rValue); + } + + m_pParserState->m_aAtkInfo[pCurrentChild] = rAtk; + } + + rProps.clear(); + rPango.clear(); + rAtk.clear(); + + if (!pCurrentChild) + { + bool bToolbarParent = (pParent && pParent->GetType() == WindowType::TOOLBOX); + pCurrentChild = (m_aChildren.empty() || bToolbarParent) ? pParent : m_aChildren.back().m_pWindow.get(); + } + return pCurrentChild; +} + +void VclBuilder::handleTabChild(vcl::Window *pParent, xmlreader::XmlReader &reader) +{ + TabControl *pTabControl = pParent && pParent->GetType() == WindowType::TABCONTROL ? + static_cast<TabControl*>(pParent) : nullptr; + + std::vector<OString> sIDs; + + int nLevel = 1; + stringmap aProperties; + stringmap aAtkProperties; + std::vector<vcl::EnumContext::Context> context; + + while(true) + { + xmlreader::Span name; + int nsId; + + xmlreader::XmlReader::Result res = reader.nextItem( + xmlreader::XmlReader::Text::NONE, &name, &nsId); + + if (res == xmlreader::XmlReader::Result::Begin) + { + ++nLevel; + if (name == "object") + { + while (reader.nextAttribute(&nsId, &name)) + { + if (name == "id") + { + name = reader.getAttributeValue(false); + OString sID(name.begin, name.length); + sal_Int32 nDelim = sID.indexOf(':'); + if (nDelim != -1) + { + OString sPattern = sID.copy(nDelim+1); + aProperties[OString("customproperty")] = OUString::fromUtf8(sPattern); + sID = sID.copy(0, nDelim); + } + sIDs.push_back(sID); + } + } + } + else if (name == "style") + { + int nPriority = 0; + context = handleStyle(reader, nPriority); + --nLevel; + } + else if (name == "property") + collectProperty(reader, aProperties); + else if (pTabControl && name == "child") + { + // just to collect the atk properties (if any) for the label + handleChild(nullptr, &aAtkProperties, reader); + --nLevel; + } + } + + if (res == xmlreader::XmlReader::Result::End) + --nLevel; + + if (!nLevel) + break; + + if (res == xmlreader::XmlReader::Result::Done) + break; + } + + if (!pParent) + return; + + VerticalTabControl *pVerticalTabControl = pParent->GetType() == WindowType::VERTICALTABCONTROL ? + static_cast<VerticalTabControl*>(pParent) : nullptr; + assert(pTabControl || pVerticalTabControl); + VclBuilder::stringmap::iterator aFind = aProperties.find(OString("label")); + if (aFind != aProperties.end()) + { + OUString sTooltip(extractTooltipText(aProperties)); + if (pTabControl) + { + sal_uInt16 nPageId = pTabControl->GetCurPageId(); + pTabControl->SetPageText(nPageId, aFind->second); + pTabControl->SetPageName(nPageId, sIDs.back()); + pTabControl->SetHelpText(nPageId, sTooltip); + if (!context.empty()) + { + TabPage* pPage = pTabControl->GetTabPage(nPageId); + pPage->SetContext(std::move(context)); + } + + for (auto const& prop : aAtkProperties) + { + const OString &rKey = prop.first; + const OUString &rValue = prop.second; + + if (rKey == "AtkObject::accessible-name") + pTabControl->SetAccessibleName(nPageId, rValue); + else if (rKey == "AtkObject::accessible-description") + pTabControl->SetAccessibleDescription(nPageId, rValue); + else + SAL_INFO("vcl.builder", "unhandled atk property: " << rKey); + } + + } + else + { + OUString sLabel(BuilderUtils::convertMnemonicMarkup(aFind->second)); + OUString sIconName(extractIconName(aProperties)); + pVerticalTabControl->InsertPage(sIDs.front(), sLabel, FixedImage::loadThemeImage(sIconName), sTooltip, + pVerticalTabControl->GetPageParent()->GetWindow(GetWindowType::LastChild)); + } + } + else + { + if (pTabControl) + pTabControl->RemovePage(pTabControl->GetCurPageId()); + } +} + +//so that tabbing between controls goes in a visually sensible sequence +//we sort these into a best-tab-order sequence +bool VclBuilder::sortIntoBestTabTraversalOrder::operator()(const vcl::Window *pA, const vcl::Window *pB) const +{ + //sort child order within parent list by grid position + sal_Int32 nTopA = pA->get_grid_top_attach(); + sal_Int32 nTopB = pB->get_grid_top_attach(); + if (nTopA < nTopB) + return true; + if (nTopA > nTopB) + return false; + sal_Int32 nLeftA = pA->get_grid_left_attach(); + sal_Int32 nLeftB = pB->get_grid_left_attach(); + if (nLeftA < nLeftB) + return true; + if (nLeftA > nLeftB) + return false; + //sort into two groups of pack start and pack end + VclPackType ePackA = pA->get_pack_type(); + VclPackType ePackB = pB->get_pack_type(); + if (ePackA < ePackB) + return true; + if (ePackA > ePackB) + return false; + bool bVerticalContainer = m_pBuilder->get_window_packing_data(pA->GetParent()).m_bVerticalOrient; + bool bPackA = pA->get_secondary(); + bool bPackB = pB->get_secondary(); + if (!bVerticalContainer) + { + //for horizontal boxes group secondaries before primaries + if (bPackA > bPackB) + return true; + if (bPackA < bPackB) + return false; + } + else + { + //for vertical boxes group secondaries after primaries + if (bPackA < bPackB) + return true; + if (bPackA > bPackB) + return false; + } + //honour relative box positions with pack group, (numerical order is reversed + //for VclPackType::End, they are packed from the end back, but here we need + //them in visual layout order so that tabbing works as expected) + sal_Int32 nPackA = m_pBuilder->get_window_packing_data(pA).m_nPosition; + sal_Int32 nPackB = m_pBuilder->get_window_packing_data(pB).m_nPosition; + if (nPackA < nPackB) + return ePackA == VclPackType::Start; + if (nPackA > nPackB) + return ePackA != VclPackType::Start; + //sort labels of Frames before body + if (pA->GetParent() == pB->GetParent()) + { + const VclFrame *pFrameParent = dynamic_cast<const VclFrame*>(pA->GetParent()); + if (pFrameParent) + { + const vcl::Window *pLabel = pFrameParent->get_label_widget(); + int nFramePosA = (pA == pLabel) ? 0 : 1; + int nFramePosB = (pB == pLabel) ? 0 : 1; + return nFramePosA < nFramePosB; + } + } + return false; +} + +void VclBuilder::handleChild(vcl::Window *pParent, stringmap* pAtkProps, xmlreader::XmlReader &reader) +{ + vcl::Window *pCurrentChild = nullptr; + + xmlreader::Span name; + int nsId; + OString sType, sInternalChild; + + while (reader.nextAttribute(&nsId, &name)) + { + if (name == "type") + { + name = reader.getAttributeValue(false); + sType = OString(name.begin, name.length); + } + else if (name == "internal-child") + { + name = reader.getAttributeValue(false); + sInternalChild = OString(name.begin, name.length); + } + } + + if (sType == "tab") + { + handleTabChild(pParent, reader); + return; + } + + int nLevel = 1; + while(true) + { + xmlreader::XmlReader::Result res = reader.nextItem( + xmlreader::XmlReader::Text::NONE, &name, &nsId); + + if (res == xmlreader::XmlReader::Result::Begin) + { + if (name == "object" || name == "placeholder") + { + pCurrentChild = handleObject(pParent, pAtkProps, reader).get(); + + bool bObjectInserted = pCurrentChild && pParent != pCurrentChild; + + if (bObjectInserted) + { + //Internal-children default in glade to not having their visible bits set + //even though they are visible (generally anyway) + if (!sInternalChild.isEmpty()) + pCurrentChild->Show(); + + //Select the first page if it's a notebook + if (pCurrentChild->GetType() == WindowType::TABCONTROL) + { + TabControl *pTabControl = static_cast<TabControl*>(pCurrentChild); + pTabControl->SetCurPageId(pTabControl->GetPageId(0)); + + //To-Do add reorder capability to the TabControl + } + else + { + // We want to sort labels before contents of frames + // for keyboard traversal, especially if there + // are multiple widgets using the same mnemonic + if (sType == "label") + { + if (VclFrame *pFrameParent = dynamic_cast<VclFrame*>(pParent)) + pFrameParent->designate_label(pCurrentChild); + } + if (sInternalChild.startsWith("vbox") || sInternalChild.startsWith("messagedialog-vbox")) + { + if (Dialog *pBoxParent = dynamic_cast<Dialog*>(pParent)) + pBoxParent->set_content_area(static_cast<VclBox*>(pCurrentChild)); // FIXME-VCLPTR + } + else if (sInternalChild.startsWith("action_area") || sInternalChild.startsWith("messagedialog-action_area")) + { + vcl::Window *pContentArea = pCurrentChild->GetParent(); + if (Dialog *pBoxParent = dynamic_cast<Dialog*>(pContentArea ? pContentArea->GetParent() : nullptr)) + { + pBoxParent->set_action_area(static_cast<VclButtonBox*>(pCurrentChild)); // FIXME-VCLPTR + } + } + + bool bIsButtonBox = dynamic_cast<VclButtonBox*>(pCurrentChild) != nullptr; + + //To-Do make reorder a virtual in Window, move this foo + //there and see above + std::vector<vcl::Window*> aChilds; + for (vcl::Window* pChild = pCurrentChild->GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (bIsButtonBox) + { + if (PushButton* pPushButton = dynamic_cast<PushButton*>(pChild)) + pPushButton->setAction(true); + } + + aChilds.push_back(pChild); + } + + //sort child order within parent so that tabbing + //between controls goes in a visually sensible sequence + std::stable_sort(aChilds.begin(), aChilds.end(), sortIntoBestTabTraversalOrder(this)); + BuilderUtils::reorderWithinParent(aChilds, bIsButtonBox); + } + } + } + else if (name == "packing") + { + handlePacking(pCurrentChild, pParent, reader); + } + else if (name == "interface") + { + while (reader.nextAttribute(&nsId, &name)) + { + if (name == "domain") + { + name = reader.getAttributeValue(false); + sType = OString(name.begin, name.length); + m_pParserState->m_aResLocale = Translate::Create(sType); + } + } + ++nLevel; + } + else + ++nLevel; + } + + if (res == xmlreader::XmlReader::Result::End) + --nLevel; + + if (!nLevel) + break; + + if (res == xmlreader::XmlReader::Result::Done) + break; + } +} + +void VclBuilder::collectPangoAttribute(xmlreader::XmlReader &reader, stringmap &rMap) +{ + xmlreader::Span span; + int nsId; + + OString sProperty; + OString sValue; + + while (reader.nextAttribute(&nsId, &span)) + { + if (span == "name") + { + span = reader.getAttributeValue(false); + sProperty = OString(span.begin, span.length); + } + else if (span == "value") + { + span = reader.getAttributeValue(false); + sValue = OString(span.begin, span.length); + } + } + + if (!sProperty.isEmpty()) + rMap[sProperty] = OUString::fromUtf8(sValue); +} + +void VclBuilder::collectAtkRelationAttribute(xmlreader::XmlReader &reader, stringmap &rMap) +{ + xmlreader::Span span; + int nsId; + + OString sProperty; + OString sValue; + + while (reader.nextAttribute(&nsId, &span)) + { + if (span == "type") + { + span = reader.getAttributeValue(false); + sProperty = OString(span.begin, span.length); + } + else if (span == "target") + { + span = reader.getAttributeValue(false); + sValue = OString(span.begin, span.length); + sal_Int32 nDelim = sValue.indexOf(':'); + if (nDelim != -1) + sValue = sValue.copy(0, nDelim); + } + } + + if (!sProperty.isEmpty()) + rMap[sProperty] = OUString::fromUtf8(sValue); +} + +void VclBuilder::collectAtkRoleAttribute(xmlreader::XmlReader &reader, stringmap &rMap) +{ + xmlreader::Span span; + int nsId; + + OString sProperty; + + while (reader.nextAttribute(&nsId, &span)) + { + if (span == "type") + { + span = reader.getAttributeValue(false); + sProperty = OString(span.begin, span.length); + } + } + + if (!sProperty.isEmpty()) + rMap["role"] = OUString::fromUtf8(sProperty); +} + +void VclBuilder::handleRow(xmlreader::XmlReader &reader, const OString &rID) +{ + int nLevel = 1; + + ListStore::row aRow; + + while(true) + { + xmlreader::Span name; + int nsId; + + xmlreader::XmlReader::Result res = reader.nextItem( + xmlreader::XmlReader::Text::NONE, &name, &nsId); + + if (res == xmlreader::XmlReader::Result::Done) + break; + + if (res == xmlreader::XmlReader::Result::Begin) + { + ++nLevel; + if (name == "col") + { + bool bTranslated = false; + sal_uInt32 nId = 0; + OString sContext; + + while (reader.nextAttribute(&nsId, &name)) + { + if (name == "id") + { + name = reader.getAttributeValue(false); + nId = OString(name.begin, name.length).toUInt32(); + } + else if (nId == 0 && name == "translatable" && reader.getAttributeValue(false) == "yes") + { + bTranslated = true; + } + else if (name == "context") + { + name = reader.getAttributeValue(false); + sContext = OString(name.begin, name.length); + } + } + + (void)reader.nextItem( + xmlreader::XmlReader::Text::Raw, &name, &nsId); + + OString sValue(name.begin, name.length); + OUString sFinalValue; + if (bTranslated) + { + sFinalValue = Translate::get(TranslateId{sContext.getStr(), sValue.getStr()}, m_pParserState->m_aResLocale); + } + else + sFinalValue = OUString::fromUtf8(sValue); + + + if (aRow.size() < nId+1) + aRow.resize(nId+1); + aRow[nId] = sFinalValue; + } + } + + if (res == xmlreader::XmlReader::Result::End) + { + --nLevel; + } + + if (!nLevel) + break; + } + + m_pParserState->m_aModels[rID].m_aEntries.push_back(aRow); +} + +void VclBuilder::handleListStore(xmlreader::XmlReader &reader, const OString &rID, std::string_view rClass) +{ + int nLevel = 1; + + while(true) + { + xmlreader::Span name; + int nsId; + + xmlreader::XmlReader::Result res = reader.nextItem( + xmlreader::XmlReader::Text::NONE, &name, &nsId); + + if (res == xmlreader::XmlReader::Result::Done) + break; + + if (res == xmlreader::XmlReader::Result::Begin) + { + if (name == "row") + { + bool bNotTreeStore = rClass != "GtkTreeStore"; + if (bNotTreeStore) + handleRow(reader, rID); + assert(bNotTreeStore && "gtk, as the time of writing, doesn't support data in GtkTreeStore serialization"); + } + else + ++nLevel; + } + + if (res == xmlreader::XmlReader::Result::End) + { + --nLevel; + } + + if (!nLevel) + break; + } +} + +VclBuilder::stringmap VclBuilder::handleAtkObject(xmlreader::XmlReader &reader) const +{ + int nLevel = 1; + + stringmap aProperties; + + while (true) + { + xmlreader::Span name; + int nsId; + + xmlreader::XmlReader::Result res = reader.nextItem( + xmlreader::XmlReader::Text::NONE, &name, &nsId); + + if (res == xmlreader::XmlReader::Result::Done) + break; + + if (res == xmlreader::XmlReader::Result::Begin) + { + ++nLevel; + if (name == "property") + collectProperty(reader, aProperties); + } + + if (res == xmlreader::XmlReader::Result::End) + { + --nLevel; + } + + if (!nLevel) + break; + } + + return aProperties; +} + +void VclBuilder::applyAtkProperties(vcl::Window *pWindow, const stringmap& rProperties) +{ + assert(pWindow); + for (auto const& prop : rProperties) + { + const OString &rKey = prop.first; + const OUString &rValue = prop.second; + + if (pWindow && rKey.match("AtkObject::")) + pWindow->set_property(rKey.copy(RTL_CONSTASCII_LENGTH("AtkObject::")), rValue); + else + SAL_WARN("vcl.builder", "unhandled atk prop: " << rKey); + } +} + +std::vector<ComboBoxTextItem> VclBuilder::handleItems(xmlreader::XmlReader &reader) const +{ + int nLevel = 1; + + std::vector<ComboBoxTextItem> aItems; + + while(true) + { + xmlreader::Span name; + int nsId; + + xmlreader::XmlReader::Result res = reader.nextItem( + xmlreader::XmlReader::Text::NONE, &name, &nsId); + + if (res == xmlreader::XmlReader::Result::Done) + break; + + if (res == xmlreader::XmlReader::Result::Begin) + { + ++nLevel; + if (name == "item") + { + bool bTranslated = false; + OString sContext, sId; + + while (reader.nextAttribute(&nsId, &name)) + { + if (name == "translatable" && reader.getAttributeValue(false) == "yes") + { + bTranslated = true; + } + else if (name == "context") + { + name = reader.getAttributeValue(false); + sContext = OString(name.begin, name.length); + } + else if (name == "id") + { + name = reader.getAttributeValue(false); + sId = OString(name.begin, name.length); + } + } + + (void)reader.nextItem( + xmlreader::XmlReader::Text::Raw, &name, &nsId); + + OString sValue(name.begin, name.length); + OUString sFinalValue; + if (bTranslated) + { + sFinalValue = Translate::get(TranslateId{sContext.getStr(), sValue.getStr()}, m_pParserState->m_aResLocale); + } + else + sFinalValue = OUString::fromUtf8(sValue); + + if (m_pStringReplace) + sFinalValue = (*m_pStringReplace)(sFinalValue); + + aItems.emplace_back(sFinalValue, sId); + } + } + + if (res == xmlreader::XmlReader::Result::End) + { + --nLevel; + } + + if (!nLevel) + break; + } + + return aItems; +} + +VclPtr<Menu> VclBuilder::handleMenu(xmlreader::XmlReader &reader, const OString &rID, bool bMenuBar) +{ + VclPtr<Menu> pCurrentMenu; + if (bMenuBar) + pCurrentMenu = VclPtr<MenuBar>::Create(); + else + pCurrentMenu = VclPtr<PopupMenu>::Create(); + + pCurrentMenu->set_id(OStringToOUString(rID, RTL_TEXTENCODING_UTF8)); + + int nLevel = 1; + + stringmap aProperties; + + while(true) + { + xmlreader::Span name; + int nsId; + + xmlreader::XmlReader::Result res = reader.nextItem( + xmlreader::XmlReader::Text::NONE, &name, &nsId); + + if (res == xmlreader::XmlReader::Result::Done) + break; + + if (res == xmlreader::XmlReader::Result::Begin) + { + if (name == "child") + { + handleMenuChild(pCurrentMenu, reader); + } + else + { + ++nLevel; + if (name == "property") + collectProperty(reader, aProperties); + } + } + + if (res == xmlreader::XmlReader::Result::End) + { + --nLevel; + } + + if (!nLevel) + break; + } + + m_aMenus.emplace_back(rID, pCurrentMenu); + + return pCurrentMenu; +} + +void VclBuilder::handleMenuChild(Menu *pParent, xmlreader::XmlReader &reader) +{ + xmlreader::Span name; + int nsId; + + int nLevel = 1; + while(true) + { + xmlreader::XmlReader::Result res = reader.nextItem( + xmlreader::XmlReader::Text::NONE, &name, &nsId); + + if (res == xmlreader::XmlReader::Result::Begin) + { + if (name == "object" || name == "placeholder") + { + handleMenuObject(pParent, reader); + } + else + ++nLevel; + } + + if (res == xmlreader::XmlReader::Result::End) + --nLevel; + + if (!nLevel) + break; + + if (res == xmlreader::XmlReader::Result::Done) + break; + } +} + +void VclBuilder::handleMenuObject(Menu *pParent, xmlreader::XmlReader &reader) +{ + OString sClass; + OString sID; + OUString sCustomProperty; + PopupMenu *pSubMenu = nullptr; + + xmlreader::Span name; + int nsId; + + while (reader.nextAttribute(&nsId, &name)) + { + if (name == "class") + { + name = reader.getAttributeValue(false); + sClass = OString(name.begin, name.length); + } + else if (name == "id") + { + name = reader.getAttributeValue(false); + sID = OString(name.begin, name.length); + if (m_bLegacy) + { + sal_Int32 nDelim = sID.indexOf(':'); + if (nDelim != -1) + { + sCustomProperty = OUString::fromUtf8(sID.subView(nDelim+1)); + sID = sID.copy(0, nDelim); + } + } + } + } + + int nLevel = 1; + + stringmap aProperties; + stringmap aAtkProperties; + accelmap aAccelerators; + + if (!sCustomProperty.isEmpty()) + aProperties[OString("customproperty")] = sCustomProperty; + + while(true) + { + xmlreader::XmlReader::Result res = reader.nextItem( + xmlreader::XmlReader::Text::NONE, &name, &nsId); + + if (res == xmlreader::XmlReader::Result::Done) + break; + + if (res == xmlreader::XmlReader::Result::Begin) + { + if (name == "child") + { + size_t nChildMenuIdx = m_aMenus.size(); + handleChild(nullptr, &aAtkProperties, reader); + bool bSubMenuInserted = m_aMenus.size() > nChildMenuIdx; + if (bSubMenuInserted) + pSubMenu = dynamic_cast<PopupMenu*>(m_aMenus[nChildMenuIdx].m_pMenu.get()); + } + else + { + ++nLevel; + if (name == "property") + collectProperty(reader, aProperties); + else if (name == "accelerator") + collectAccelerator(reader, aAccelerators); + } + } + + if (res == xmlreader::XmlReader::Result::End) + { + --nLevel; + } + + if (!nLevel) + break; + } + + insertMenuObject(pParent, pSubMenu, sClass, sID, aProperties, aAtkProperties, aAccelerators); +} + +void VclBuilder::handleSizeGroup(xmlreader::XmlReader &reader) +{ + m_pParserState->m_aSizeGroups.emplace_back(); + SizeGroup &rSizeGroup = m_pParserState->m_aSizeGroups.back(); + + int nLevel = 1; + + while(true) + { + xmlreader::Span name; + int nsId; + + xmlreader::XmlReader::Result res = reader.nextItem( + xmlreader::XmlReader::Text::NONE, &name, &nsId); + + if (res == xmlreader::XmlReader::Result::Done) + break; + + if (res == xmlreader::XmlReader::Result::Begin) + { + ++nLevel; + if (name == "widget") + { + while (reader.nextAttribute(&nsId, &name)) + { + if (name == "name") + { + name = reader.getAttributeValue(false); + OString sWidget(name.begin, name.length); + sal_Int32 nDelim = sWidget.indexOf(':'); + if (nDelim != -1) + sWidget = sWidget.copy(0, nDelim); + rSizeGroup.m_aWidgets.push_back(sWidget); + } + } + } + else + { + if (name == "property") + collectProperty(reader, rSizeGroup.m_aProperties); + } + } + + if (res == xmlreader::XmlReader::Result::End) + { + --nLevel; + } + + if (!nLevel) + break; + } +} + +namespace +{ + vcl::KeyCode makeKeyCode(const std::pair<OString,OString> &rKey) + { + bool bShift = rKey.second.indexOf("GDK_SHIFT_MASK") != -1; + bool bMod1 = rKey.second.indexOf("GDK_CONTROL_MASK") != -1; + bool bMod2 = rKey.second.indexOf("GDK_ALT_MASK") != -1; + bool bMod3 = rKey.second.indexOf("GDK_MOD2_MASK") != -1; + + if (rKey.first == "Insert") + return vcl::KeyCode(KEY_INSERT, bShift, bMod1, bMod2, bMod3); + else if (rKey.first == "Delete") + return vcl::KeyCode(KEY_DELETE, bShift, bMod1, bMod2, bMod3); + else if (rKey.first == "Return") + return vcl::KeyCode(KEY_RETURN, bShift, bMod1, bMod2, bMod3); + else if (rKey.first == "Up") + return vcl::KeyCode(KEY_UP, bShift, bMod1, bMod2, bMod3); + else if (rKey.first == "Down") + return vcl::KeyCode(KEY_DOWN, bShift, bMod1, bMod2, bMod3); + else if (rKey.first == "Left") + return vcl::KeyCode(KEY_LEFT, bShift, bMod1, bMod2, bMod3); + else if (rKey.first == "Right") + return vcl::KeyCode(KEY_RIGHT, bShift, bMod1, bMod2, bMod3); + else if (rKey.first == "asterisk") + return vcl::KeyCode(KEY_MULTIPLY, bShift, bMod1, bMod2, bMod3); + else if (rKey.first.getLength() > 1 && rKey.first[0] == 'F') + { + sal_uInt32 nIndex = o3tl::toUInt32(rKey.first.subView(1)); + assert(nIndex >= 1 && nIndex <= 26); + return vcl::KeyCode(KEY_F1 + nIndex - 1, bShift, bMod1, bMod2, bMod3); + } + + assert (rKey.first.getLength() == 1); + char cChar = rKey.first.toChar(); + + if (cChar >= 'a' && cChar <= 'z') + return vcl::KeyCode(KEY_A + (cChar - 'a'), bShift, bMod1, bMod2, bMod3); + else if (cChar >= 'A' && cChar <= 'Z') + return vcl::KeyCode(KEY_A + (cChar - 'A'), bShift, bMod1, bMod2, bMod3); + else if (cChar >= '0' && cChar <= '9') + return vcl::KeyCode(KEY_0 + (cChar - 'A'), bShift, bMod1, bMod2, bMod3); + + return vcl::KeyCode(cChar, bShift, bMod1, bMod2, bMod3); + } +} + +void VclBuilder::insertMenuObject(Menu *pParent, PopupMenu *pSubMenu, const OString &rClass, const OString &rID, + stringmap &rProps, stringmap &rAtkProps, accelmap &rAccels) +{ + sal_uInt16 nOldCount = pParent->GetItemCount(); + sal_uInt16 nNewId = ++m_pParserState->m_nLastMenuItemId; + + if(rClass == "NotebookBarAddonsMenuMergePoint") + { + NotebookBarAddonsMerger::MergeNotebookBarMenuAddons(pParent, nNewId, rID, *m_pNotebookBarAddonsItem); + m_pParserState->m_nLastMenuItemId = pParent->GetItemCount(); + } + else if (rClass == "GtkMenuItem") + { + OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps))); + OUString aCommand(extractActionName(rProps)); + pParent->InsertItem(nNewId, sLabel, MenuItemBits::NONE , rID); + pParent->SetItemCommand(nNewId, aCommand); + if (pSubMenu) + pParent->SetPopupMenu(nNewId, pSubMenu); + } + else if (rClass == "GtkCheckMenuItem") + { + OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps))); + OUString aCommand(extractActionName(rProps)); + pParent->InsertItem(nNewId, sLabel, MenuItemBits::CHECKABLE, rID); + pParent->SetItemCommand(nNewId, aCommand); + } + else if (rClass == "GtkRadioMenuItem") + { + OUString sLabel(BuilderUtils::convertMnemonicMarkup(extractLabel(rProps))); + OUString aCommand(extractActionName(rProps)); + pParent->InsertItem(nNewId, sLabel, MenuItemBits::AUTOCHECK | MenuItemBits::RADIOCHECK, rID); + pParent->SetItemCommand(nNewId, aCommand); + } + else if (rClass == "GtkSeparatorMenuItem") + { + pParent->InsertSeparator(rID); + } + + SAL_WARN_IF(nOldCount == pParent->GetItemCount(), "vcl.builder", "probably need to implement " << rClass); + + if (nOldCount != pParent->GetItemCount()) + { + pParent->SetHelpId(nNewId, m_sHelpRoot + rID); + if (!extractVisible(rProps)) + pParent->HideItem(nNewId); + + for (auto const& prop : rProps) + { + const OString &rKey = prop.first; + const OUString &rValue = prop.second; + + if (rKey == "tooltip-markup") + pParent->SetTipHelpText(nNewId, rValue); + else if (rKey == "tooltip-text") + pParent->SetTipHelpText(nNewId, rValue); + else + SAL_INFO("vcl.builder", "unhandled property: " << rKey); + } + + for (auto const& prop : rAtkProps) + { + const OString &rKey = prop.first; + const OUString &rValue = prop.second; + + if (rKey == "AtkObject::accessible-name") + pParent->SetAccessibleName(nNewId, rValue); + else if (rKey == "AtkObject::accessible-description") + pParent->SetAccessibleDescription(nNewId, rValue); + else + SAL_INFO("vcl.builder", "unhandled atk property: " << rKey); + } + + for (auto const& accel : rAccels) + { + const OString &rSignal = accel.first; + const auto &rValue = accel.second; + + if (rSignal == "activate") + pParent->SetAccelKey(nNewId, makeKeyCode(rValue)); + else + SAL_INFO("vcl.builder", "unhandled accelerator for: " << rSignal); + } + } + + rProps.clear(); +} + +/// Insert items to a ComboBox or a ListBox. +/// They have no common ancestor that would have 'InsertEntry()', so use a template. +template<typename T> static bool insertItems(vcl::Window *pWindow, VclBuilder::stringmap &rMap, + std::vector<std::unique_ptr<OUString>>& rUserData, + const std::vector<ComboBoxTextItem> &rItems) +{ + T *pContainer = dynamic_cast<T*>(pWindow); + if (!pContainer) + return false; + + sal_uInt16 nActiveId = extractActive(rMap); + for (auto const& item : rItems) + { + sal_Int32 nPos = pContainer->InsertEntry(item.m_sItem); + if (!item.m_sId.isEmpty()) + { + rUserData.emplace_back(std::make_unique<OUString>(OUString::fromUtf8(item.m_sId))); + pContainer->SetEntryData(nPos, rUserData.back().get()); + } + } + if (nActiveId < rItems.size()) + pContainer->SelectEntryPos(nActiveId); + + return true; +} + +VclPtr<vcl::Window> VclBuilder::handleObject(vcl::Window *pParent, stringmap *pAtkProps, xmlreader::XmlReader &reader) +{ + OString sClass; + OString sID; + OUString sCustomProperty; + + xmlreader::Span name; + int nsId; + + while (reader.nextAttribute(&nsId, &name)) + { + if (name == "class") + { + name = reader.getAttributeValue(false); + sClass = OString(name.begin, name.length); + } + else if (name == "id") + { + name = reader.getAttributeValue(false); + sID = OString(name.begin, name.length); + if (m_bLegacy) + { + sal_Int32 nDelim = sID.indexOf(':'); + if (nDelim != -1) + { + sCustomProperty = OUString::fromUtf8(sID.subView(nDelim+1)); + sID = sID.copy(0, nDelim); + } + } + } + } + + if (sClass == "GtkListStore" || sClass == "GtkTreeStore") + { + handleListStore(reader, sID, sClass); + return nullptr; + } + else if (sClass == "GtkMenu") + { + handleMenu(reader, sID, false); + return nullptr; + } + else if (sClass == "GtkMenuBar") + { + VclPtr<Menu> xMenu = handleMenu(reader, sID, true); + if (SystemWindow* pTopLevel = pParent ? pParent->GetSystemWindow() : nullptr) + pTopLevel->SetMenuBar(dynamic_cast<MenuBar*>(xMenu.get())); + return nullptr; + } + else if (sClass == "GtkSizeGroup") + { + handleSizeGroup(reader); + return nullptr; + } + else if (sClass == "AtkObject") + { + assert((pParent || pAtkProps) && "must have one set"); + assert(!(pParent && pAtkProps) && "must not have both"); + auto aAtkProperties = handleAtkObject(reader); + if (pParent) + applyAtkProperties(pParent, aAtkProperties); + if (pAtkProps) + *pAtkProps = aAtkProperties; + return nullptr; + } + + int nLevel = 1; + + stringmap aProperties, aPangoAttributes; + stringmap aAtkAttributes; + std::vector<ComboBoxTextItem> aItems; + + if (!sCustomProperty.isEmpty()) + aProperties[OString("customproperty")] = sCustomProperty; + + VclPtr<vcl::Window> pCurrentChild; + while(true) + { + xmlreader::XmlReader::Result res = reader.nextItem( + xmlreader::XmlReader::Text::NONE, &name, &nsId); + + if (res == xmlreader::XmlReader::Result::Done) + break; + + if (res == xmlreader::XmlReader::Result::Begin) + { + if (name == "child") + { + if (!pCurrentChild) + { + pCurrentChild = insertObject(pParent, sClass, sID, + aProperties, aPangoAttributes, aAtkAttributes); + } + handleChild(pCurrentChild, nullptr, reader); + } + else if (name == "items") + aItems = handleItems(reader); + else if (name == "style") + { + int nPriority = 0; + std::vector<vcl::EnumContext::Context> aContext = handleStyle(reader, nPriority); + if (nPriority != 0) + { + vcl::IPrioritable* pPrioritable = dynamic_cast<vcl::IPrioritable*>(pCurrentChild.get()); + SAL_WARN_IF(!pPrioritable, "vcl", "priority set for not supported item"); + if (pPrioritable) + pPrioritable->SetPriority(nPriority); + } + if (!aContext.empty()) + { + vcl::IContext* pContextControl = dynamic_cast<vcl::IContext*>(pCurrentChild.get()); + SAL_WARN_IF(!pContextControl, "vcl", "context set for not supported item"); + if (pContextControl) + pContextControl->SetContext(std::move(aContext)); + } + } + else + { + ++nLevel; + if (name == "property") + collectProperty(reader, aProperties); + else if (name == "attribute") + collectPangoAttribute(reader, aPangoAttributes); + else if (name == "relation") + collectAtkRelationAttribute(reader, aAtkAttributes); + else if (name == "role") + collectAtkRoleAttribute(reader, aAtkAttributes); + else if (name == "action-widget") + handleActionWidget(reader); + } + } + + if (res == xmlreader::XmlReader::Result::End) + { + --nLevel; + } + + if (!nLevel) + break; + } + + if (sClass == "GtkAdjustment") + { + m_pParserState->m_aAdjustments[sID] = aProperties; + return nullptr; + } + else if (sClass == "GtkTextBuffer") + { + m_pParserState->m_aTextBuffers[sID] = aProperties; + return nullptr; + } + + if (!pCurrentChild) + { + pCurrentChild = insertObject(pParent, sClass, sID, aProperties, + aPangoAttributes, aAtkAttributes); + } + + if (!aItems.empty()) + { + // try to fill-in the items + if (!insertItems<ComboBox>(pCurrentChild, aProperties, m_aUserData, aItems)) + insertItems<ListBox>(pCurrentChild, aProperties, m_aUserData, aItems); + } + + return pCurrentChild; +} + +void VclBuilder::handlePacking(vcl::Window *pCurrent, vcl::Window *pParent, xmlreader::XmlReader &reader) +{ + xmlreader::Span name; + int nsId; + + int nLevel = 1; + + while(true) + { + xmlreader::XmlReader::Result res = reader.nextItem( + xmlreader::XmlReader::Text::NONE, &name, &nsId); + + if (res == xmlreader::XmlReader::Result::Done) + break; + + if (res == xmlreader::XmlReader::Result::Begin) + { + ++nLevel; + if (name == "property") + applyPackingProperty(pCurrent, pParent, reader); + } + + if (res == xmlreader::XmlReader::Result::End) + { + --nLevel; + } + + if (!nLevel) + break; + } +} + +void VclBuilder::applyPackingProperty(vcl::Window *pCurrent, + vcl::Window *pParent, + xmlreader::XmlReader &reader) +{ + if (!pCurrent) + return; + + //ToolBoxItems are not true widgets just elements + //of the ToolBox itself + ToolBox *pToolBoxParent = nullptr; + if (pCurrent == pParent) + pToolBoxParent = dynamic_cast<ToolBox*>(pParent); + + xmlreader::Span name; + int nsId; + + if (pCurrent->GetType() == WindowType::SCROLLWINDOW) + { + auto aFind = m_pParserState->m_aRedundantParentWidgets.find(VclPtr<vcl::Window>(pCurrent)); + if (aFind != m_pParserState->m_aRedundantParentWidgets.end()) + { + pCurrent = aFind->second; + assert(pCurrent); + } + } + + while (reader.nextAttribute(&nsId, &name)) + { + if (name == "name") + { + name = reader.getAttributeValue(false); + OString sKey(name.begin, name.length); + sKey = sKey.replace('_', '-'); + (void)reader.nextItem( + xmlreader::XmlReader::Text::Raw, &name, &nsId); + OString sValue(name.begin, name.length); + + if (sKey == "expand" || sKey == "resize") + { + bool bTrue = (!sValue.isEmpty() && (sValue[0] == 't' || sValue[0] == 'T' || sValue[0] == '1')); + if (pToolBoxParent) + pToolBoxParent->SetItemExpand(m_pParserState->m_nLastToolbarId, bTrue); + else + pCurrent->set_expand(bTrue); + continue; + } + + if (pToolBoxParent) + continue; + + if (sKey == "fill") + { + bool bTrue = (!sValue.isEmpty() && (sValue[0] == 't' || sValue[0] == 'T' || sValue[0] == '1')); + pCurrent->set_fill(bTrue); + } + else if (sKey == "pack-type") + { + VclPackType ePackType = (!sValue.isEmpty() && (sValue[0] == 'e' || sValue[0] == 'E')) ? VclPackType::End : VclPackType::Start; + pCurrent->set_pack_type(ePackType); + } + else if (sKey == "left-attach") + { + pCurrent->set_grid_left_attach(sValue.toInt32()); + } + else if (sKey == "top-attach") + { + pCurrent->set_grid_top_attach(sValue.toInt32()); + } + else if (sKey == "width") + { + pCurrent->set_grid_width(sValue.toInt32()); + } + else if (sKey == "height") + { + pCurrent->set_grid_height(sValue.toInt32()); + } + else if (sKey == "padding") + { + pCurrent->set_padding(sValue.toInt32()); + } + else if (sKey == "position") + { + set_window_packing_position(pCurrent, sValue.toInt32()); + } + else if (sKey == "secondary") + { + pCurrent->set_secondary(toBool(sValue)); + } + else if (sKey == "non-homogeneous") + { + pCurrent->set_non_homogeneous(toBool(sValue)); + } + else if (sKey == "homogeneous") + { + pCurrent->set_non_homogeneous(!toBool(sValue)); + } + else + { + SAL_WARN_IF(sKey != "shrink", "vcl.builder", "unknown packing: " << sKey); + } + } + } +} + +std::vector<vcl::EnumContext::Context> VclBuilder::handleStyle(xmlreader::XmlReader &reader, int &nPriority) +{ + std::vector<vcl::EnumContext::Context> aContext; + + xmlreader::Span name; + int nsId; + + int nLevel = 1; + + while(true) + { + xmlreader::XmlReader::Result res = reader.nextItem( + xmlreader::XmlReader::Text::NONE, &name, &nsId); + + if (res == xmlreader::XmlReader::Result::Done) + break; + + if (res == xmlreader::XmlReader::Result::Begin) + { + ++nLevel; + if (name == "class") + { + OString classStyle = getStyleClass(reader); + + if (classStyle.startsWith("context-")) + { + OString sContext = classStyle.copy(classStyle.indexOf('-') + 1); + OUString sContext2(sContext.getStr(), sContext.getLength(), RTL_TEXTENCODING_UTF8); + aContext.push_back(vcl::EnumContext::GetContextEnum(sContext2)); + } + else if (classStyle.startsWith("priority-")) + { + OString aPriority = classStyle.copy(classStyle.indexOf('-') + 1); + OUString aPriority2(aPriority.getStr(), aPriority.getLength(), RTL_TEXTENCODING_UTF8); + nPriority = aPriority2.toInt32(); + } + else if (classStyle != "small-button" && classStyle != "destructive-action" && classStyle != "suggested-action") + { + SAL_WARN("vcl.builder", "unknown class: " << classStyle); + } + } + } + + if (res == xmlreader::XmlReader::Result::End) + { + --nLevel; + } + + if (!nLevel) + break; + } + + return aContext; +} + +OString VclBuilder::getStyleClass(xmlreader::XmlReader &reader) +{ + xmlreader::Span name; + int nsId; + OString aRet; + + while (reader.nextAttribute(&nsId, &name)) + { + if (name == "name") + { + name = reader.getAttributeValue(false); + aRet = OString (name.begin, name.length); + } + } + + return aRet; +} + +void VclBuilder::collectProperty(xmlreader::XmlReader &reader, stringmap &rMap) const +{ + xmlreader::Span name; + int nsId; + + OString sProperty, sContext; + + bool bTranslated = false; + + while (reader.nextAttribute(&nsId, &name)) + { + if (name == "name") + { + name = reader.getAttributeValue(false); + sProperty = OString(name.begin, name.length); + } + else if (name == "context") + { + name = reader.getAttributeValue(false); + sContext = OString(name.begin, name.length); + } + else if (name == "translatable" && reader.getAttributeValue(false) == "yes") + { + bTranslated = true; + } + } + + (void)reader.nextItem(xmlreader::XmlReader::Text::Raw, &name, &nsId); + OString sValue(name.begin, name.length); + OUString sFinalValue; + if (bTranslated) + { + sFinalValue = Translate::get(TranslateId{sContext.getStr(), sValue.getStr()}, m_pParserState->m_aResLocale); + } + else + sFinalValue = OUString::fromUtf8(sValue); + + if (!sProperty.isEmpty()) + { + sProperty = sProperty.replace('_', '-'); + if (m_pStringReplace) + sFinalValue = (*m_pStringReplace)(sFinalValue); + rMap[sProperty] = sFinalValue; + } +} + +void VclBuilder::handleActionWidget(xmlreader::XmlReader &reader) +{ + xmlreader::Span name; + int nsId; + + OString sResponse; + + while (reader.nextAttribute(&nsId, &name)) + { + if (name == "response") + { + name = reader.getAttributeValue(false); + sResponse = OString(name.begin, name.length); + } + } + + (void)reader.nextItem(xmlreader::XmlReader::Text::Raw, &name, &nsId); + OString sID(name.begin, name.length); + sal_Int32 nDelim = sID.indexOf(':'); + if (nDelim != -1) + sID = sID.copy(0, nDelim); + set_response(sID, sResponse.toInt32()); +} + +void VclBuilder::collectAccelerator(xmlreader::XmlReader &reader, accelmap &rMap) +{ + xmlreader::Span name; + int nsId; + + OString sProperty; + OString sValue; + OString sModifiers; + + while (reader.nextAttribute(&nsId, &name)) + { + if (name == "key") + { + name = reader.getAttributeValue(false); + sValue = OString(name.begin, name.length); + } + else if (name == "signal") + { + name = reader.getAttributeValue(false); + sProperty = OString(name.begin, name.length); + } + else if (name == "modifiers") + { + name = reader.getAttributeValue(false); + sModifiers = OString(name.begin, name.length); + } + } + + if (!sProperty.isEmpty() && !sValue.isEmpty()) + { + rMap[sProperty] = std::make_pair(sValue, sModifiers); + } +} + +vcl::Window *VclBuilder::get_widget_root() +{ + return m_aChildren.empty() ? nullptr : m_aChildren[0].m_pWindow.get(); +} + +vcl::Window *VclBuilder::get_by_name(std::string_view sID) +{ + for (auto const& child : m_aChildren) + { + if (child.m_sID == sID) + return child.m_pWindow; + } + + return nullptr; +} + +PopupMenu *VclBuilder::get_menu(std::string_view sID) +{ + for (auto const& menu : m_aMenus) + { + if (menu.m_sID == sID) + return dynamic_cast<PopupMenu*>(menu.m_pMenu.get()); + } + + return nullptr; +} + +void VclBuilder::set_response(std::string_view sID, short nResponse) +{ + switch (nResponse) + { + case -5: + nResponse = RET_OK; + break; + case -6: + nResponse = RET_CANCEL; + break; + case -7: + nResponse = RET_CLOSE; + break; + case -8: + nResponse = RET_YES; + break; + case -9: + nResponse = RET_NO; + break; + case -11: + nResponse = RET_HELP; + break; + default: + assert(nResponse >= 100 && "keep non-canned responses in range 100+ to avoid collision with vcl RET_*"); + break; + } + + for (const auto & child : m_aChildren) + { + if (child.m_sID == sID) + { + PushButton* pPushButton = dynamic_cast<PushButton*>(child.m_pWindow.get()); + assert(pPushButton); + Dialog* pDialog = pPushButton->GetParentDialog(); + assert(pDialog); + pDialog->add_button(pPushButton, nResponse, false); + return; + } + } + + assert(false); +} + +void VclBuilder::delete_by_name(const OString& sID) +{ + auto aI = std::find_if(m_aChildren.begin(), m_aChildren.end(), + [&sID](WinAndId& rItem) { return rItem.m_sID == sID; }); + if (aI != m_aChildren.end()) + { + aI->m_pWindow.disposeAndClear(); + m_aChildren.erase(aI); + } +} + +void VclBuilder::delete_by_window(vcl::Window *pWindow) +{ + drop_ownership(pWindow); + pWindow->disposeOnce(); +} + +void VclBuilder::drop_ownership(const vcl::Window *pWindow) +{ + auto aI = std::find_if(m_aChildren.begin(), m_aChildren.end(), + [&pWindow](WinAndId& rItem) { return rItem.m_pWindow == pWindow; }); + if (aI != m_aChildren.end()) + m_aChildren.erase(aI); +} + +OString VclBuilder::get_by_window(const vcl::Window *pWindow) const +{ + for (auto const& child : m_aChildren) + { + if (child.m_pWindow == pWindow) + return child.m_sID; + } + + return OString(); +} + +VclBuilder::PackingData VclBuilder::get_window_packing_data(const vcl::Window *pWindow) const +{ + //We've stored the return of new Control, some of these get + //border windows placed around them which are what you get + //from GetChild, so scoot up a level if necessary to get the + //window whose position value we have + const vcl::Window *pPropHolder = pWindow->ImplGetWindow(); + + for (auto const& child : m_aChildren) + { + if (child.m_pWindow == pPropHolder) + return child.m_aPackingData; + } + + return PackingData(); +} + +void VclBuilder::set_window_packing_position(const vcl::Window *pWindow, sal_Int32 nPosition) +{ + for (auto & child : m_aChildren) + { + if (child.m_pWindow == pWindow) + child.m_aPackingData.m_nPosition = nPosition; + } +} + +const VclBuilder::ListStore *VclBuilder::get_model_by_name(const OString& sID) const +{ + std::map<OString, ListStore>::const_iterator aI = m_pParserState->m_aModels.find(sID); + if (aI != m_pParserState->m_aModels.end()) + return &(aI->second); + return nullptr; +} + +const VclBuilder::TextBuffer *VclBuilder::get_buffer_by_name(const OString& sID) const +{ + std::map<OString, TextBuffer>::const_iterator aI = m_pParserState->m_aTextBuffers.find(sID); + if (aI != m_pParserState->m_aTextBuffers.end()) + return &(aI->second); + return nullptr; +} + +const VclBuilder::Adjustment *VclBuilder::get_adjustment_by_name(const OString& sID) const +{ + std::map<OString, Adjustment>::const_iterator aI = m_pParserState->m_aAdjustments.find(sID); + if (aI != m_pParserState->m_aAdjustments.end()) + return &(aI->second); + return nullptr; +} + +void VclBuilder::mungeModel(ComboBox &rTarget, const ListStore &rStore, sal_uInt16 nActiveId) +{ + for (auto const& entry : rStore.m_aEntries) + { + const ListStore::row &rRow = entry; + sal_uInt16 nEntry = rTarget.InsertEntry(rRow[0]); + if (rRow.size() > 1) + { + if (m_bLegacy) + { + sal_Int32 nValue = rRow[1].toInt32(); + rTarget.SetEntryData(nEntry, reinterpret_cast<void*>(nValue)); + } + else + { + if (!rRow[1].isEmpty()) + { + m_aUserData.emplace_back(std::make_unique<OUString>(rRow[1])); + rTarget.SetEntryData(nEntry, m_aUserData.back().get()); + } + } + } + } + if (nActiveId < rStore.m_aEntries.size()) + rTarget.SelectEntryPos(nActiveId); +} + +void VclBuilder::mungeModel(ListBox &rTarget, const ListStore &rStore, sal_uInt16 nActiveId) +{ + for (auto const& entry : rStore.m_aEntries) + { + const ListStore::row &rRow = entry; + sal_uInt16 nEntry = rTarget.InsertEntry(rRow[0]); + if (rRow.size() > 1) + { + if (m_bLegacy) + { + sal_Int32 nValue = rRow[1].toInt32(); + rTarget.SetEntryData(nEntry, reinterpret_cast<void*>(nValue)); + } + else + { + if (!rRow[1].isEmpty()) + { + m_aUserData.emplace_back(std::make_unique<OUString>(rRow[1])); + rTarget.SetEntryData(nEntry, m_aUserData.back().get()); + } + } + } + } + if (nActiveId < rStore.m_aEntries.size()) + rTarget.SelectEntryPos(nActiveId); +} + +void VclBuilder::mungeModel(SvTabListBox& rTarget, const ListStore &rStore, sal_uInt16 nActiveId) +{ + for (auto const& entry : rStore.m_aEntries) + { + const ListStore::row &rRow = entry; + auto pEntry = rTarget.InsertEntry(rRow[0]); + if (rRow.size() > 1) + { + if (m_bLegacy) + { + sal_Int32 nValue = rRow[1].toInt32(); + pEntry->SetUserData(reinterpret_cast<void*>(nValue)); + } + else + { + if (!rRow[1].isEmpty()) + { + m_aUserData.emplace_back(std::make_unique<OUString>(rRow[1])); + pEntry->SetUserData(m_aUserData.back().get()); + } + } + } + } + if (nActiveId < rStore.m_aEntries.size()) + { + SvTreeListEntry* pEntry = rTarget.GetEntry(nullptr, nActiveId); + rTarget.Select(pEntry); + } +} + +void VclBuilder::mungeAdjustment(NumericFormatter &rTarget, const Adjustment &rAdjustment) +{ + int nMul = rtl_math_pow10Exp(1, rTarget.GetDecimalDigits()); + + for (auto const& elem : rAdjustment) + { + const OString &rKey = elem.first; + const OUString &rValue = elem.second; + + if (rKey == "upper") + { + sal_Int64 nUpper = rValue.toDouble() * nMul; + rTarget.SetMax(nUpper); + rTarget.SetLast(nUpper); + } + else if (rKey == "lower") + { + sal_Int64 nLower = rValue.toDouble() * nMul; + rTarget.SetMin(nLower); + rTarget.SetFirst(nLower); + } + else if (rKey == "value") + { + sal_Int64 nValue = rValue.toDouble() * nMul; + rTarget.SetValue(nValue); + } + else if (rKey == "step-increment") + { + sal_Int64 nSpinSize = rValue.toDouble() * nMul; + rTarget.SetSpinSize(nSpinSize); + } + else + { + SAL_INFO("vcl.builder", "unhandled property :" << rKey); + } + } +} + +void VclBuilder::mungeAdjustment(FormattedField &rTarget, const Adjustment &rAdjustment) +{ + double nMaxValue = 0, nMinValue = 0, nValue = 0, nSpinSize = 0; + + for (auto const& elem : rAdjustment) + { + const OString &rKey = elem.first; + const OUString &rValue = elem.second; + + if (rKey == "upper") + nMaxValue = rValue.toDouble(); + else if (rKey == "lower") + nMinValue = rValue.toDouble(); + else if (rKey == "value") + nValue = rValue.toDouble(); + else if (rKey == "step-increment") + nSpinSize = rValue.toDouble(); + else + SAL_INFO("vcl.builder", "unhandled property :" << rKey); + } + + Formatter& rFormatter = rTarget.GetFormatter(); + rFormatter.SetMinValue(nMinValue); + rFormatter.SetMaxValue(nMaxValue); + rFormatter.SetValue(nValue); + rFormatter.SetSpinSize(nSpinSize); +} + +void VclBuilder::mungeAdjustment(ScrollBar &rTarget, const Adjustment &rAdjustment) +{ + for (auto const& elem : rAdjustment) + { + const OString &rKey = elem.first; + const OUString &rValue = elem.second; + + if (rKey == "upper") + rTarget.SetRangeMax(rValue.toInt32()); + else if (rKey == "lower") + rTarget.SetRangeMin(rValue.toInt32()); + else if (rKey == "value") + rTarget.SetThumbPos(rValue.toInt32()); + else if (rKey == "step-increment") + rTarget.SetLineSize(rValue.toInt32()); + else if (rKey == "page-increment") + rTarget.SetPageSize(rValue.toInt32()); + else + { + SAL_INFO("vcl.builder", "unhandled property :" << rKey); + } + } +} + +void VclBuilder::mungeAdjustment(Slider& rTarget, const Adjustment& rAdjustment) +{ + for (auto const& elem : rAdjustment) + { + const OString &rKey = elem.first; + const OUString &rValue = elem.second; + + if (rKey == "upper") + rTarget.SetRangeMax(rValue.toInt32()); + else if (rKey == "lower") + rTarget.SetRangeMin(rValue.toInt32()); + else if (rKey == "value") + rTarget.SetThumbPos(rValue.toInt32()); + else if (rKey == "step-increment") + rTarget.SetLineSize(rValue.toInt32()); + else if (rKey == "page-increment") + rTarget.SetPageSize(rValue.toInt32()); + else + { + SAL_INFO("vcl.builder", "unhandled property :" << rKey); + } + } +} + +void VclBuilder::mungeTextBuffer(VclMultiLineEdit &rTarget, const TextBuffer &rTextBuffer) +{ + for (auto const& elem : rTextBuffer) + { + const OString &rKey = elem.first; + const OUString &rValue = elem.second; + + if (rKey == "text") + rTarget.SetText(rValue); + else + { + SAL_INFO("vcl.builder", "unhandled property :" << rKey); + } + } +} + +VclBuilder::ParserState::ParserState() + : m_nLastToolbarId(0) + , m_nLastMenuItemId(0) +{} + +VclBuilder::MenuAndId::MenuAndId(const OString &rId, Menu *pMenu) + : m_sID(rId) + , m_pMenu(pMenu) +{} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/clipping.cxx b/vcl/source/window/clipping.cxx new file mode 100644 index 000000000..f55283cff --- /dev/null +++ b/vcl/source/window/clipping.cxx @@ -0,0 +1,709 @@ +/* -*- 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/window.hxx> +#include <vcl/virdev.hxx> + +#include <tools/debug.hxx> + +#include <salobj.hxx> +#include <window.h> + +namespace vcl { + +vcl::Region WindowOutputDevice::GetOutputBoundsClipRegion() const +{ + vcl::Region aClip(GetClipRegion()); + aClip.Intersect(tools::Rectangle(Point(), GetOutputSize())); + + return aClip; +} + +void WindowOutputDevice::InitClipRegion() +{ + DBG_TESTSOLARMUTEX(); + + vcl::Region aRegion; + + if ( mxOwnerWindow->mpWindowImpl->mbInPaint ) + aRegion = *(mxOwnerWindow->mpWindowImpl->mpPaintRegion); + else + { + aRegion = mxOwnerWindow->ImplGetWinChildClipRegion(); + // only this region is in frame coordinates, so re-mirror it + // the mpWindowImpl->mpPaintRegion above is already correct (see ImplCallPaint()) ! + if( ImplIsAntiparallel() ) + ReMirror ( aRegion ); + } + if ( mbClipRegion ) + aRegion.Intersect( ImplPixelToDevicePixel( maRegion ) ); + if ( aRegion.IsEmpty() ) + mbOutputClipped = true; + else + { + mbOutputClipped = false; + SelectClipRegion( aRegion ); + } + mbClipRegionSet = true; + + mbInitClipRegion = false; +} + +void Window::SetParentClipMode( ParentClipMode nMode ) +{ + if ( mpWindowImpl->mpBorderWindow ) + mpWindowImpl->mpBorderWindow->SetParentClipMode( nMode ); + else + { + if ( !ImplIsOverlapWindow() ) + { + mpWindowImpl->mnParentClipMode = nMode; + if ( nMode & ParentClipMode::Clip ) + mpWindowImpl->mpParent->mpWindowImpl->mbClipChildren = true; + } + } +} + +ParentClipMode Window::GetParentClipMode() const +{ + if ( mpWindowImpl->mpBorderWindow ) + return mpWindowImpl->mpBorderWindow->GetParentClipMode(); + else + return mpWindowImpl->mnParentClipMode; +} + +void Window::ExpandPaintClipRegion( const vcl::Region& rRegion ) +{ + if( !mpWindowImpl->mpPaintRegion ) + return; + + vcl::Region aPixRegion = LogicToPixel( rRegion ); + vcl::Region aDevPixRegion = GetOutDev()->ImplPixelToDevicePixel( aPixRegion ); + + vcl::Region aWinChildRegion = ImplGetWinChildClipRegion(); + // only this region is in frame coordinates, so re-mirror it + if( GetOutDev()->ImplIsAntiparallel() ) + { + const OutputDevice *pOutDev = GetOutDev(); + pOutDev->ReMirror( aWinChildRegion ); + } + + aDevPixRegion.Intersect( aWinChildRegion ); + if( ! aDevPixRegion.IsEmpty() ) + { + mpWindowImpl->mpPaintRegion->Union( aDevPixRegion ); + GetOutDev()->mbInitClipRegion = true; + } +} + +vcl::Region Window::GetWindowClipRegionPixel() const +{ + vcl::Region aWinClipRegion; + + if ( mpWindowImpl->mbInitWinClipRegion ) + const_cast<vcl::Window*>(this)->ImplInitWinClipRegion(); + aWinClipRegion = mpWindowImpl->maWinClipRegion; + + vcl::Region aWinRegion( GetOutputRectPixel() ); + + if ( aWinRegion == aWinClipRegion ) + aWinClipRegion.SetNull(); + + aWinClipRegion.Move( -GetOutDev()->mnOutOffX, -GetOutDev()->mnOutOffY ); + + return aWinClipRegion; +} + + +vcl::Region WindowOutputDevice::GetActiveClipRegion() const +{ + vcl::Region aRegion(true); + + if ( mxOwnerWindow->mpWindowImpl->mbInPaint ) + { + aRegion = *(mxOwnerWindow->mpWindowImpl->mpPaintRegion); + aRegion.Move( -mnOutOffX, -mnOutOffY ); + } + + if ( mbClipRegion ) + aRegion.Intersect( maRegion ); + + return PixelToLogic( aRegion ); +} + +void WindowOutputDevice::ClipToPaintRegion(tools::Rectangle& rDstRect) +{ + const vcl::Region aPaintRgn(mxOwnerWindow->GetPaintRegion()); + + if (!aPaintRgn.IsNull()) + rDstRect.Intersection(LogicToPixel(aPaintRgn.GetBoundRect())); +} + +void Window::EnableClipSiblings( bool bClipSiblings ) +{ + + if ( mpWindowImpl->mpBorderWindow ) + mpWindowImpl->mpBorderWindow->EnableClipSiblings( bClipSiblings ); + + mpWindowImpl->mbClipSiblings = bClipSiblings; +} + +void Window::ImplClipBoundaries( vcl::Region& rRegion, bool bThis, bool bOverlaps ) +{ + if ( bThis ) + ImplIntersectWindowClipRegion( rRegion ); + else if ( ImplIsOverlapWindow() ) + { + // clip to frame if required + if ( !mpWindowImpl->mbFrame ) + rRegion.Intersect( tools::Rectangle( Point( 0, 0 ), mpWindowImpl->mpFrameWindow->GetOutputSizePixel() ) ); + + if ( bOverlaps && !rRegion.IsEmpty() ) + { + // Clip Overlap Siblings + vcl::Window* pStartOverlapWindow = this; + while ( !pStartOverlapWindow->mpWindowImpl->mbFrame ) + { + vcl::Window* pOverlapWindow = pStartOverlapWindow->mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap; + while ( pOverlapWindow && (pOverlapWindow != pStartOverlapWindow) ) + { + pOverlapWindow->ImplExcludeOverlapWindows2( rRegion ); + pOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext; + } + pStartOverlapWindow = pStartOverlapWindow->mpWindowImpl->mpOverlapWindow; + } + + // Clip Child Overlap Windows + ImplExcludeOverlapWindows( rRegion ); + } + } + else + ImplGetParent()->ImplIntersectWindowClipRegion( rRegion ); +} + +bool Window::ImplClipChildren( vcl::Region& rRegion ) const +{ + bool bOtherClip = false; + vcl::Window* pWindow = mpWindowImpl->mpFirstChild; + while ( pWindow ) + { + if ( pWindow->mpWindowImpl->mbReallyVisible ) + { + // read-out ParentClipMode-Flags + ParentClipMode nClipMode = pWindow->GetParentClipMode(); + if ( !(nClipMode & ParentClipMode::NoClip) && + ((nClipMode & ParentClipMode::Clip) || (GetStyle() & WB_CLIPCHILDREN)) ) + pWindow->ImplExcludeWindowRegion( rRegion ); + else + bOtherClip = true; + } + + pWindow = pWindow->mpWindowImpl->mpNext; + } + + return bOtherClip; +} + +void Window::ImplClipAllChildren( vcl::Region& rRegion ) const +{ + vcl::Window* pWindow = mpWindowImpl->mpFirstChild; + while ( pWindow ) + { + if ( pWindow->mpWindowImpl->mbReallyVisible ) + pWindow->ImplExcludeWindowRegion( rRegion ); + pWindow = pWindow->mpWindowImpl->mpNext; + } +} + +void Window::ImplClipSiblings( vcl::Region& rRegion ) const +{ + vcl::Window* pWindow = ImplGetParent()->mpWindowImpl->mpFirstChild; + while ( pWindow ) + { + if ( pWindow == this ) + break; + + if ( pWindow->mpWindowImpl->mbReallyVisible ) + pWindow->ImplExcludeWindowRegion( rRegion ); + + pWindow = pWindow->mpWindowImpl->mpNext; + } +} + +void Window::ImplInitWinClipRegion() +{ + // Build Window Region + mpWindowImpl->maWinClipRegion = GetOutputRectPixel(); + if ( mpWindowImpl->mbWinRegion ) + mpWindowImpl->maWinClipRegion.Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) ); + + // ClipSiblings + if ( mpWindowImpl->mbClipSiblings && !ImplIsOverlapWindow() ) + ImplClipSiblings( mpWindowImpl->maWinClipRegion ); + + // Clip Parent Boundaries + ImplClipBoundaries( mpWindowImpl->maWinClipRegion, false, true ); + + // Clip Children + if ( (GetStyle() & WB_CLIPCHILDREN) || mpWindowImpl->mbClipChildren ) + mpWindowImpl->mbInitChildRegion = true; + + mpWindowImpl->mbInitWinClipRegion = false; +} + +void Window::ImplInitWinChildClipRegion() +{ + if ( !mpWindowImpl->mpFirstChild ) + { + mpWindowImpl->mpChildClipRegion.reset(); + } + else + { + if ( !mpWindowImpl->mpChildClipRegion ) + mpWindowImpl->mpChildClipRegion.reset( new vcl::Region( mpWindowImpl->maWinClipRegion ) ); + else + *mpWindowImpl->mpChildClipRegion = mpWindowImpl->maWinClipRegion; + + ImplClipChildren( *mpWindowImpl->mpChildClipRegion ); + } + + mpWindowImpl->mbInitChildRegion = false; +} + +Region& Window::ImplGetWinChildClipRegion() +{ + if ( mpWindowImpl->mbInitWinClipRegion ) + ImplInitWinClipRegion(); + if ( mpWindowImpl->mbInitChildRegion ) + ImplInitWinChildClipRegion(); + if ( mpWindowImpl->mpChildClipRegion ) + return *mpWindowImpl->mpChildClipRegion; + return mpWindowImpl->maWinClipRegion; +} + +bool Window::ImplSysObjClip( const vcl::Region* pOldRegion ) +{ + bool bUpdate = true; + + if ( mpWindowImpl->mpSysObj ) + { + bool bVisibleState = mpWindowImpl->mbReallyVisible; + + if ( bVisibleState ) + { + vcl::Region& rWinChildClipRegion = ImplGetWinChildClipRegion(); + + if (!rWinChildClipRegion.IsEmpty()) + { + if ( pOldRegion ) + { + vcl::Region aNewRegion = rWinChildClipRegion; + rWinChildClipRegion.Intersect(*pOldRegion); + bUpdate = aNewRegion == rWinChildClipRegion; + } + + vcl::Region aRegion = rWinChildClipRegion; + vcl::Region aWinRectRegion( GetOutputRectPixel() ); + + if ( aRegion == aWinRectRegion ) + mpWindowImpl->mpSysObj->ResetClipRegion(); + else + { + aRegion.Move( -GetOutDev()->mnOutOffX, -GetOutDev()->mnOutOffY ); + + // set/update clip region + RectangleVector aRectangles; + aRegion.GetRegionRectangles(aRectangles); + mpWindowImpl->mpSysObj->BeginSetClipRegion(aRectangles.size()); + + for (auto const& rectangle : aRectangles) + { + mpWindowImpl->mpSysObj->UnionClipRegion( + rectangle.Left(), + rectangle.Top(), + rectangle.GetWidth(), // orig nWidth was ((R - L) + 1), same as GetWidth does + rectangle.GetHeight()); // same for height + } + + mpWindowImpl->mpSysObj->EndSetClipRegion(); + } + } + else + bVisibleState = false; + } + + // update visible status + mpWindowImpl->mpSysObj->Show( bVisibleState ); + } + + return bUpdate; +} + +void Window::ImplUpdateSysObjChildrenClip() +{ + if ( mpWindowImpl->mpSysObj && mpWindowImpl->mbInitWinClipRegion ) + ImplSysObjClip( nullptr ); + + vcl::Window* pWindow = mpWindowImpl->mpFirstChild; + while ( pWindow ) + { + pWindow->ImplUpdateSysObjChildrenClip(); + pWindow = pWindow->mpWindowImpl->mpNext; + } +} + +void Window::ImplUpdateSysObjOverlapsClip() +{ + ImplUpdateSysObjChildrenClip(); + + vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap; + while ( pWindow ) + { + pWindow->ImplUpdateSysObjOverlapsClip(); + pWindow = pWindow->mpWindowImpl->mpNext; + } +} + +void Window::ImplUpdateSysObjClip() +{ + if ( !ImplIsOverlapWindow() ) + { + ImplUpdateSysObjChildrenClip(); + + // siblings should recalculate their clip region + if ( mpWindowImpl->mbClipSiblings ) + { + vcl::Window* pWindow = mpWindowImpl->mpNext; + while ( pWindow ) + { + pWindow->ImplUpdateSysObjChildrenClip(); + pWindow = pWindow->mpWindowImpl->mpNext; + } + } + } + else + mpWindowImpl->mpFrameWindow->ImplUpdateSysObjOverlapsClip(); +} + +bool Window::ImplSetClipFlagChildren( bool bSysObjOnlySmaller ) +{ + bool bUpdate = true; + if ( mpWindowImpl->mpSysObj ) + { + std::unique_ptr<vcl::Region> pOldRegion; + if ( bSysObjOnlySmaller && !mpWindowImpl->mbInitWinClipRegion ) + pOldRegion.reset(new vcl::Region( mpWindowImpl->maWinClipRegion )); + + GetOutDev()->mbInitClipRegion = true; + mpWindowImpl->mbInitWinClipRegion = true; + + vcl::Window* pWindow = mpWindowImpl->mpFirstChild; + while ( pWindow ) + { + if ( !pWindow->ImplSetClipFlagChildren( bSysObjOnlySmaller ) ) + bUpdate = false; + pWindow = pWindow->mpWindowImpl->mpNext; + } + + if ( !ImplSysObjClip( pOldRegion.get() ) ) + { + GetOutDev()->mbInitClipRegion = true; + mpWindowImpl->mbInitWinClipRegion = true; + bUpdate = false; + } + } + else + { + GetOutDev()->mbInitClipRegion = true; + mpWindowImpl->mbInitWinClipRegion = true; + + vcl::Window* pWindow = mpWindowImpl->mpFirstChild; + while ( pWindow ) + { + if ( !pWindow->ImplSetClipFlagChildren( bSysObjOnlySmaller ) ) + bUpdate = false; + pWindow = pWindow->mpWindowImpl->mpNext; + } + } + return bUpdate; +} + +bool Window::ImplSetClipFlagOverlapWindows( bool bSysObjOnlySmaller ) +{ + bool bUpdate = ImplSetClipFlagChildren( bSysObjOnlySmaller ); + + vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap; + while ( pWindow ) + { + if ( !pWindow->ImplSetClipFlagOverlapWindows( bSysObjOnlySmaller ) ) + bUpdate = false; + pWindow = pWindow->mpWindowImpl->mpNext; + } + + return bUpdate; +} + +bool Window::ImplSetClipFlag( bool bSysObjOnlySmaller ) +{ + if ( !ImplIsOverlapWindow() ) + { + bool bUpdate = ImplSetClipFlagChildren( bSysObjOnlySmaller ); + + vcl::Window* pParent = ImplGetParent(); + if ( pParent && + ((pParent->GetStyle() & WB_CLIPCHILDREN) || (mpWindowImpl->mnParentClipMode & ParentClipMode::Clip)) ) + { + pParent->GetOutDev()->mbInitClipRegion = true; + pParent->mpWindowImpl->mbInitChildRegion = true; + } + + // siblings should recalculate their clip region + if ( mpWindowImpl->mbClipSiblings ) + { + vcl::Window* pWindow = mpWindowImpl->mpNext; + while ( pWindow ) + { + if ( !pWindow->ImplSetClipFlagChildren( bSysObjOnlySmaller ) ) + bUpdate = false; + pWindow = pWindow->mpWindowImpl->mpNext; + } + } + + return bUpdate; + } + else + return mpWindowImpl->mpFrameWindow->ImplSetClipFlagOverlapWindows( bSysObjOnlySmaller ); +} + +void Window::ImplIntersectWindowClipRegion( vcl::Region& rRegion ) +{ + if ( mpWindowImpl->mbInitWinClipRegion ) + ImplInitWinClipRegion(); + + rRegion.Intersect( mpWindowImpl->maWinClipRegion ); +} + +void Window::ImplIntersectWindowRegion( vcl::Region& rRegion ) +{ + rRegion.Intersect( GetOutputRectPixel() ); + if ( mpWindowImpl->mbWinRegion ) + rRegion.Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) ); +} + +void Window::ImplExcludeWindowRegion( vcl::Region& rRegion ) +{ + if ( mpWindowImpl->mbWinRegion ) + { + vcl::Region aRegion( GetOutputRectPixel() ); + aRegion.Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) ); + rRegion.Exclude( aRegion ); + } + else + { + rRegion.Exclude( GetOutputRectPixel() ); + } +} + +void Window::ImplExcludeOverlapWindows( vcl::Region& rRegion ) const +{ + vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap; + while ( pWindow ) + { + if ( pWindow->mpWindowImpl->mbReallyVisible ) + { + pWindow->ImplExcludeWindowRegion( rRegion ); + pWindow->ImplExcludeOverlapWindows( rRegion ); + } + + pWindow = pWindow->mpWindowImpl->mpNext; + } +} + +void Window::ImplExcludeOverlapWindows2( vcl::Region& rRegion ) +{ + if ( mpWindowImpl->mbReallyVisible ) + ImplExcludeWindowRegion( rRegion ); + + ImplExcludeOverlapWindows( rRegion ); +} + +void Window::ImplIntersectAndUnionOverlapWindows( const vcl::Region& rInterRegion, vcl::Region& rRegion ) const +{ + vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap; + while ( pWindow ) + { + if ( pWindow->mpWindowImpl->mbReallyVisible ) + { + vcl::Region aTempRegion( rInterRegion ); + pWindow->ImplIntersectWindowRegion( aTempRegion ); + rRegion.Union( aTempRegion ); + pWindow->ImplIntersectAndUnionOverlapWindows( rInterRegion, rRegion ); + } + + pWindow = pWindow->mpWindowImpl->mpNext; + } +} + +void Window::ImplIntersectAndUnionOverlapWindows2( const vcl::Region& rInterRegion, vcl::Region& rRegion ) +{ + if ( mpWindowImpl->mbReallyVisible ) + { + vcl::Region aTempRegion( rInterRegion ); + ImplIntersectWindowRegion( aTempRegion ); + rRegion.Union( aTempRegion ); + } + + ImplIntersectAndUnionOverlapWindows( rInterRegion, rRegion ); +} + +void Window::ImplCalcOverlapRegionOverlaps( const vcl::Region& rInterRegion, vcl::Region& rRegion ) const +{ + // Clip Overlap Siblings + vcl::Window const * pStartOverlapWindow; + if ( !ImplIsOverlapWindow() ) + pStartOverlapWindow = mpWindowImpl->mpOverlapWindow; + else + pStartOverlapWindow = this; + while ( !pStartOverlapWindow->mpWindowImpl->mbFrame ) + { + vcl::Window* pOverlapWindow = pStartOverlapWindow->mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap; + while ( pOverlapWindow && (pOverlapWindow != pStartOverlapWindow) ) + { + pOverlapWindow->ImplIntersectAndUnionOverlapWindows2( rInterRegion, rRegion ); + pOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext; + } + pStartOverlapWindow = pStartOverlapWindow->mpWindowImpl->mpOverlapWindow; + } + + // Clip Child Overlap Windows + if ( !ImplIsOverlapWindow() ) + mpWindowImpl->mpOverlapWindow->ImplIntersectAndUnionOverlapWindows( rInterRegion, rRegion ); + else + ImplIntersectAndUnionOverlapWindows( rInterRegion, rRegion ); +} + +void Window::ImplCalcOverlapRegion( const tools::Rectangle& rSourceRect, vcl::Region& rRegion, + bool bChildren, bool bSiblings ) +{ + vcl::Region aRegion( rSourceRect ); + if ( mpWindowImpl->mbWinRegion ) + rRegion.Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) ); + vcl::Region aTempRegion; + vcl::Window* pWindow; + + ImplCalcOverlapRegionOverlaps( aRegion, rRegion ); + + // Parent-Boundaries + pWindow = this; + if ( !ImplIsOverlapWindow() ) + { + pWindow = ImplGetParent(); + do + { + aTempRegion = aRegion; + pWindow->ImplExcludeWindowRegion( aTempRegion ); + rRegion.Union( aTempRegion ); + if ( pWindow->ImplIsOverlapWindow() ) + break; + pWindow = pWindow->ImplGetParent(); + } + while ( pWindow ); + } + if ( pWindow && !pWindow->mpWindowImpl->mbFrame ) + { + aTempRegion = aRegion; + aTempRegion.Exclude( tools::Rectangle( Point( 0, 0 ), mpWindowImpl->mpFrameWindow->GetOutputSizePixel() ) ); + rRegion.Union( aTempRegion ); + } + + // Siblings + if ( bSiblings && !ImplIsOverlapWindow() ) + { + pWindow = mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild; + do + { + if ( pWindow->mpWindowImpl->mbReallyVisible && (pWindow != this) ) + { + aTempRegion = aRegion; + pWindow->ImplIntersectWindowRegion( aTempRegion ); + rRegion.Union( aTempRegion ); + } + pWindow = pWindow->mpWindowImpl->mpNext; + } + while ( pWindow ); + } + + if ( !bChildren ) + return; + + pWindow = mpWindowImpl->mpFirstChild; + while ( pWindow ) + { + if ( pWindow->mpWindowImpl->mbReallyVisible ) + { + aTempRegion = aRegion; + pWindow->ImplIntersectWindowRegion( aTempRegion ); + rRegion.Union( aTempRegion ); + } + pWindow = pWindow->mpWindowImpl->mpNext; + } +} + +void WindowOutputDevice::SaveBackground(VirtualDevice& rSaveDevice, const Point& rPos, const Size& rSize, const Size&) const +{ + MapMode aTempMap(GetMapMode()); + aTempMap.SetOrigin(Point()); + rSaveDevice.SetMapMode(aTempMap); + + if ( mxOwnerWindow->mpWindowImpl->mpPaintRegion ) + { + vcl::Region aClip( *mxOwnerWindow->mpWindowImpl->mpPaintRegion ); + const Point aPixPos( LogicToPixel( rPos ) ); + + aClip.Move( -mnOutOffX, -mnOutOffY ); + aClip.Intersect( tools::Rectangle( aPixPos, LogicToPixel( rSize ) ) ); + + if ( !aClip.IsEmpty() ) + { + const vcl::Region aOldClip( rSaveDevice.GetClipRegion() ); + const Point aPixOffset( rSaveDevice.LogicToPixel( Point() ) ); + const bool bMap = rSaveDevice.IsMapModeEnabled(); + + // move clip region to have the same distance to DestOffset + aClip.Move( aPixOffset.X() - aPixPos.X(), aPixOffset.Y() - aPixPos.Y() ); + + // set pixel clip region + rSaveDevice.EnableMapMode( false ); + rSaveDevice.SetClipRegion( aClip ); + rSaveDevice.EnableMapMode( bMap ); + rSaveDevice.DrawOutDev( Point(), rSize, rPos, rSize, *this ); + rSaveDevice.SetClipRegion( aOldClip ); + } + } + else + { + rSaveDevice.DrawOutDev( Point(), rSize, rPos, rSize, *this ); + } + + rSaveDevice.SetMapMode(MapMode()); +} + +} /* namespace vcl */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/commandevent.cxx b/vcl/source/window/commandevent.cxx new file mode 100644 index 000000000..812409ca1 --- /dev/null +++ b/vcl/source/window/commandevent.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 <string.h> + +#include <vcl/commandevent.hxx> + +CommandExtTextInputData::CommandExtTextInputData( const OUString& rText, + const ExtTextInputAttr* pTextAttr, sal_Int32 nCursorPos, sal_uInt16 nCursorFlags, + bool bOnlyCursor) + : maText(rText) +{ + if ( pTextAttr && !maText.isEmpty() ) + { + mpTextAttr.reset( new ExtTextInputAttr[maText.getLength()] ); + memcpy( mpTextAttr.get(), pTextAttr, maText.getLength()*sizeof(ExtTextInputAttr) ); + } + + mnCursorPos = nCursorPos; + mnCursorFlags = nCursorFlags; + mbOnlyCursor = bOnlyCursor; +} + +CommandExtTextInputData::CommandExtTextInputData( const CommandExtTextInputData& rData ) : + maText( rData.maText ) +{ + if ( rData.mpTextAttr && !maText.isEmpty() ) + { + mpTextAttr.reset( new ExtTextInputAttr[maText.getLength()] ); + memcpy( mpTextAttr.get(), rData.mpTextAttr.get(), maText.getLength()*sizeof(ExtTextInputAttr) ); + } + + mnCursorPos = rData.mnCursorPos; + mnCursorFlags = rData.mnCursorFlags; + mbOnlyCursor = rData.mbOnlyCursor; +} + +CommandExtTextInputData::~CommandExtTextInputData() +{ +} + +CommandWheelData::CommandWheelData() +{ + mnDelta = 0; + mnNotchDelta = 0; + mnLines = 0.0; + mnWheelMode = CommandWheelMode::NONE; + mnCode = 0; + mbHorz = false; + mbDeltaIsPixel = false; +} + +CommandWheelData::CommandWheelData( tools::Long nWheelDelta, tools::Long nWheelNotchDelta, + double nScrollLines, + CommandWheelMode nWheelMode, sal_uInt16 nKeyModifier, + bool bHorz, bool bDeltaIsPixel ) +{ + mnDelta = nWheelDelta; + mnNotchDelta = nWheelNotchDelta; + mnLines = nScrollLines; + mnWheelMode = nWheelMode; + mnCode = nKeyModifier; + mbHorz = bHorz; + mbDeltaIsPixel = bDeltaIsPixel; +} + +CommandScrollData::CommandScrollData( tools::Long nDeltaX, tools::Long nDeltaY ) +{ + mnDeltaX = nDeltaX; + mnDeltaY = nDeltaY; +} + +CommandModKeyData::CommandModKeyData( ModKeyFlags nCode, bool bDown ) +{ + mbDown = bDown; + mnCode = nCode; +} + +CommandSelectionChangeData::CommandSelectionChangeData( sal_uLong nStart, sal_uLong nEnd ) +{ + mnStart = nStart; + mnEnd = nEnd; +} + +CommandEvent::CommandEvent() +{ + mpData = nullptr; + mnCommand = CommandEventId::NONE; + mbMouseEvent = false; +} + +CommandEvent::CommandEvent( const Point& rMousePos, + CommandEventId nCmd, bool bMEvt, const void* pCmdData ) : + maPos( rMousePos ) +{ + mpData = const_cast<void*>(pCmdData); + mnCommand = nCmd; + mbMouseEvent = bMEvt; +} + +const CommandExtTextInputData* CommandEvent::GetExtTextInputData() const +{ + if ( mnCommand == CommandEventId::ExtTextInput ) + return static_cast<const CommandExtTextInputData*>(mpData); + else + return nullptr; +} + +const CommandWheelData* CommandEvent::GetWheelData() const +{ + if ( mnCommand == CommandEventId::Wheel ) + return static_cast<const CommandWheelData*>(mpData); + else + return nullptr; +} + +const CommandScrollData* CommandEvent::GetAutoScrollData() const +{ + if ( mnCommand == CommandEventId::AutoScroll ) + return static_cast<const CommandScrollData*>(mpData); + else + return nullptr; +} + +const CommandModKeyData* CommandEvent::GetModKeyData() const +{ + if( mnCommand == CommandEventId::ModKeyChange ) + return static_cast<const CommandModKeyData*>(mpData); + else + return nullptr; +} + +const CommandDialogData* CommandEvent::GetDialogData() const +{ + if( mnCommand == CommandEventId::ShowDialog ) + return static_cast<const CommandDialogData*>(mpData); + else + return nullptr; +} + +CommandMediaData* CommandEvent::GetMediaData() const +{ + if( mnCommand == CommandEventId::Media ) + return static_cast<CommandMediaData*>(mpData); + else + return nullptr; +} + +const CommandSelectionChangeData* CommandEvent::GetSelectionChangeData() const +{ + if( mnCommand == CommandEventId::SelectionChange ) + return static_cast<const CommandSelectionChangeData*>(mpData); + else + return nullptr; +} + +const CommandSwipeData* CommandEvent::GetSwipeData() const +{ + if( mnCommand == CommandEventId::Swipe ) + return static_cast<const CommandSwipeData*>(mpData); + else + return nullptr; +} + +const CommandLongPressData* CommandEvent::GetLongPressData() const +{ + if( mnCommand == CommandEventId::LongPress ) + return static_cast<const CommandLongPressData*>(mpData); + else + return nullptr; +} + +const CommandGestureData* CommandEvent::GetGestureData() const +{ + if (mnCommand == CommandEventId::Gesture) + return static_cast<const CommandGestureData*>(mpData); + else + return nullptr; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/cursor.cxx b/vcl/source/window/cursor.cxx new file mode 100644 index 000000000..406491ed1 --- /dev/null +++ b/vcl/source/window/cursor.cxx @@ -0,0 +1,485 @@ +/* -*- 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 <vcl/svapp.hxx> +#include <vcl/timer.hxx> +#include <vcl/settings.hxx> +#include <vcl/window.hxx> +#include <vcl/cursor.hxx> + +#include <window.h> + +#include <tools/poly.hxx> + +struct ImplCursorData +{ + AutoTimer maTimer { "vcl ImplCursorData maTimer" }; // Timer + Point maPixPos; // Pixel-Position + Point maPixRotOff; // Pixel-Offset-Position + Size maPixSize; // Pixel-Size + Degree10 mnOrientation; // Pixel-Orientation + CursorDirection mnDirection; // indicates writing direction + sal_uInt16 mnStyle; // Cursor-Style + bool mbCurVisible; // Is cursor currently visible + VclPtr<vcl::Window> mpWindow; // assigned window +}; + +namespace +{ +const char* pDisableCursorIndicator(getenv("SAL_DISABLE_CURSOR_INDICATOR")); +bool bDisableCursorIndicator(nullptr != pDisableCursorIndicator); +} + +static tools::Rectangle ImplCursorInvert(vcl::RenderContext* pRenderContext, ImplCursorData const * pData) +{ + tools::Rectangle aPaintRect; + + bool bMapMode = pRenderContext->IsMapModeEnabled(); + pRenderContext->EnableMapMode( false ); + InvertFlags nInvertStyle; + if ( pData->mnStyle & CURSOR_SHADOW ) + nInvertStyle = InvertFlags::N50; + else + nInvertStyle = InvertFlags::NONE; + + tools::Rectangle aRect( pData->maPixPos, pData->maPixSize ); + if ( pData->mnDirection != CursorDirection::NONE || pData->mnOrientation ) + { + tools::Polygon aPoly( aRect ); + if( aPoly.GetSize() == 5 ) + { + aPoly[1].AdjustX(1 ); // include the right border + aPoly[2].AdjustX(1 ); + + // apply direction flag after slant to use the correct shape + if (!bDisableCursorIndicator && pData->mnDirection != CursorDirection::NONE) + { + Point pAry[7]; + // Related system settings for "delta" could be: + // gtk cursor-aspect-ratio and windows SPI_GETCARETWIDTH + int delta = (aRect.getHeight() * 4 / 100) + 1; + if( pData->mnDirection == CursorDirection::LTR ) + { + // left-to-right + pAry[0] = aPoly.GetPoint( 0 ); + pAry[1] = aPoly.GetPoint( 1 ); + pAry[2] = pAry[1]; + pAry[2].AdjustX(delta); + pAry[2].AdjustY(delta); + pAry[3] = pAry[1]; + pAry[3].AdjustY(delta * 2); + pAry[4] = aPoly.GetPoint( 2 ); + pAry[5] = aPoly.GetPoint( 3 ); + pAry[6] = aPoly.GetPoint( 4 ); + } + else if( pData->mnDirection == CursorDirection::RTL ) + { + // right-to-left + pAry[0] = aPoly.GetPoint( 0 ); + pAry[1] = aPoly.GetPoint( 1 ); + pAry[2] = aPoly.GetPoint( 2 ); + pAry[3] = aPoly.GetPoint( 3 ); + pAry[4] = pAry[0]; + pAry[4].AdjustY(delta*2); + pAry[5] = pAry[0]; + pAry[5].AdjustX(-delta); + pAry[5].AdjustY(delta); + pAry[6] = aPoly.GetPoint( 4 ); + } + aPoly = tools::Polygon( 7, pAry); + } + + if ( pData->mnOrientation ) + aPoly.Rotate( pData->maPixRotOff, pData->mnOrientation ); + pRenderContext->Invert( aPoly, nInvertStyle ); + aPaintRect = aPoly.GetBoundRect(); + } + } + else + { + pRenderContext->Invert( aRect, nInvertStyle ); + aPaintRect = aRect; + } + pRenderContext->EnableMapMode( bMapMode ); + return aPaintRect; +} + +static void ImplCursorInvert(vcl::Window* pWindow, ImplCursorData const * pData) +{ + if (!pWindow || pWindow->isDisposed()) + return; + + vcl::PaintBufferGuardPtr pGuard; + const bool bDoubleBuffering = pWindow->SupportsDoubleBuffering(); + if (bDoubleBuffering) + pGuard.reset(new vcl::PaintBufferGuard(pWindow->ImplGetWindowImpl()->mpFrameData, pWindow)); + + vcl::RenderContext* pRenderContext = bDoubleBuffering ? pGuard->GetRenderContext() : pWindow->GetOutDev(); + + tools::Rectangle aPaintRect = ImplCursorInvert(pRenderContext, pData); + if (bDoubleBuffering) + pGuard->SetPaintRect(pRenderContext->PixelToLogic(aPaintRect)); +} + +bool vcl::Cursor::ImplPrepForDraw(const OutputDevice* pDevice, ImplCursorData& rData) +{ + if (pDevice && !rData.mbCurVisible) + { + rData.maPixPos = pDevice->LogicToPixel( maPos ); + rData.maPixSize = pDevice->LogicToPixel( maSize ); + rData.mnOrientation = mnOrientation; + rData.mnDirection = mnDirection; + + // correct the position with the offset + rData.maPixRotOff = rData.maPixPos; + + // use width (as set in Settings) if size is 0, + if (!rData.maPixSize.Width()) + rData.maPixSize.setWidth(pDevice->GetSettings().GetStyleSettings().GetCursorSize()); + return true; + } + return false; +} + +void vcl::Cursor::ImplDraw() +{ + if (mpData && mpData->mpWindow) + { + // calculate output area + if (ImplPrepForDraw(mpData->mpWindow->GetOutDev(), *mpData)) + { + // display + ImplCursorInvert(mpData->mpWindow, mpData.get()); + mpData->mbCurVisible = true; + } + } +} + +void vcl::Cursor::DrawToDevice(OutputDevice& rRenderContext) +{ + ImplCursorData aData; + aData.mnStyle = 0; + aData.mbCurVisible = false; + // calculate output area + if (ImplPrepForDraw(&rRenderContext, aData)) + { + // display + ImplCursorInvert(&rRenderContext, &aData); + } +} + +void vcl::Cursor::ImplRestore() +{ + assert( mpData && mpData->mbCurVisible ); + + ImplCursorInvert(mpData->mpWindow, mpData.get()); + mpData->mbCurVisible = false; +} + +void vcl::Cursor::ImplDoShow( bool bDrawDirect, bool bRestore ) +{ + if ( !mbVisible ) + return; + + vcl::Window* pWindow; + if ( mpWindow ) + pWindow = mpWindow; + else + { + // show the cursor, if there is an active window and the cursor + // has been selected in this window + pWindow = Application::GetFocusWindow(); + if (!pWindow || !pWindow->mpWindowImpl || (pWindow->mpWindowImpl->mpCursor != this) + || pWindow->mpWindowImpl->mbInPaint + || !pWindow->mpWindowImpl->mpFrameData->mbHasFocus) + pWindow = nullptr; + } + + if ( !pWindow ) + return; + + if ( !mpData ) + { + mpData.reset( new ImplCursorData ); + mpData->mbCurVisible = false; + mpData->maTimer.SetInvokeHandler( LINK( this, Cursor, ImplTimerHdl ) ); + } + + mpData->mpWindow = pWindow; + mpData->mnStyle = mnStyle; + if ( bDrawDirect || bRestore ) + ImplDraw(); + + if ( !mpWindow && (bDrawDirect || !mpData->maTimer.IsActive()) ) + { + mpData->maTimer.SetTimeout( pWindow->GetSettings().GetStyleSettings().GetCursorBlinkTime() ); + if ( mpData->maTimer.GetTimeout() != STYLE_CURSOR_NOBLINKTIME ) + mpData->maTimer.Start(); + else if ( !mpData->mbCurVisible ) + ImplDraw(); + LOKNotify( pWindow, "cursor_invalidate" ); + LOKNotify( pWindow, "cursor_visible" ); + } +} + +void vcl::Cursor::LOKNotify( vcl::Window* pWindow, const OUString& rAction ) +{ + VclPtr<vcl::Window> pParent = pWindow->GetParentWithLOKNotifier(); + if (!pParent) + return; + + assert(pWindow && "Cannot notify without a window"); + assert(mpData && "Require ImplCursorData"); + assert(comphelper::LibreOfficeKit::isActive()); + + if (comphelper::LibreOfficeKit::isDialogPainting()) + return; + + const vcl::ILibreOfficeKitNotifier* pNotifier = pParent->GetLOKNotifier(); + std::vector<vcl::LOKPayloadItem> aItems; + if (rAction == "cursor_visible") + aItems.emplace_back("visible", mpData->mbCurVisible ? "true" : "false"); + else if (rAction == "cursor_invalidate") + { + const tools::Long nX = pWindow->GetOutOffXPixel() + pWindow->LogicToPixel(GetPos()).X() - pParent->GetOutOffXPixel(); + const tools::Long nY = pWindow->GetOutOffYPixel() + pWindow->LogicToPixel(GetPos()).Y() - pParent->GetOutOffYPixel(); + Size aSize = pWindow->LogicToPixel(GetSize()); + if (!aSize.Width()) + aSize.setWidth( pWindow->GetSettings().GetStyleSettings().GetCursorSize() ); + + Point aPos(nX, nY); + + if (pWindow->IsRTLEnabled() && pWindow->GetOutDev() && pParent->GetOutDev() + && !pWindow->GetOutDev()->ImplIsAntiparallel()) + pParent->GetOutDev()->ReMirror(aPos); + + if (!pWindow->IsRTLEnabled() && pWindow->GetOutDev() && pParent->GetOutDev() + && pWindow->GetOutDev()->ImplIsAntiparallel()) + { + pWindow->GetOutDev()->ReMirror(aPos); + pParent->GetOutDev()->ReMirror(aPos); + } + + const tools::Rectangle aRect(aPos, aSize); + aItems.emplace_back("rectangle", aRect.toString()); + } + + pNotifier->notifyWindow(pParent->GetLOKWindowId(), rAction, aItems); +} + +bool vcl::Cursor::ImplDoHide( bool bSuspend ) +{ + bool bWasCurVisible = false; + if ( mpData && mpData->mpWindow ) + { + bWasCurVisible = mpData->mbCurVisible; + if ( mpData->mbCurVisible ) + ImplRestore(); + + if ( !bSuspend ) + { + LOKNotify( mpData->mpWindow, "cursor_visible" ); + mpData->maTimer.Stop(); + mpData->mpWindow = nullptr; + } + } + return bWasCurVisible; +} + +void vcl::Cursor::ImplShow() +{ + ImplDoShow( true/*bDrawDirect*/, false ); +} + +void vcl::Cursor::ImplHide() +{ + ImplDoHide( false ); +} + +void vcl::Cursor::ImplResume( bool bRestore ) +{ + ImplDoShow( false, bRestore ); +} + +bool vcl::Cursor::ImplSuspend() +{ + return ImplDoHide( true ); +} + +void vcl::Cursor::ImplNew() +{ + if ( !(mbVisible && mpData && mpData->mpWindow) ) + return; + + if ( mpData->mbCurVisible ) + ImplRestore(); + + ImplDraw(); + if ( !mpWindow ) + { + LOKNotify( mpData->mpWindow, "cursor_invalidate" ); + if ( mpData->maTimer.GetTimeout() != STYLE_CURSOR_NOBLINKTIME ) + mpData->maTimer.Start(); + } +} + +IMPL_LINK_NOARG(vcl::Cursor, ImplTimerHdl, Timer *, void) +{ + if ( mpData->mbCurVisible ) + ImplRestore(); + else + ImplDraw(); +} + +vcl::Cursor::Cursor() +{ + mpData = nullptr; + mpWindow = nullptr; + mnOrientation = 0_deg10; + mnDirection = CursorDirection::NONE; + mnStyle = 0; + mbVisible = false; +} + +vcl::Cursor::Cursor( const Cursor& rCursor ) : + maSize( rCursor.maSize ), + maPos( rCursor.maPos ) +{ + mpData = nullptr; + mpWindow = nullptr; + mnOrientation = rCursor.mnOrientation; + mnDirection = rCursor.mnDirection; + mnStyle = 0; + mbVisible = rCursor.mbVisible; +} + +vcl::Cursor::~Cursor() +{ + if (mpData && mpData->mbCurVisible) + ImplRestore(); +} + +void vcl::Cursor::SetStyle( sal_uInt16 nStyle ) +{ + if ( mnStyle != nStyle ) + { + mnStyle = nStyle; + ImplNew(); + } +} + +void vcl::Cursor::Show() +{ + if ( !mbVisible ) + { + mbVisible = true; + ImplShow(); + } +} + +void vcl::Cursor::Hide() +{ + if ( mbVisible ) + { + mbVisible = false; + ImplHide(); + } +} + +void vcl::Cursor::SetWindow( vcl::Window* pWindow ) +{ + if ( mpWindow.get() != pWindow ) + { + mpWindow = pWindow; + ImplNew(); + } +} + +void vcl::Cursor::SetPos( const Point& rPoint ) +{ + if ( maPos != rPoint ) + { + maPos = rPoint; + ImplNew(); + } +} + +void vcl::Cursor::SetSize( const Size& rSize ) +{ + if ( maSize != rSize ) + { + maSize = rSize; + ImplNew(); + } +} + +void vcl::Cursor::SetWidth( tools::Long nNewWidth ) +{ + if ( maSize.Width() != nNewWidth ) + { + maSize.setWidth( nNewWidth ); + ImplNew(); + } +} + +void vcl::Cursor::SetOrientation( Degree10 nNewOrientation ) +{ + if ( mnOrientation != nNewOrientation ) + { + mnOrientation = nNewOrientation; + ImplNew(); + } +} + +void vcl::Cursor::SetDirection( CursorDirection nNewDirection ) +{ + if ( mnDirection != nNewDirection ) + { + mnDirection = nNewDirection; + ImplNew(); + } +} + +vcl::Cursor& vcl::Cursor::operator=( const vcl::Cursor& rCursor ) +{ + maPos = rCursor.maPos; + maSize = rCursor.maSize; + mnOrientation = rCursor.mnOrientation; + mnDirection = rCursor.mnDirection; + mbVisible = rCursor.mbVisible; + ImplNew(); + + return *this; +} + +bool vcl::Cursor::operator==( const vcl::Cursor& rCursor ) const +{ + return + ((maPos == rCursor.maPos) && + (maSize == rCursor.maSize) && + (mnOrientation == rCursor.mnOrientation) && + (mnDirection == rCursor.mnDirection) && + (mbVisible == rCursor.mbVisible)) + ; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/debug.cxx b/vcl/source/window/debug.cxx new file mode 100644 index 000000000..346456923 --- /dev/null +++ b/vcl/source/window/debug.cxx @@ -0,0 +1,48 @@ +/* -*- 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/window.hxx> +#include <tools/debug.hxx> + +#include <window.h> + +#ifdef DBG_UTIL +const char* ImplDbgCheckWindow(const void* pObj) +{ + DBG_TESTSOLARMUTEX(); + + const vcl::Window* pWindow = static_cast<vcl::Window const*>(pObj); + + if ((pWindow->GetType() < WindowType::FIRST) || (pWindow->GetType() > WindowType::LAST)) + return "Window data overwrite"; + + // check window-chain + vcl::Window* pChild = pWindow->mpWindowImpl->mpFirstChild; + while (pChild) + { + if (pChild->mpWindowImpl->mpParent != pWindow) + return "Child-Window-Parent wrong"; + pChild = pChild->mpWindowImpl->mpNext; + } + + return nullptr; +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/debugevent.cxx b/vcl/source/window/debugevent.cxx new file mode 100644 index 000000000..f9f6978f0 --- /dev/null +++ b/vcl/source/window/debugevent.cxx @@ -0,0 +1,271 @@ +/* -*- 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 <comphelper/random.hxx> +#include <o3tl/string_view.hxx> +#include <rtl/string.hxx> +#include <sal/log.hxx> +#include <vcl/keycodes.hxx> +#include <vcl/svapp.hxx> +#include <vcl/wrkwin.hxx> +#include <vcl/menu.hxx> +#include <debugevent.hxx> +#include <window.h> +#include <salwtype.hxx> + +DebugEventInjector::DebugEventInjector( sal_uInt32 nMaxEvents) : + Timer("debug event injector") + , mnEventsLeft( nMaxEvents ) +{ + SetTimeout( 1000 /* ms */ ); + Start(); +} + +static double getRandom() +{ + return comphelper::rng::uniform_real_distribution(); +} + +vcl::Window *DebugEventInjector::ChooseWindow() +{ + vcl::Window *pParent; + + if (getRandom() < 0.80) + if (vcl::Window * pWindow = Application::GetFocusWindow()) + return pWindow; + + if (getRandom() > 0.50 || + !(pParent = Application::GetActiveTopWindow())) + { + // select a top window at random + tools::Long nIdx = Application::GetTopWindowCount() * getRandom(); + pParent = Application::GetTopWindow( nIdx ); + } + assert (pParent != nullptr); + + std::vector< vcl::Window *> aChildren; + pParent->CollectChildren( aChildren ); + + return aChildren[ aChildren.size() * getRandom() ]; +} + + +static void CollectMenuItemIds( Menu *pMenu, std::vector< SalMenuEvent > &rIds ) +{ + sal_uInt16 nItems = pMenu->GetItemCount(); + for (sal_uInt16 i = 0; i < nItems; i++) + { + if (pMenu->GetItemType( i ) != MenuItemType::SEPARATOR || getRandom() < 0.01) + rIds.emplace_back( pMenu->GetItemId( i ), pMenu ); + PopupMenu *pPopup = pMenu->GetPopupMenu( i ); + if (pPopup) + CollectMenuItemIds( pPopup, rIds ); + } +} + +void DebugEventInjector::InjectMenuEvent() +{ + vcl::Window *pFocus = Application::GetFocusWindow(); + if (!pFocus) + return; + + SystemWindow *pSysWin = pFocus->GetSystemWindow(); + if (!pSysWin) + return; + + MenuBar *pMenuBar = pSysWin->GetMenuBar(); + if (!pMenuBar) + return; + + SalEvent nEvents[] = { + SalEvent::MenuCommand, + SalEvent::MenuCommand, + SalEvent::MenuActivate, + SalEvent::MenuDeactivate, + SalEvent::MenuHighlight, + SalEvent::MenuCommand, + SalEvent::MenuCommand, + SalEvent::MenuCommand, + SalEvent::MenuButtonCommand, + SalEvent::MenuButtonCommand, + }; + + std::vector< SalMenuEvent > aIds; + CollectMenuItemIds( pMenuBar, aIds ); + + SalEvent nEvent = nEvents[ static_cast<int>(getRandom() * SAL_N_ELEMENTS( nEvents )) ]; + SalMenuEvent aEvent = aIds[ getRandom() * aIds.size() ]; + bool bHandled = ImplWindowFrameProc( pSysWin, nEvent, &aEvent); + + SAL_INFO( "vcl.debugevent", + "Injected menu event " << aEvent.mpMenu + << " (" << aEvent.mnId << ") '" + << static_cast<Menu *>(aEvent.mpMenu)->GetItemText( aEvent.mnId ) << "' -> " + << bHandled ); +} + +static void InitKeyEvent( SalKeyEvent &rKeyEvent ) +{ + if (getRandom() < 0.01) + rKeyEvent.mnRepeat = getRandom() * 20; + else + rKeyEvent.mnRepeat = 0; +} + +void DebugEventInjector::InjectTextEvent() +{ + SalKeyEvent aKeyEvent; + vcl::Window *pWindow = ChooseWindow(); + + InitKeyEvent( aKeyEvent ); + + if (getRandom() < 0.10) // Occasionally a truly random event + { + aKeyEvent.mnCode = getRandom() * KEY_CODE_MASK; + aKeyEvent.mnCharCode = getRandom() * 0xffff; + } + else + { + static struct { + sal_uInt16 nCodeStart, nCodeEnd; + char aCharStart; + } const nTextCodes[] = { + { KEY_0, KEY_9, '0' }, + { KEY_A, KEY_Z, 'a' } + }; + + size_t i = getRandom() * SAL_N_ELEMENTS( nTextCodes ); + int offset = int( getRandom() * ( nTextCodes[i].nCodeEnd - nTextCodes[i].nCodeStart ) ); + aKeyEvent.mnCode = nTextCodes[i].nCodeStart + offset; + aKeyEvent.mnCharCode = nTextCodes[i].aCharStart + offset; +// fprintf( stderr, "Char '%c' offset %d into record %d base '%c'\n", +// aKeyEvent.mnCharCode, offset, (int)i, nTextCodes[i].aCharStart ); + } + + if( getRandom() < 0.05 ) // modifier + aKeyEvent.mnCode |= static_cast<sal_uInt16>( getRandom() * KEY_MODIFIERS_MASK ) & KEY_MODIFIERS_MASK; + + bool bHandled = ImplWindowFrameProc( pWindow, SalEvent::KeyInput, &aKeyEvent); + + SAL_INFO( "vcl.debugevent", + "Injected key 0x" << std::hex << static_cast<int>(aKeyEvent.mnCode) << std::dec + << " -> " << bHandled + << " win " << pWindow ); + + ImplWindowFrameProc( pWindow, SalEvent::KeyUp, &aKeyEvent ); +} + +/* + * The more heuristics we have to inform this the better, + * key-bindings, menu entries, allowable entry types etc. + */ +void DebugEventInjector::InjectEvent() +{ +// fprintf( stderr, "%6d - ", (int)mnEventsLeft ); + + double nRand = getRandom(); + if (nRand < 0.30) + { + int nEvents = getRandom() * 10; + for (int i = 0; i < nEvents; i++) + InjectTextEvent(); + } + else if (nRand < 0.60) + InjectKeyNavEdit(); + else if (nRand < 0.95) + InjectMenuEvent(); +} + +void DebugEventInjector::InjectKeyNavEdit() +{ + vcl::Window *pWindow = ChooseWindow(); + + static struct { + double mnProb; + sal_uInt16 mnKey; + } const nWeights[] = { + // edit / escape etc. - 50% + { 0.20, KEY_SPACE }, + { 0.10, KEY_TAB }, + { 0.07, KEY_RETURN }, + { 0.05, KEY_DELETE }, + { 0.05, KEY_BACKSPACE }, + + // navigate - 45% + { 0.15, KEY_LEFT }, + { 0.10, KEY_RIGHT }, + { 0.05, KEY_UP }, + { 0.05, KEY_DOWN }, + { 0.05, KEY_PAGEUP }, + { 0.05, KEY_PAGEDOWN }, + + // other + { 0.01, KEY_INSERT }, + { 0.02, KEY_HOME }, + { 0.02, KEY_END }, + }; + + double d = 0.0, nRand = getRandom(); + sal_uInt16 nKey = KEY_SPACE; + for (auto & rWeight : nWeights) + { + d += rWeight.mnProb; + assert (d < 1.01); + if ( nRand < d ) + { + nKey = rWeight.mnKey; + break; + } + } + + SalKeyEvent aKeyEvent; + InitKeyEvent( aKeyEvent ); + aKeyEvent.mnCode = nKey; + + if (getRandom() < 0.15) // modifier + aKeyEvent.mnCode |= static_cast<sal_uInt16>(getRandom() * KEY_MODIFIERS_MASK) & KEY_MODIFIERS_MASK; + + aKeyEvent.mnCharCode = 0x0; // hopefully unused. + + bool bHandled = ImplWindowFrameProc( pWindow, SalEvent::KeyInput, &aKeyEvent ); + + SAL_INFO( "vcl.debugevent", + "Injected edit / move key 0x" << std::hex << static_cast<int>(aKeyEvent.mnCode) << std::dec + << " -> " << bHandled + << " win " << pWindow ); + ImplWindowFrameProc( pWindow, SalEvent::KeyUp, &aKeyEvent ); +} + +void DebugEventInjector::Invoke() +{ + InjectEvent(); + mnEventsLeft--; + if (mnEventsLeft > 0) + { + SetTimeout( 1 ); + Start(); + } + else + Application::Quit(); +} + +DebugEventInjector *DebugEventInjector::getCreate() +{ + sal_uInt32 nEvents; + const char *pEvents = getenv("VCL_EVENT_INJECTION"); + if (!pEvents) + return nullptr; + nEvents = o3tl::toUInt32( pEvents ); + if (nEvents > 0) + return new DebugEventInjector( nEvents ); + else + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/decoview.cxx b/vcl/source/window/decoview.cxx new file mode 100644 index 000000000..71481200d --- /dev/null +++ b/vcl/source/window/decoview.cxx @@ -0,0 +1,1046 @@ +/* -*- 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/settings.hxx> +#include <vcl/outdev.hxx> +#include <vcl/decoview.hxx> +#include <vcl/window.hxx> +#include <vcl/ctrl.hxx> + +constexpr auto BUTTON_DRAW_FLATTEST = DrawButtonFlags::Flat | + DrawButtonFlags::Pressed | + DrawButtonFlags::Checked | + DrawButtonFlags::Highlight; + +namespace { + +tools::Long AdjustRectToSquare( tools::Rectangle &rRect ) +{ + const tools::Long nWidth = rRect.GetWidth(); + const tools::Long nHeight = rRect.GetHeight(); + tools::Long nSide = std::min( nWidth, nHeight ); + + if ( nSide && !(nSide & 1) ) + { + // we prefer an odd size + --nSide; + } + + // Make the rectangle a square + rRect.SetSize( Size( nSide, nSide ) ); + + // and place it at the center of the original rectangle + rRect.Move( (nWidth-nSide)/2, (nHeight-nSide)/2 ); + + return nSide; +} + +void ImplDrawSymbol( OutputDevice* pDev, tools::Rectangle nRect, const SymbolType eType ) +{ + const tools::Long nSide = AdjustRectToSquare( nRect ); + + if ( !nSide ) return; + if ( nSide==1 ) + { + pDev->DrawPixel( Point( nRect.Left(), nRect.Top() ) ); + return; + } + + // Precalculate some values + const tools::Long n2 = nSide/2; + const tools::Long n4 = (n2+1)/2; + const tools::Long n8 = (n4+1)/2; + const tools::Long n16 = (n8+1)/2; + const Point aCenter = nRect.Center(); + + switch ( eType ) + { + case SymbolType::ARROW_UP: + { + tools::Polygon arrow(7); + arrow.SetPoint( Point( aCenter.X(), nRect.Top()), 0 ); + arrow.SetPoint( Point( aCenter.X() - n2, nRect.Top() + n2 ), 1 ); + arrow.SetPoint( Point( aCenter.X() - n8, nRect.Top() + n2 ), 2 ); + arrow.SetPoint( Point( aCenter.X() - n8, nRect.Bottom()), 3 ); + arrow.SetPoint( Point( aCenter.X() + n8, nRect.Bottom()), 4 ); + arrow.SetPoint( Point( aCenter.X() + n8, nRect.Top() + n2 ), 5 ); + arrow.SetPoint( Point( aCenter.X() + n2, nRect.Top() + n2 ), 6 ); + pDev->Push(vcl::PushFlags::LINECOLOR); + pDev->SetLineColor(); + pDev->DrawPolygon( arrow ); + pDev->Pop(); + break; + } + + case SymbolType::ARROW_DOWN: + { + tools::Polygon arrow(7); + arrow.SetPoint( Point( aCenter.X(), nRect.Bottom()), 0 ); + arrow.SetPoint( Point( aCenter.X() - n2, nRect.Bottom() - n2 ), 1 ); + arrow.SetPoint( Point( aCenter.X() - n8, nRect.Bottom() - n2 ), 2 ); + arrow.SetPoint( Point( aCenter.X() - n8, nRect.Top()), 3 ); + arrow.SetPoint( Point( aCenter.X() + n8, nRect.Top()), 4 ); + arrow.SetPoint( Point( aCenter.X() + n8, nRect.Bottom() - n2 ), 5 ); + arrow.SetPoint( Point( aCenter.X() + n2, nRect.Bottom() - n2 ), 6 ); + pDev->Push(vcl::PushFlags::LINECOLOR); + pDev->SetLineColor(); + pDev->DrawPolygon( arrow ); + pDev->Pop(); + break; + } + + case SymbolType::ARROW_LEFT: + { + tools::Polygon arrow(7); + arrow.SetPoint( Point( nRect.Left(), aCenter.Y()), 0 ); + arrow.SetPoint( Point( nRect.Left() + n2, aCenter.Y() - n2 ), 1 ); + arrow.SetPoint( Point( nRect.Left() + n2, aCenter.Y() - n8 ), 2 ); + arrow.SetPoint( Point( nRect.Right(), aCenter.Y() - n8 ), 3 ); + arrow.SetPoint( Point( nRect.Right(), aCenter.Y() + n8 ), 4 ); + arrow.SetPoint( Point( nRect.Left() + n2, aCenter.Y() + n8 ), 5 ); + arrow.SetPoint( Point( nRect.Left() + n2, aCenter.Y() + n2 ), 6 ); + pDev->Push(vcl::PushFlags::LINECOLOR); + pDev->SetLineColor(); + pDev->DrawPolygon( arrow ); + pDev->Pop(); + break; + } + + case SymbolType::ARROW_RIGHT: + { + tools::Polygon arrow(7); + arrow.SetPoint( Point( nRect.Right(), aCenter.Y()), 0 ); + arrow.SetPoint( Point( nRect.Right() - n2, aCenter.Y() - n2 ), 1 ); + arrow.SetPoint( Point( nRect.Right() - n2, aCenter.Y() - n8 ), 2 ); + arrow.SetPoint( Point( nRect.Left(), aCenter.Y() - n8 ), 3 ); + arrow.SetPoint( Point( nRect.Left(), aCenter.Y() + n8 ), 4 ); + arrow.SetPoint( Point( nRect.Right() - n2, aCenter.Y() + n8 ), 5 ); + arrow.SetPoint( Point( nRect.Right() - n2, aCenter.Y() + n2 ), 6 ); + pDev->Push(vcl::PushFlags::LINECOLOR); + pDev->SetLineColor(); + pDev->DrawPolygon( arrow ); + pDev->Pop(); + break; + } + + case SymbolType::SPIN_UP: + { + tools::Polygon triangle( 3 ); + triangle.SetPoint( Point( aCenter.X(), nRect.Top() + n4 ), 0 ); + triangle.SetPoint( Point( aCenter.X() - n2, nRect.Top() + n4 + n2 ), 1 ); + triangle.SetPoint( Point( aCenter.X() + n2, nRect.Top() + n4 + n2 ), 2 ); + pDev->Push(vcl::PushFlags::LINECOLOR); + pDev->SetLineColor(); + pDev->DrawPolygon( triangle ); + pDev->Pop(); + break; + } + + case SymbolType::SPIN_DOWN: + { + tools::Polygon triangle( 3 ); + triangle.SetPoint( Point( aCenter.X(), nRect.Bottom() - n4 ), 0 ); + triangle.SetPoint( Point( aCenter.X() - n2, nRect.Bottom() - n4 - n2 ), 1 ); + triangle.SetPoint( Point( aCenter.X() + n2, nRect.Bottom() - n4 - n2 ), 2 ); + pDev->Push(vcl::PushFlags::LINECOLOR); + pDev->SetLineColor(); + pDev->DrawPolygon( triangle ); + pDev->Pop(); + break; + } + + case SymbolType::SPIN_LEFT: + case SymbolType::FIRST: + case SymbolType::PREV: + { + nRect.AdjustLeft(n4 ); + if ( eType==SymbolType::FIRST ) + { + pDev->DrawLine( Point( nRect.Left(), nRect.Top() ), + Point( nRect.Left(), nRect.Bottom() ) ); + nRect.AdjustLeft( 1 ); + } + + tools::Polygon aTriangle(3); + aTriangle.SetPoint(Point(nRect.Left() + n2, aCenter.Y() - n2), 0); + aTriangle.SetPoint(Point(nRect.Left(), aCenter.Y()), 1); + aTriangle.SetPoint(Point(nRect.Left() + n2, aCenter.Y() + n2), 2); + + pDev->Push(vcl::PushFlags::LINECOLOR); + pDev->SetLineColor(); + pDev->DrawPolygon(aTriangle); + pDev->Pop(); + + break; + } + + case SymbolType::SPIN_RIGHT: + case SymbolType::LAST: + case SymbolType::NEXT: + case SymbolType::PLAY: + { + nRect.AdjustRight( -n4 ); + if ( eType==SymbolType::LAST ) + { + pDev->DrawLine( Point( nRect.Right(), nRect.Top() ), + Point( nRect.Right(), nRect.Bottom() ) ); + nRect.AdjustRight( -1 ); + } + + tools::Polygon aTriangle(3); + aTriangle.SetPoint(Point(nRect.Right() - n2, aCenter.Y() - n2), 0); + aTriangle.SetPoint(Point(nRect.Right(), aCenter.Y()), 1); + aTriangle.SetPoint(Point(nRect.Right() - n2, aCenter.Y() + n2), 2); + + pDev->Push(vcl::PushFlags::LINECOLOR); + pDev->SetLineColor(); + pDev->DrawPolygon(aTriangle); + pDev->Pop(); + break; + } + + case SymbolType::PAGEUP: + { + tools::Polygon triangle( 3 ); + triangle.SetPoint( Point( aCenter.X(), nRect.Top()), 0 ); + triangle.SetPoint( Point( aCenter.X() - n2, nRect.Top() + n2 ), 1 ); + triangle.SetPoint( Point( aCenter.X() + n2, nRect.Top() + n2 ), 2 ); + pDev->Push(vcl::PushFlags::LINECOLOR); + pDev->SetLineColor(); + pDev->DrawPolygon( triangle ); + triangle.Move( 0, n2 ); + pDev->DrawPolygon( triangle ); + pDev->Pop(); + break; + } + + case SymbolType::PAGEDOWN: + { + tools::Polygon triangle( 3 ); + triangle.SetPoint( Point( aCenter.X(), nRect.Bottom()), 0 ); + triangle.SetPoint( Point( aCenter.X() - n2, nRect.Bottom() - n2 ), 1 ); + triangle.SetPoint( Point( aCenter.X() + n2, nRect.Bottom() - n2 ), 2 ); + pDev->Push(vcl::PushFlags::LINECOLOR); + pDev->SetLineColor(); + pDev->DrawPolygon( triangle ); + triangle.Move( 0, -n2 ); + pDev->DrawPolygon( triangle ); + pDev->Pop(); + break; + } + + case SymbolType::RADIOCHECKMARK: + { + pDev->DrawEllipse(nRect); + break; + } + + case SymbolType::STOP: + pDev->DrawRect( nRect ); + break; + + case SymbolType::CLOSE: + { + const tools::Long diff = std::max<tools::Long>( 0, n8 - 1 ); + tools::Polygon cross( 16 ); + cross.SetPoint( Point( nRect.Left(), nRect.Top()), 0 ); + cross.SetPoint( Point( nRect.Left(), nRect.Top() + diff ), 1 ); + cross.SetPoint( Point( aCenter.X() - diff, aCenter.Y()), 2 ); + cross.SetPoint( Point( nRect.Left(), nRect.Bottom() - diff ), 3 ); + cross.SetPoint( Point( nRect.Left(), nRect.Bottom()), 4 ); + cross.SetPoint( Point( nRect.Left() + diff, nRect.Bottom()), 5 ); + cross.SetPoint( Point( aCenter.X(), aCenter.Y() + diff ), 6 ); + cross.SetPoint( Point( nRect.Right() - diff, nRect.Bottom()), 7 ); + cross.SetPoint( Point( nRect.Right(), nRect.Bottom()), 8 ); + cross.SetPoint( Point( nRect.Right(), nRect.Bottom() - diff ), 9 ); + cross.SetPoint( Point( aCenter.X() + diff, aCenter.Y()), 10 ); + cross.SetPoint( Point( nRect.Right(), nRect.Top() + diff ), 11 ); + cross.SetPoint( Point( nRect.Right(), nRect.Top()), 12 ); + cross.SetPoint( Point( nRect.Right() - diff, nRect.Top()), 13 ); + cross.SetPoint( Point( aCenter.X(), aCenter.Y() - diff ), 14 ); + cross.SetPoint( Point( nRect.Left() + diff, nRect.Top()), 15 ); + pDev->DrawPolygon( cross ); + break; + } + + case SymbolType::CHECKMARK: + { + tools::Long n3 = nSide/3; + nRect.AdjustTop( -(n3/2) ); + nRect.AdjustBottom( -(n3/2) ); + tools::Polygon checkmark(6); + // #106953# never mirror checkmarks + if ( pDev->HasMirroredGraphics() && pDev->IsRTLEnabled() ) + { + // Draw a mirrored checkmark so that it looks "normal" in a + // mirrored graphics device (double mirroring!) + checkmark.SetPoint( Point( nRect.Right(), nRect.Bottom()-n3 ), 0 ); + checkmark.SetPoint( Point( nRect.Right()-n3, nRect.Bottom()), 1 ); + checkmark.SetPoint( Point( nRect.Left(), nRect.Top()+n3 ), 2 ); + checkmark.SetPoint( Point( nRect.Left(), nRect.Top()+n3 + 1 ), 3 ); + checkmark.SetPoint( Point( nRect.Right()-n3, nRect.Bottom() + 1 ), 4 ); + checkmark.SetPoint( Point( nRect.Right(), nRect.Bottom()-n3 + 1 ), 5 ); + } + else + { + checkmark.SetPoint( Point( nRect.Left(), nRect.Bottom()-n3 ), 0 ); + checkmark.SetPoint( Point( nRect.Left()+n3, nRect.Bottom()), 1 ); + checkmark.SetPoint( Point( nRect.Right(), nRect.Top()+n3 ), 2 ); + checkmark.SetPoint( Point( nRect.Right(), nRect.Top()+n3 + 1 ), 3 ); + checkmark.SetPoint( Point( nRect.Left()+n3, nRect.Bottom() + 1 ), 4 ); + checkmark.SetPoint( Point( nRect.Left(), nRect.Bottom()-n3 + 1 ), 5 ); + } + pDev->DrawPolygon( checkmark ); + } + break; + + case SymbolType::FLOAT: + nRect.AdjustRight( -n4 ); + nRect.AdjustTop(n4+1 ); + pDev->DrawRect( tools::Rectangle( nRect.Left(), nRect.Top(), + nRect.Right(), nRect.Top()+n8 ) ); + pDev->DrawLine( Point( nRect.Left(), nRect.Top()+n8 ), + Point( nRect.Left(), nRect.Bottom() ) ); + pDev->DrawLine( Point( nRect.Left(), nRect.Bottom() ), + Point( nRect.Right(), nRect.Bottom() ) ); + pDev->DrawLine( Point( nRect.Right(), nRect.Top()+n8 ), + Point( nRect.Right(), nRect.Bottom() ) ); + nRect.AdjustRight(n4 ); + nRect.AdjustTop( -(n4+1) ); + nRect.AdjustLeft(n4 ); + nRect.AdjustBottom( -(n4+1) ); + pDev->DrawRect( tools::Rectangle( nRect.Left(), nRect.Top(), + nRect.Right(), nRect.Top()+n8 ) ); + pDev->DrawLine( Point( nRect.Left(), nRect.Top()+n8 ), + Point( nRect.Left(), nRect.Bottom() ) ); + pDev->DrawLine( Point( nRect.Left(), nRect.Bottom() ), + Point( nRect.Right(), nRect.Bottom() ) ); + pDev->DrawLine( Point( nRect.Right(), nRect.Top()+n8 ), + Point( nRect.Right(), nRect.Bottom() ) ); + break; + + case SymbolType::DOCK: + pDev->DrawLine( Point( nRect.Left(), nRect.Top() ), + Point( nRect.Right(), nRect.Top() ) ); + pDev->DrawLine( Point( nRect.Left(), nRect.Top() ), + Point( nRect.Left(), nRect.Bottom() ) ); + pDev->DrawLine( Point( nRect.Left(), nRect.Bottom() ), + Point( nRect.Right(), nRect.Bottom() ) ); + pDev->DrawLine( Point( nRect.Right(), nRect.Top() ), + Point( nRect.Right(), nRect.Bottom() ) ); + break; + + case SymbolType::HIDE: + pDev->DrawRect( tools::Rectangle( nRect.Left()+n8, nRect.Bottom()-n8, + nRect.Right()-n8, nRect.Bottom() ) ); + break; + + case SymbolType::PLUS: + pDev->DrawRect( tools::Rectangle( nRect.Left(), aCenter.Y()-n16, + nRect.Right(), aCenter.Y()+n16 ) ); + pDev->DrawRect( tools::Rectangle( aCenter.X()-n16, nRect.Top(), + aCenter.X()+n16, nRect.Bottom() ) ); + break; + case SymbolType::DONTKNOW: + case SymbolType::IMAGE: + case SymbolType::HELP: break; + } +} + +void ImplDrawDPILineRect( OutputDevice *const pDev, tools::Rectangle& rRect, + const Color *const pColor, const bool bRound = false ) +{ + tools::Long nLineWidth = pDev->GetDPIX()/300; + tools::Long nLineHeight = pDev->GetDPIY()/300; + if ( !nLineWidth ) + nLineWidth = 1; + if ( !nLineHeight ) + nLineHeight = 1; + + if ( pColor ) + { + if ( (nLineWidth == 1) && (nLineHeight == 1) ) + { + pDev->SetLineColor( *pColor ); + if( bRound ) + { + pDev->DrawLine( Point( rRect.Left()+1, rRect.Top()), Point( rRect.Right()-1, rRect.Top()) ); + pDev->DrawLine( Point( rRect.Left()+1, rRect.Bottom()), Point( rRect.Right()-1, rRect.Bottom()) ); + pDev->DrawLine( Point( rRect.Left(), rRect.Top()+1), Point( rRect.Left(), rRect.Bottom()-1) ); + pDev->DrawLine( Point( rRect.Right(), rRect.Top()+1), Point( rRect.Right(), rRect.Bottom()-1) ); + } + else + { + pDev->SetFillColor(); + pDev->DrawRect( rRect ); + } + } + else + { + const tools::Long nWidth = rRect.GetWidth(); + const tools::Long nHeight = rRect.GetHeight(); + pDev->SetLineColor(); + pDev->SetFillColor( *pColor ); + pDev->DrawRect( tools::Rectangle( rRect.TopLeft(), Size( nWidth, nLineHeight ) ) ); + pDev->DrawRect( tools::Rectangle( rRect.TopLeft(), Size( nLineWidth, nHeight ) ) ); + pDev->DrawRect( tools::Rectangle( Point( rRect.Left(), rRect.Bottom()-nLineHeight ), + Size( nWidth, nLineHeight ) ) ); + pDev->DrawRect( tools::Rectangle( Point( rRect.Right()-nLineWidth, rRect.Top() ), + Size( nLineWidth, nHeight ) ) ); + } + } + + rRect.AdjustLeft(nLineWidth ); + rRect.AdjustTop(nLineHeight ); + rRect.AdjustRight( -nLineWidth ); + rRect.AdjustBottom( -nLineHeight ); +} + +void ImplDraw2ColorFrame( OutputDevice *const pDev, tools::Rectangle& rRect, + const Color& rLeftTopColor, const Color& rRightBottomColor ) +{ + pDev->SetLineColor( rLeftTopColor ); + pDev->DrawLine( rRect.TopLeft(), rRect.BottomLeft() ); + pDev->DrawLine( rRect.TopLeft(), rRect.TopRight() ); + pDev->SetLineColor( rRightBottomColor ); + pDev->DrawLine( rRect.BottomLeft(), rRect.BottomRight() ); + pDev->DrawLine( rRect.TopRight(), rRect.BottomRight() ); + + // reduce drawing area + rRect.AdjustLeft( 1 ); + rRect.AdjustTop( 1 ); + rRect.AdjustRight( -1 ); + rRect.AdjustBottom( -1 ); +} + +void ImplDrawButton( OutputDevice *const pDev, tools::Rectangle aFillRect, + const DrawButtonFlags nStyle ) +{ + const StyleSettings& rStyleSettings = pDev->GetSettings().GetStyleSettings(); + + if ( (nStyle & DrawButtonFlags::Mono) || + (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) ) + { + const Color aBlackColor(COL_BLACK); + + if ( nStyle & DrawButtonFlags::Default ) + { + // default selection shows a wider border + ImplDrawDPILineRect( pDev, aFillRect, &aBlackColor ); + } + + ImplDrawDPILineRect( pDev, aFillRect, &aBlackColor ); + + Size aBrdSize(pDev->GetButtonBorderSize()); + + pDev->SetLineColor(); + pDev->SetFillColor( aBlackColor ); + const tools::Rectangle aOrigFillRect(aFillRect); + if ( nStyle & (DrawButtonFlags::Pressed | DrawButtonFlags::Checked) ) + { + // shrink fill rect + aFillRect.AdjustLeft(aBrdSize.Width() ); + aFillRect.AdjustTop(aBrdSize.Height() ); + // draw top and left borders (aOrigFillRect-aFillRect) + pDev->DrawRect( tools::Rectangle( aOrigFillRect.Left(), aOrigFillRect.Top(), + aOrigFillRect.Right(), aFillRect.Top()-1 ) ); + pDev->DrawRect( tools::Rectangle( aOrigFillRect.Left(), aOrigFillRect.Top(), + aFillRect.Left()-1, aOrigFillRect.Bottom() ) ); + } + else + { + // shrink fill rect + aFillRect.AdjustRight( -(aBrdSize.Width()) ); + aFillRect.AdjustBottom( -(aBrdSize.Height()) ); + // draw bottom and right borders (aOrigFillRect-aFillRect) + pDev->DrawRect( tools::Rectangle( aOrigFillRect.Left(), aFillRect.Bottom()+1, + aOrigFillRect.Right(), aOrigFillRect.Bottom() ) ); + pDev->DrawRect( tools::Rectangle( aFillRect.Right()+1, aOrigFillRect.Top(), + aOrigFillRect.Right(), aOrigFillRect.Bottom() ) ); + } + + // Hack: in monochrome mode on printers we like to have grey buttons + pDev->SetFillColor(pDev->GetMonochromeButtonColor()); + pDev->DrawRect( aFillRect ); + } + else + { + if ( nStyle & DrawButtonFlags::Default ) + { + const Color aDefBtnColor = rStyleSettings.GetDarkShadowColor(); + ImplDrawDPILineRect( pDev, aFillRect, &aDefBtnColor ); + } + + if ( nStyle & DrawButtonFlags::NoLeftLightBorder ) + { + pDev->SetLineColor( rStyleSettings.GetLightBorderColor() ); + pDev->DrawLine( Point( aFillRect.Left(), aFillRect.Top() ), + Point( aFillRect.Left(), aFillRect.Bottom() ) ); + aFillRect.AdjustLeft( 1 ); + } + + Color aColor1; + Color aColor2; + if ( nStyle & (DrawButtonFlags::Pressed | DrawButtonFlags::Checked) ) + { + aColor1 = rStyleSettings.GetDarkShadowColor(); + aColor2 = rStyleSettings.GetLightColor(); + } + else + { + if ( nStyle & DrawButtonFlags::NoLightBorder ) + aColor1 = rStyleSettings.GetLightBorderColor(); + else + aColor1 = rStyleSettings.GetLightColor(); + if ( (nStyle & BUTTON_DRAW_FLATTEST) == DrawButtonFlags::Flat ) + aColor2 = rStyleSettings.GetShadowColor(); + else + aColor2 = rStyleSettings.GetDarkShadowColor(); + } + + ImplDraw2ColorFrame( pDev, aFillRect, aColor1, aColor2 ); + + if ( (nStyle & BUTTON_DRAW_FLATTEST) != DrawButtonFlags::Flat ) + { + if ( nStyle & (DrawButtonFlags::Pressed | DrawButtonFlags::Checked) ) + { + aColor1 = rStyleSettings.GetShadowColor(); + aColor2 = rStyleSettings.GetLightBorderColor(); + } + else + { + if ( nStyle & DrawButtonFlags::NoLightBorder ) + aColor1 = rStyleSettings.GetLightColor(); + else + aColor1 = rStyleSettings.GetLightBorderColor(); + aColor2 = rStyleSettings.GetShadowColor(); + } + ImplDraw2ColorFrame( pDev, aFillRect, aColor1, aColor2 ); + } + + pDev->SetLineColor(); + if ( nStyle & (DrawButtonFlags::Checked | DrawButtonFlags::DontKnow) ) + pDev->SetFillColor( rStyleSettings.GetCheckedColor() ); + else + pDev->SetFillColor( rStyleSettings.GetFaceColor() ); + pDev->DrawRect( aFillRect ); + } +} + +void ImplDrawFrame( OutputDevice *const pDev, tools::Rectangle& rRect, + const StyleSettings& rStyleSettings, DrawFrameStyle nStyle, DrawFrameFlags nFlags ) +{ + vcl::Window * pWin = pDev->GetOwnerWindow(); + + const bool bMenuStyle(nFlags & DrawFrameFlags::Menu); + + // UseFlatBorders disables 3D style for all frames except menus + // menus may use different border colors (eg on XP) + // normal frames will be drawn using the shadow color + // whereas window frame borders will use black + bool bFlatBorders = !bMenuStyle && rStyleSettings.GetUseFlatBorders(); + + // no flat borders for standard VCL controls (ie formcontrols that keep their classic look) + // will not affect frame windows (like dropdowns) + if( bFlatBorders && pWin && pWin->GetType() == WindowType::BORDERWINDOW && (pWin != pWin->ImplGetFrameWindow()) ) + { + // check for formcontrol, i.e., a control without NWF enabled + Control *const pControl = dynamic_cast< Control* >( pWin->GetWindow( GetWindowType::Client ) ); + if( !pControl || !pControl->IsNativeWidgetEnabled() ) + bFlatBorders = false; + } + + const bool bNoDraw(nFlags & DrawFrameFlags::NoDraw); + + if ( (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) || + (pDev->GetOutDevType() == OUTDEV_PRINTER) || + bFlatBorders ) + nFlags |= DrawFrameFlags::Mono; + + if( nStyle != DrawFrameStyle::NWF && + pWin && pWin->IsNativeControlSupported(ControlType::Frame, ControlPart::Border) ) + { + tools::Long nControlFlags = static_cast<tools::Long>(nStyle); + nControlFlags |= static_cast<tools::Long>(nFlags); + nControlFlags |= static_cast<tools::Long>(pWin->GetType() == WindowType::BORDERWINDOW ? + DrawFrameFlags::BorderWindowBorder : DrawFrameFlags::NONE); + ImplControlValue aControlValue( nControlFlags ); + + tools::Rectangle aBound, aContent; + tools::Rectangle aNatRgn( rRect ); + if( pWin->GetNativeControlRegion(ControlType::Frame, ControlPart::Border, + aNatRgn, ControlState::NONE, aControlValue, aBound, aContent) ) + { + // if bNoDraw is true then don't call the drawing routine + // but just update the target rectangle + if( bNoDraw || + pWin->GetOutDev()->DrawNativeControl( ControlType::Frame, ControlPart::Border, aBound, ControlState::ENABLED, + aControlValue, OUString()) ) + { + rRect = aContent; + return; + } + } + } + + if ( nFlags & DrawFrameFlags::Mono ) + { + // no round corners for window frame borders + const bool bRound = bFlatBorders && !(nFlags & DrawFrameFlags::WindowBorder); + + if ( bNoDraw ) + { + ImplDrawDPILineRect( pDev, rRect, nullptr, bRound ); + } + else + { + Color aColor = bRound ? rStyleSettings.GetShadowColor() + : pDev->GetSettings().GetStyleSettings().GetMonoColor(); + // when the MonoColor wasn't set, check face color + if ( + (bRound && aColor.IsDark()) || + ( + (aColor == COL_BLACK) && + pDev->GetSettings().GetStyleSettings().GetFaceColor().IsDark() + ) + ) + { + aColor = COL_WHITE; + } + ImplDrawDPILineRect( pDev, rRect, &aColor, bRound ); + } + } + else + { + if ( bNoDraw ) + { + switch ( nStyle ) + { + case DrawFrameStyle::In: + case DrawFrameStyle::Out: + rRect.AdjustLeft( 1 ); + rRect.AdjustTop( 1 ); + rRect.AdjustRight( -1 ); + rRect.AdjustBottom( -1 ); + break; + + case DrawFrameStyle::Group: + case DrawFrameStyle::DoubleIn: + case DrawFrameStyle::DoubleOut: + rRect.AdjustLeft(2 ); + rRect.AdjustTop(2 ); + rRect.AdjustRight( -2 ); + rRect.AdjustBottom( -2 ); + break; + + case DrawFrameStyle::NWF: + // enough space for the native rendering + rRect.AdjustLeft(4 ); + rRect.AdjustTop(4 ); + rRect.AdjustRight( -4 ); + rRect.AdjustBottom( -4 ); + break; + default: break; + } + } + else + { + switch ( nStyle ) + { + case DrawFrameStyle::Group: + pDev->SetFillColor(); + pDev->SetLineColor( rStyleSettings.GetLightColor() ); + pDev->DrawRect( tools::Rectangle( rRect.Left()+1, rRect.Top()+1, + rRect.Right(), rRect.Bottom() ) ); + pDev->SetLineColor( rStyleSettings.GetShadowColor() ); + pDev->DrawRect( tools::Rectangle( rRect.Left(), rRect.Top(), + rRect.Right()-1, rRect.Bottom()-1 ) ); + + // adjust target rectangle + rRect.AdjustLeft(2 ); + rRect.AdjustTop(2 ); + rRect.AdjustRight( -2 ); + rRect.AdjustBottom( -2 ); + break; + + case DrawFrameStyle::In: + ImplDraw2ColorFrame( pDev, rRect, + rStyleSettings.GetShadowColor(), + rStyleSettings.GetLightColor() ); + break; + + case DrawFrameStyle::Out: + ImplDraw2ColorFrame( pDev, rRect, + rStyleSettings.GetLightColor(), + rStyleSettings.GetShadowColor() ); + break; + + case DrawFrameStyle::DoubleIn: + if( bFlatBorders ) + { + // no 3d effect + ImplDraw2ColorFrame( pDev, rRect, + rStyleSettings.GetShadowColor(), + rStyleSettings.GetShadowColor() ); + ImplDraw2ColorFrame( pDev, rRect, + rStyleSettings.GetFaceColor(), + rStyleSettings.GetFaceColor() ); + } + else + { + ImplDraw2ColorFrame( pDev, rRect, + rStyleSettings.GetShadowColor(), + rStyleSettings.GetLightColor() ); + ImplDraw2ColorFrame( pDev, rRect, + rStyleSettings.GetDarkShadowColor(), + rStyleSettings.GetLightBorderColor() ); + } + break; + + case DrawFrameStyle::DoubleOut: + if( bMenuStyle ) + { + ImplDraw2ColorFrame( pDev, rRect, + rStyleSettings.GetMenuBorderColor(), + rStyleSettings.GetDarkShadowColor() ); + if ( !rStyleSettings.GetUseFlatMenus() ) + { + ImplDraw2ColorFrame( pDev, rRect, + rStyleSettings.GetLightColor(), + rStyleSettings.GetShadowColor() ); + } + } + else + { + ImplDraw2ColorFrame( pDev, rRect, + bFlatBorders ? // no 3d effect + rStyleSettings.GetDarkShadowColor() : + rStyleSettings.GetLightBorderColor(), + rStyleSettings.GetDarkShadowColor() ); + ImplDraw2ColorFrame( pDev, rRect, + rStyleSettings.GetLightColor(), + rStyleSettings.GetShadowColor() ); + } + break; + + case DrawFrameStyle::NWF: + // no rendering, just enough space for the native rendering + rRect.AdjustLeft(4 ); + rRect.AdjustTop(4 ); + rRect.AdjustRight( -4 ); + rRect.AdjustBottom( -4 ); + break; + default: break; + } + } + } +} + +} // end anonymous namespace + +DecorationView::DecorationView(OutputDevice* pOutDev) : + mpOutDev(pOutDev) +{} + +void DecorationView::DrawSymbol( const tools::Rectangle& rRect, SymbolType eType, + const Color& rColor, DrawSymbolFlags nStyle ) +{ + const StyleSettings& rStyleSettings = mpOutDev->GetSettings().GetStyleSettings(); + const tools::Rectangle aRect = mpOutDev->LogicToPixel( rRect ); + const Color aOldLineColor = mpOutDev->GetLineColor(); + const Color aOldFillColor = mpOutDev->GetFillColor(); + const bool bOldMapMode = mpOutDev->IsMapModeEnabled(); + Color nColor(rColor); + mpOutDev->EnableMapMode( false ); + + if ( (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) || + (mpOutDev->GetOutDevType() == OUTDEV_PRINTER) ) + nStyle |= DrawSymbolFlags::Mono; + + if ( nStyle & DrawSymbolFlags::Mono ) + { + // Monochrome: set color to black if enabled, to gray if disabled + nColor = ( nStyle & DrawSymbolFlags::Disable ) ? COL_GRAY : COL_BLACK; + } + else + { + if ( nStyle & DrawSymbolFlags::Disable ) + { + // Draw shifted and brighter symbol for embossed look + mpOutDev->SetLineColor( rStyleSettings.GetLightColor() ); + mpOutDev->SetFillColor( rStyleSettings.GetLightColor() ); + ImplDrawSymbol( mpOutDev, aRect + Point(1, 1) , eType ); + nColor = rStyleSettings.GetShadowColor(); + } + } + + // Set selected color and draw the symbol + mpOutDev->SetLineColor( nColor ); + mpOutDev->SetFillColor( nColor ); + ImplDrawSymbol( mpOutDev, aRect, eType ); + + // Restore previous settings + mpOutDev->SetLineColor( aOldLineColor ); + mpOutDev->SetFillColor( aOldFillColor ); + mpOutDev->EnableMapMode( bOldMapMode ); +} + +void DecorationView::DrawFrame( const tools::Rectangle& rRect, + const Color& rLeftTopColor, + const Color& rRightBottomColor ) +{ + tools::Rectangle aRect = mpOutDev->LogicToPixel( rRect ); + const Color aOldLineColor = mpOutDev->GetLineColor(); + const bool bOldMapMode = mpOutDev->IsMapModeEnabled(); + mpOutDev->EnableMapMode( false ); + ImplDraw2ColorFrame( mpOutDev, aRect, rLeftTopColor, rRightBottomColor ); + mpOutDev->SetLineColor( aOldLineColor ); + mpOutDev->EnableMapMode( bOldMapMode ); +} + +void DecorationView::DrawHighlightFrame( const tools::Rectangle& rRect, + DrawHighlightFrameStyle nStyle ) +{ + const StyleSettings& rStyleSettings = mpOutDev->GetSettings().GetStyleSettings(); + Color aLightColor = rStyleSettings.GetLightColor(); + Color aShadowColor = rStyleSettings.GetShadowColor(); + + if ( (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) || + (mpOutDev->GetOutDevType() == OUTDEV_PRINTER) ) + { + aLightColor = COL_BLACK; + aShadowColor = COL_BLACK; + } + else + { + Wallpaper aBackground = mpOutDev->GetBackground(); + if ( aBackground.IsBitmap() || aBackground.IsGradient() ) + { + aLightColor = rStyleSettings.GetFaceColor(); + aShadowColor = COL_BLACK; + } + else + { + Color aBackColor = aBackground.GetColor(); + if ( (aLightColor.GetColorError( aBackColor ) < 96) || + (aShadowColor.GetColorError( aBackColor ) < 96) ) + { + aLightColor = COL_WHITE; + aShadowColor = COL_BLACK; + + if ( aLightColor.GetColorError( aBackColor ) < 96 ) + aLightColor.DecreaseLuminance( 64 ); + if ( aShadowColor.GetColorError( aBackColor ) < 96 ) + aShadowColor.IncreaseLuminance( 64 ); + } + } + } + + if ( nStyle == DrawHighlightFrameStyle::In ) + { + Color aTempColor = aLightColor; + aLightColor = aShadowColor; + aShadowColor = aTempColor; + } + + DrawFrame( rRect, aLightColor, aShadowColor ); +} + +tools::Rectangle DecorationView::DrawFrame( const tools::Rectangle& rRect, DrawFrameStyle nStyle, DrawFrameFlags nFlags ) +{ + tools::Rectangle aRect = rRect; + bool bOldMap = mpOutDev->IsMapModeEnabled(); + if ( bOldMap ) + { + aRect = mpOutDev->LogicToPixel( aRect ); + mpOutDev->EnableMapMode( false ); + } + + if ( !rRect.IsEmpty() ) + { + if ( nFlags & DrawFrameFlags::NoDraw ) + ImplDrawFrame( mpOutDev, aRect, mpOutDev->GetSettings().GetStyleSettings(), nStyle, nFlags ); + else + { + Color aOldLineColor = mpOutDev->GetLineColor(); + Color aOldFillColor = mpOutDev->GetFillColor(); + ImplDrawFrame( mpOutDev, aRect, mpOutDev->GetSettings().GetStyleSettings(), nStyle, nFlags ); + mpOutDev->SetLineColor( aOldLineColor ); + mpOutDev->SetFillColor( aOldFillColor ); + } + } + + if ( bOldMap ) + { + mpOutDev->EnableMapMode( bOldMap ); + aRect = mpOutDev->PixelToLogic( aRect ); + } + + return aRect; +} + +tools::Rectangle DecorationView::DrawButton( const tools::Rectangle& rRect, DrawButtonFlags nStyle ) +{ + if ( rRect.IsEmpty() ) + { + return rRect; + } + + tools::Rectangle aRect = rRect; + const bool bOldMap = mpOutDev->IsMapModeEnabled(); + + if ( bOldMap ) + { + aRect = mpOutDev->LogicToPixel( aRect ); + mpOutDev->EnableMapMode( false ); + } + + const Color aOldLineColor = mpOutDev->GetLineColor(); + const Color aOldFillColor = mpOutDev->GetFillColor(); + ImplDrawButton( mpOutDev, aRect, nStyle ); + mpOutDev->SetLineColor( aOldLineColor ); + mpOutDev->SetFillColor( aOldFillColor ); + + // keep border free, although it is used at default representation + aRect.AdjustLeft( 1 ); + aRect.AdjustTop( 1 ); + aRect.AdjustRight( -1 ); + aRect.AdjustBottom( -1 ); + + if ( nStyle & DrawButtonFlags::NoLightBorder ) + { + aRect.AdjustLeft( 1 ); + aRect.AdjustTop( 1 ); + } + else if ( nStyle & DrawButtonFlags::NoLeftLightBorder ) + { + aRect.AdjustLeft( 1 ); + } + + if ( nStyle & DrawButtonFlags::Pressed ) + { + if ( (aRect.GetHeight() > 10) && (aRect.GetWidth() > 10) ) + { + aRect.AdjustLeft(4 ); + aRect.AdjustTop(4 ); + aRect.AdjustRight( -1 ); + aRect.AdjustBottom( -1 ); + } + else + { + aRect.AdjustLeft(3 ); + aRect.AdjustTop(3 ); + aRect.AdjustRight( -2 ); + aRect.AdjustBottom( -2 ); + } + } + else if ( nStyle & DrawButtonFlags::Checked ) + { + aRect.AdjustLeft(3 ); + aRect.AdjustTop(3 ); + aRect.AdjustRight( -2 ); + aRect.AdjustBottom( -2 ); + } + else + { + aRect.AdjustLeft(2 ); + aRect.AdjustTop(2 ); + aRect.AdjustRight( -3 ); + aRect.AdjustBottom( -3 ); + } + + if ( bOldMap ) + { + mpOutDev->EnableMapMode( bOldMap ); + aRect = mpOutDev->PixelToLogic( aRect ); + } + + return aRect; +} + +void DecorationView::DrawSeparator( const Point& rStart, const Point& rStop, bool bVertical ) +{ + Point aStart( rStart ), aStop( rStop ); + const StyleSettings& rStyleSettings = mpOutDev->GetSettings().GetStyleSettings(); + vcl::Window *const pWin = mpOutDev->GetOwnerWindow(); + if(pWin) + { + ControlPart nPart = ( bVertical ? ControlPart::SeparatorVert : ControlPart::SeparatorHorz ); + bool nativeSupported = pWin->IsNativeControlSupported( ControlType::Fixedline, nPart ); + ImplControlValue aValue; + tools::Rectangle aRect(rStart,rStop); + if(nativeSupported && pWin->GetOutDev()->DrawNativeControl(ControlType::Fixedline,nPart,aRect,ControlState::NONE,aValue,OUString())) + return; + } + + mpOutDev->Push( vcl::PushFlags::LINECOLOR ); + if ( rStyleSettings.GetOptions() & StyleSettingsOptions::Mono ) + mpOutDev->SetLineColor( COL_BLACK ); + else + mpOutDev->SetLineColor( rStyleSettings.GetShadowColor() ); + + mpOutDev->DrawLine( aStart, aStop ); + if ( !(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) ) + { + mpOutDev->SetLineColor( rStyleSettings.GetLightColor() ); + if( bVertical ) + { + aStart.AdjustX( 1 ); + aStop.AdjustX( 1 ); + } + else + { + aStart.AdjustY( 1 ); + aStop.AdjustY( 1 ); + } + mpOutDev->DrawLine( aStart, aStop ); + } + mpOutDev->Pop(); +} + +void DecorationView::DrawHandle(const tools::Rectangle& rRect) +{ + const StyleSettings& rStyleSettings = mpOutDev->GetSettings().GetStyleSettings(); + + Size aOutputSize = rRect.GetSize(); + + mpOutDev->SetLineColor(rStyleSettings.GetDarkShadowColor()); + mpOutDev->SetFillColor(rStyleSettings.GetDarkShadowColor()); + + const sal_Int32 nNumberOfPoints = 3; + + tools::Long nHalfWidth = aOutputSize.Width() / 2.0f; + + float fDistance = aOutputSize.Height(); + fDistance /= (nNumberOfPoints + 1); + + tools::Long nRadius = aOutputSize.Width(); + nRadius /= (nNumberOfPoints + 2); + + for (tools::Long i = 1; i <= nNumberOfPoints; i++) + { + tools::Rectangle aLocation(nHalfWidth - nRadius, + round(fDistance * i) - nRadius, + nHalfWidth + nRadius, + round(fDistance * i) + nRadius); + mpOutDev->DrawEllipse(aLocation); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/dialog.cxx b/vcl/source/window/dialog.cxx new file mode 100644 index 000000000..b1f18dc50 --- /dev/null +++ b/vcl/source/window/dialog.cxx @@ -0,0 +1,1711 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_feature_desktop.h> + +#ifdef IOS +#include <premac.h> +#include <UIKit/UIKit.h> +#include <postmac.h> +#endif + +#include <com/sun/star/frame/theGlobalEventBroadcaster.hpp> +#include <comphelper/lok.hxx> +#include <comphelper/scopeguard.hxx> +#include <comphelper/processfactory.hxx> +#include <officecfg/Office/Common.hxx> +#include <osl/diagnose.h> + +#include <svdata.hxx> +#include <window.h> +#include <accel.hxx> +#include <brdwin.hxx> + +#include <rtl/bootstrap.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> + +#include <vcl/abstdlg.hxx> +#include <vcl/builder.hxx> +#include <vcl/toolkit/floatwin.hxx> +#include <vcl/layout.hxx> +#include <vcl/svapp.hxx> +#include <vcl/event.hxx> +#include <vcl/locktoplevels.hxx> +#include <vcl/wrkwin.hxx> +#include <vcl/toolkit/button.hxx> +#include <vcl/mnemonic.hxx> +#include <vcl/toolkit/dialog.hxx> +#include <vcl/dialoghelper.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weld.hxx> +#include <vcl/uitest/uiobject.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/IDialogRenderable.hxx> +#include <messagedialog.hxx> +#include <salframe.hxx> +#include <tools/json_writer.hxx> + +#include <iostream> +#include <stack> +#include <utility> +#include <vector> + +static OString ImplGetDialogText( Dialog* pDialog ) +{ + OUString aErrorStr(pDialog->GetText()); + + OUString sMessage; + if (MessageDialog* pMessDialog = dynamic_cast<MessageDialog*>(pDialog)) + { + sMessage = pMessDialog->get_primary_text(); + } + + if (!sMessage.isEmpty()) + { + aErrorStr += ", " + sMessage; + } + return OUStringToOString(aErrorStr, RTL_TEXTENCODING_UTF8); +} + +static bool ImplIsMnemonicCtrl( vcl::Window* pWindow ) +{ + if( ! pWindow->GetSettings().GetStyleSettings().GetAutoMnemonic() ) + return false; + + if ( (pWindow->GetType() == WindowType::RADIOBUTTON) || + (pWindow->GetType() == WindowType::CHECKBOX) || + (pWindow->GetType() == WindowType::TRISTATEBOX) || + (pWindow->GetType() == WindowType::PUSHBUTTON) ) + return true; + + if ( pWindow->GetType() == WindowType::FIXEDTEXT ) + { + FixedText *pText = static_cast<FixedText*>(pWindow); + if (pText->get_mnemonic_widget()) + return true; + //This is the legacy pre-layout logic which we retain + //until we can be sure we can remove it + if (pWindow->GetStyle() & WB_NOLABEL) + return false; + vcl::Window* pNextWindow = pWindow->GetWindow( GetWindowType::Next ); + if ( !pNextWindow ) + return false; + pNextWindow = pNextWindow->GetWindow( GetWindowType::Client ); + return !(!(pNextWindow->GetStyle() & WB_TABSTOP) || + (pNextWindow->GetType() == WindowType::FIXEDTEXT) || + (pNextWindow->GetType() == WindowType::GROUPBOX) || + (pNextWindow->GetType() == WindowType::RADIOBUTTON) || + (pNextWindow->GetType() == WindowType::CHECKBOX) || + (pNextWindow->GetType() == WindowType::TRISTATEBOX) || + (pNextWindow->GetType() == WindowType::PUSHBUTTON)); + } + + return false; +} + +// Called by native error dialog popup implementations +void ImplHideSplash() +{ + ImplSVData* pSVData = ImplGetSVData(); + if( pSVData->mpIntroWindow ) + pSVData->mpIntroWindow->Hide(); +} + +vcl::Window * nextLogicalChildOfParent(const vcl::Window *pTopLevel, const vcl::Window *pChild) +{ + const vcl::Window *pLastChild = pChild; + + if (pChild->GetType() == WindowType::SCROLLWINDOW) + pChild = static_cast<const VclScrolledWindow*>(pChild)->get_child(); + else if (isContainerWindow(*pChild)) + pChild = pChild->GetWindow(GetWindowType::FirstChild); + else + pChild = pChild->GetWindow(GetWindowType::Next); + + while (!pChild) + { + vcl::Window *pParent = pLastChild->GetParent(); + if (!pParent) + return nullptr; + if (pParent == pTopLevel) + return nullptr; + pLastChild = pParent; + pChild = pParent->GetWindow(GetWindowType::Next); + } + + if (isContainerWindow(*pChild)) + pChild = nextLogicalChildOfParent(pTopLevel, pChild); + + return const_cast<vcl::Window *>(pChild); +} + +vcl::Window * prevLogicalChildOfParent(const vcl::Window *pTopLevel, const vcl::Window *pChild) +{ + const vcl::Window *pLastChild = pChild; + + if (pChild->GetType() == WindowType::SCROLLWINDOW) + pChild = static_cast<const VclScrolledWindow*>(pChild)->get_child(); + else if (isContainerWindow(*pChild)) + pChild = pChild->GetWindow(GetWindowType::LastChild); + else + pChild = pChild->GetWindow(GetWindowType::Prev); + + while (!pChild) + { + vcl::Window *pParent = pLastChild->GetParent(); + if (!pParent) + return nullptr; + if (pParent == pTopLevel) + return nullptr; + pLastChild = pParent; + pChild = pParent->GetWindow(GetWindowType::Prev); + } + + if (isContainerWindow(*pChild)) + pChild = prevLogicalChildOfParent(pTopLevel, pChild); + + return const_cast<vcl::Window *>(pChild); +} + +vcl::Window * firstLogicalChildOfParent(const vcl::Window *pTopLevel) +{ + const vcl::Window *pChild = pTopLevel->GetWindow(GetWindowType::FirstChild); + if (pChild && isContainerWindow(*pChild)) + pChild = nextLogicalChildOfParent(pTopLevel, pChild); + return const_cast<vcl::Window *>(pChild); +} + +vcl::Window * lastLogicalChildOfParent(const vcl::Window *pTopLevel) +{ + const vcl::Window *pChild = pTopLevel->GetWindow(GetWindowType::LastChild); + if (pChild && isContainerWindow(*pChild)) + pChild = prevLogicalChildOfParent(pTopLevel, pChild); + return const_cast<vcl::Window *>(pChild); +} + +void GenerateAutoMnemonicsOnHierarchy(const vcl::Window* pWindow) +{ + MnemonicGenerator aMnemonicGenerator; + vcl::Window* pGetChild; + vcl::Window* pChild; + + // register the assigned mnemonics + pGetChild = pWindow->GetWindow( GetWindowType::FirstChild ); + while ( pGetChild ) + { + pChild = pGetChild->ImplGetWindow(); + aMnemonicGenerator.RegisterMnemonic( pChild->GetText() ); + pGetChild = nextLogicalChildOfParent(pWindow, pGetChild); + } + + // take the Controls of the dialog into account for TabPages + if ( pWindow->GetType() == WindowType::TABPAGE ) + { + vcl::Window* pParent = pWindow->GetParent(); + if (pParent && pParent->GetType() == WindowType::TABCONTROL ) + pParent = pParent->GetParent(); + + if (pParent && (pParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL ) + { + pGetChild = pParent->GetWindow( GetWindowType::FirstChild ); + while ( pGetChild ) + { + pChild = pGetChild->ImplGetWindow(); + aMnemonicGenerator.RegisterMnemonic( pChild->GetText() ); + pGetChild = nextLogicalChildOfParent(pWindow, pGetChild); + } + } + } + + // assign mnemonics to Controls which have none + pGetChild = pWindow->GetWindow( GetWindowType::FirstChild ); + while ( pGetChild ) + { + pChild = pGetChild->ImplGetWindow(); + if ( ImplIsMnemonicCtrl( pChild ) ) + { + OUString aText = pChild->GetText(); + OUString aNewText = aMnemonicGenerator.CreateMnemonic( aText ); + if ( aText != aNewText ) + pChild->SetText( aNewText ); + } + + pGetChild = nextLogicalChildOfParent(pWindow, pGetChild); + } +} + +static VclButtonBox* getActionArea(Dialog const *pDialog) +{ + VclButtonBox *pButtonBox = nullptr; + if (pDialog->isLayoutEnabled()) + { + vcl::Window *pBox = pDialog->GetWindow(GetWindowType::FirstChild); + vcl::Window *pChild = pBox->GetWindow(GetWindowType::LastChild); + while (pChild) + { + pButtonBox = dynamic_cast<VclButtonBox*>(pChild); + if (pButtonBox) + break; + pChild = pChild->GetWindow(GetWindowType::Prev); + } + } + return pButtonBox; +} + +static vcl::Window* getActionAreaButtonList(Dialog const *pDialog) +{ + VclButtonBox* pButtonBox = getActionArea(pDialog); + if (pButtonBox) + return pButtonBox->GetWindow(GetWindowType::FirstChild); + return pDialog->GetWindow(GetWindowType::FirstChild); +} + +static PushButton* ImplGetDefaultButton( Dialog const * pDialog ) +{ + vcl::Window* pChild = getActionAreaButtonList(pDialog); + while ( pChild ) + { + if ( pChild->ImplIsPushButton() ) + { + PushButton* pPushButton = static_cast<PushButton*>(pChild); + if ( pPushButton->ImplIsDefButton() ) + return pPushButton; + } + + pChild = pChild->GetWindow( GetWindowType::Next ); + } + + return nullptr; +} + +static PushButton* ImplGetOKButton( Dialog const * pDialog ) +{ + vcl::Window* pChild = getActionAreaButtonList(pDialog); + while ( pChild ) + { + if ( pChild->GetType() == WindowType::OKBUTTON ) + return static_cast<PushButton*>(pChild); + + pChild = pChild->GetWindow( GetWindowType::Next ); + } + + return nullptr; +} + +static PushButton* ImplGetCancelButton( Dialog const * pDialog ) +{ + vcl::Window* pChild = getActionAreaButtonList(pDialog); + + while ( pChild ) + { + if ( pChild->GetType() == WindowType::CANCELBUTTON ) + return static_cast<PushButton*>(pChild); + + pChild = pChild->GetWindow( GetWindowType::Next ); + } + + return nullptr; +} + +static void ImplMouseAutoPos( Dialog* pDialog ) +{ + MouseSettingsOptions nMouseOptions = pDialog->GetSettings().GetMouseSettings().GetOptions(); + if ( nMouseOptions & MouseSettingsOptions::AutoCenterPos ) + { + Size aSize = pDialog->GetOutputSizePixel(); + pDialog->SetPointerPosPixel( Point( aSize.Width()/2, aSize.Height()/2 ) ); + } + else if ( nMouseOptions & MouseSettingsOptions::AutoDefBtnPos ) + { + vcl::Window* pWindow = ImplGetDefaultButton( pDialog ); + if ( !pWindow ) + pWindow = ImplGetOKButton( pDialog ); + if ( !pWindow ) + pWindow = ImplGetCancelButton( pDialog ); + if ( !pWindow ) + pWindow = pDialog; + Size aSize = pWindow->GetOutputSizePixel(); + pWindow->SetPointerPosPixel( Point( aSize.Width()/2, aSize.Height()/2 ) ); + } +} + +struct DialogImpl +{ + std::vector<VclPtr<PushButton>> maOwnedButtons; + std::map<VclPtr<vcl::Window>, short> maResponses; + tools::Long mnResult; + bool mbStartedModal; + VclAbstractDialog::AsyncContext maEndCtx; + Link<const CommandEvent&, bool> m_aPopupMenuHdl; + Link<void*, vcl::ILibreOfficeKitNotifier*> m_aInstallLOKNotifierHdl; + bool m_bLOKTunneling; + + DialogImpl() : mnResult( -1 ), mbStartedModal( false ), m_bLOKTunneling( true ) {} + +#ifndef NDEBUG + short get_response(vcl::Window *pWindow) const + { + auto aFind = maResponses.find(pWindow); + if (aFind != maResponses.end()) + return aFind->second; + return RET_CANCEL; + } +#endif + + ~DialogImpl() + { + for (VclPtr<PushButton> & pOwnedButton : maOwnedButtons) + pOwnedButton.disposeAndClear(); + } +}; + +void Dialog::disposeOwnedButtons() +{ + for (VclPtr<PushButton> & pOwnedButton : mpDialogImpl->maOwnedButtons) + pOwnedButton.disposeAndClear(); +} + +void Dialog::ImplInitDialogData() +{ + mpWindowImpl->mbDialog = true; + mbInExecute = false; + mbInSyncExecute = false; + mbInClose = false; + mbModalMode = false; + mpContentArea.clear(); + mpActionArea.clear(); + mnMousePositioned = 0; + mpDialogImpl.reset(new DialogImpl); +} + +void Dialog::PixelInvalidate(const tools::Rectangle* pRectangle) +{ + if (!mpDialogImpl->m_bLOKTunneling) + return; + + Window::PixelInvalidate(pRectangle); +} + +vcl::Window* Dialog::GetDefaultParent(WinBits nStyle) +{ + vcl::Window* pParent = Dialog::GetDefDialogParent(); + if (!pParent && !(nStyle & WB_SYSTEMWINDOW)) + pParent = ImplGetSVData()->maFrameData.mpAppWin; + + // If Parent is disabled, then we search for a modal dialog + // in this frame + if (pParent && (!pParent->IsInputEnabled() || pParent->IsInModalMode())) + { + ImplSVData* pSVData = ImplGetSVData(); + auto& rExecuteDialogs = pSVData->mpWinData->mpExecuteDialogs; + auto it = std::find_if(rExecuteDialogs.rbegin(), rExecuteDialogs.rend(), + [&pParent](VclPtr<Dialog>& rDialogPtr) { + return pParent->ImplGetFirstOverlapWindow() && + pParent->ImplGetFirstOverlapWindow()->IsWindowOrChild(rDialogPtr, true) && + rDialogPtr->IsReallyVisible() && rDialogPtr->IsEnabled() && + rDialogPtr->IsInputEnabled() && !rDialogPtr->IsInModalMode(); }); + if (it != rExecuteDialogs.rend()) + pParent = it->get(); + } + + return pParent; +} + +VclPtr<vcl::Window> Dialog::AddBorderWindow(vcl::Window* pParent, WinBits nStyle) +{ + VclPtrInstance<ImplBorderWindow> pBorderWin( pParent, nStyle, BorderWindowStyle::Frame ); + ImplInit( pBorderWin, nStyle & ~WB_BORDER, nullptr ); + pBorderWin->mpWindowImpl->mpClientWindow = this; + pBorderWin->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder ); + mpWindowImpl->mpBorderWindow = pBorderWin; + mpWindowImpl->mpRealParent = pParent; + + return pBorderWin; +} + +void Dialog::ImplInitDialog( vcl::Window* pParent, WinBits nStyle, InitFlag eFlag ) +{ + SystemWindowFlags nSysWinMode = Application::GetSystemWindowMode(); + + if ( !(nStyle & WB_NODIALOGCONTROL) ) + nStyle |= WB_DIALOGCONTROL; + + // Now, all Dialogs are per default system windows !!! + nStyle |= WB_SYSTEMWINDOW; + + if (InitFlag::NoParent == eFlag) + { + pParent = nullptr; + } + else if (!pParent) // parent is NULL: get the default Dialog parent + { + pParent = Dialog::GetDefaultParent(nStyle); + } + + if ( !pParent || (nStyle & WB_SYSTEMWINDOW) || + (pParent->mpWindowImpl->mpFrameData->mbNeedSysWindow && !(nSysWinMode & SystemWindowFlags::NOAUTOMODE)) || + (nSysWinMode & SystemWindowFlags::DIALOG) ) + { + // create window with a small border ? + if ((nStyle & WB_ALLOWMENUBAR) || ((nStyle & (WB_BORDER | WB_NOBORDER | WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE)) == WB_BORDER)) + { + AddBorderWindow(pParent, nStyle); + } + else + { + mpWindowImpl->mbFrame = true; + mpWindowImpl->mbOverlapWin = true; + ImplInit( pParent, (nStyle & (WB_MOVEABLE | WB_SIZEABLE | WB_STANDALONE)) | WB_CLOSEABLE, nullptr ); + // Now set all style bits + mpWindowImpl->mnStyle = nStyle; + } + } + else + { + VclPtrInstance<ImplBorderWindow> pBorderWin( pParent, nStyle, BorderWindowStyle::Overlap ); + ImplInit( pBorderWin, nStyle & ~WB_BORDER, nullptr ); + pBorderWin->mpWindowImpl->mpClientWindow = this; + pBorderWin->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder ); + mpWindowImpl->mpBorderWindow = pBorderWin; + mpWindowImpl->mpRealParent = pParent; + } + + SetActivateMode( ActivateModeFlags::GrabFocus ); + + ImplInitSettings(); +} + +void Dialog::ApplySettings(vcl::RenderContext& rRenderContext) +{ + if (IsControlBackground()) + { + // user override + SetBackground(GetControlBackground()); + } + else if (rRenderContext.IsNativeControlSupported(ControlType::WindowBackground, ControlPart::BackgroundDialog)) + { + // NWF background + mpWindowImpl->mnNativeBackground = ControlPart::BackgroundDialog; + EnableChildTransparentMode(); + } + else + { + // fallback to settings color + rRenderContext.SetBackground(GetSettings().GetStyleSettings().GetDialogColor()); + } +} + +void Dialog::ImplInitSettings() +{ + // user override + if (IsControlBackground()) + SetBackground(GetControlBackground()); + // NWF background + else if( IsNativeControlSupported(ControlType::WindowBackground, ControlPart::BackgroundDialog)) + { + mpWindowImpl->mnNativeBackground = ControlPart::BackgroundDialog; + EnableChildTransparentMode(); + } + // fallback to settings color + else + SetBackground(GetSettings().GetStyleSettings().GetDialogColor()); +} + +void Dialog::ImplLOKNotifier(vcl::Window* pParent) +{ + if (comphelper::LibreOfficeKit::isActive() && pParent) + { + if (VclPtr<vcl::Window> pWin = pParent->GetParentWithLOKNotifier()) + { + SetLOKNotifier(pWin->GetLOKNotifier()); + } + } +} + +Dialog::Dialog( WindowType nType ) + : SystemWindow( nType, "vcl::Dialog maLayoutIdle" ) + , mnInitFlag(InitFlag::Default) +{ + ImplInitDialogData(); +} + +void VclBuilderContainer::disposeBuilder() +{ + if (m_pUIBuilder) + m_pUIBuilder->disposeBuilder(); +} + +OUString AllSettings::GetUIRootDir() +{ + OUString sShareLayer("$BRAND_BASE_DIR/$BRAND_SHARE_SUBDIR/config/soffice.cfg/"); + rtl::Bootstrap::expandMacros(sShareLayer); + return sShareLayer; +} + +//we can't change sizeable after the fact, so need to defer until we know and then +//do the init. Find the real parent stashed in mpDialogParent. +void Dialog::doDeferredInit(WinBits nBits) +{ + VclPtr<vcl::Window> pParent = mpDialogParent; + mpDialogParent = nullptr; + ImplInitDialog(pParent, nBits | WB_BORDER, mnInitFlag); + mbIsDeferredInit = false; +} + +Dialog::Dialog(vcl::Window* pParent, std::u16string_view rID, const OUString& rUIXMLDescription) + : SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle") + , mnInitFlag(InitFlag::Default) +{ + ImplLOKNotifier(pParent); + ImplInitDialogData(); + loadUI(pParent, OUStringToOString(rID, RTL_TEXTENCODING_UTF8), rUIXMLDescription); +} + +Dialog::Dialog(vcl::Window* pParent, WinBits nStyle, InitFlag eFlag) + : SystemWindow(WindowType::DIALOG, "vcl::Dialog maLayoutIdle") + , mnInitFlag(eFlag) +{ + ImplLOKNotifier(pParent); + ImplInitDialogData(); + ImplInitDialog( pParent, nStyle, eFlag ); +} + +void Dialog::set_action_area(VclButtonBox* pBox) +{ + mpActionArea.set(pBox); + if (pBox) + { + const DialogStyle& rDialogStyle = + GetSettings().GetStyleSettings().GetDialogStyle(); + pBox->set_border_width(rDialogStyle.action_area_border); + } +} + +void Dialog::set_content_area(VclBox* pBox) +{ + mpContentArea.set(pBox); +} + +void Dialog::settingOptimalLayoutSize(Window *pBox) +{ + const DialogStyle& rDialogStyle = + GetSettings().GetStyleSettings().GetDialogStyle(); + VclBox * pBox2 = static_cast<VclBox*>(pBox); + pBox2->set_border_width(rDialogStyle.content_area_border); +} + +Dialog::~Dialog() +{ + disposeOnce(); +} + +void Dialog::dispose() +{ + bool bTunnelingEnabled = mpDialogImpl->m_bLOKTunneling; + + mpDialogImpl.reset(); + RemoveFromDlgList(); + mpActionArea.clear(); + mpContentArea.clear(); + + css::uno::Reference< css::uno::XComponentContext > xContext( + comphelper::getProcessComponentContext() ); + css::uno::Reference<css::frame::XGlobalEventBroadcaster> xEventBroadcaster(css::frame::theGlobalEventBroadcaster::get(xContext), css::uno::UNO_SET_THROW); + css::document::DocumentEvent aObject; + aObject.EventName = "DialogClosed"; + xEventBroadcaster->documentEventOccured(aObject); + UITestLogger::getInstance().log(u"Close Dialog"); + + if (comphelper::LibreOfficeKit::isActive()) + { + if(const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier()) + { + if (bTunnelingEnabled) + pNotifier->notifyWindow(GetLOKWindowId(), "close"); + ReleaseLOKNotifier(); + } + } + + SystemWindow::dispose(); +} + +IMPL_LINK_NOARG(Dialog, ImplAsyncCloseHdl, void*, void) +{ + Close(); +} + +bool Dialog::EventNotify( NotifyEvent& rNEvt ) +{ + // first call the base class due to Tab control + bool bRet = SystemWindow::EventNotify( rNEvt ); + if ( !bRet ) + { + if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) + { + const KeyEvent* pKEvt = rNEvt.GetKeyEvent(); + vcl::KeyCode aKeyCode = pKEvt->GetKeyCode(); + sal_uInt16 nKeyCode = aKeyCode.GetCode(); + + if ( (nKeyCode == KEY_ESCAPE) && + ((GetStyle() & WB_CLOSEABLE) || ImplGetCancelButton( this ) || ImplGetOKButton( this )) ) + { + // #i89505# for the benefit of slightly mentally challenged implementations + // like e.g. SfxModelessDialog which destroy themselves inside Close() + // post this Close asynchronous so we can leave our key handler before + // we get destroyed + PostUserEvent( LINK( this, Dialog, ImplAsyncCloseHdl ), nullptr, true); + return true; + } + } + else if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS ) + { + // make sure the dialog is still modal + // changing focus between application frames may + // have re-enabled input for our parent + if( mbInExecute && mbModalMode ) + { + ImplSetModalInputMode( false ); + ImplSetModalInputMode( true ); + + // #93022# def-button might have changed after show + if( !mnMousePositioned ) + { + mnMousePositioned = 1; + ImplMouseAutoPos( this ); + } + + } + } + } + + return bRet; +} + +//What we really want here is something that gives the available width and +//height of a users screen, taking away the space taken up the OS +//taskbar, menus, etc. +Size bestmaxFrameSizeForScreenSize(const Size &rScreenSize) +{ +#ifndef IOS + tools::Long w = rScreenSize.Width(); + if (w <= 800) + w -= 15; + else if (w <= 1024) + w -= 65; + else + w -= 115; + + tools::Long h = rScreenSize.Height(); + if (h <= 768) + h -= 50; + else + h -= 100; + + return Size(std::max<tools::Long>(w, 640 - 15), + std::max<tools::Long>(h, 480 - 50)); +#else + // Don't bother with ancient magic numbers of unclear relevance on non-desktop apps anyway. It + // seems that at least currently in the iOS app, this function is called just once per dialog, + // with a rScreenSize parameter of 1x1 (!). This would lead to always returning 625x430 which is + // a bit random and needlessly small on an iPad at least. We want something that closely will + // just fit on the display in either orientation. + + // We ignore the rScreenSize as it will be the dummy 1x1 from iosinst.cxx (see "Totally wrong of course"). + (void) rScreenSize; + + const int n = std::min<CGFloat>([[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height); + return Size(n-10, n-10); +#endif +} + +void Dialog::SetPopupMenuHdl(const Link<const CommandEvent&, bool>& rLink) +{ + mpDialogImpl->m_aPopupMenuHdl = rLink; +} + +void Dialog::SetInstallLOKNotifierHdl(const Link<void*, vcl::ILibreOfficeKitNotifier*>& rLink) +{ + mpDialogImpl->m_aInstallLOKNotifierHdl = rLink; +} + +void Dialog::SetLOKTunnelingState(bool bEnabled) +{ + mpDialogImpl->m_bLOKTunneling = bEnabled; +} + +void Dialog::StateChanged( StateChangedType nType ) +{ + bool bTunnelingEnabled = mpDialogImpl->m_bLOKTunneling; + + if (nType == StateChangedType::InitShow) + { + DoInitialLayout(); + + const bool bKitActive = comphelper::LibreOfficeKit::isActive(); + if (bKitActive && bTunnelingEnabled) + { + std::vector<vcl::LOKPayloadItem> aItems; + aItems.emplace_back("type", "dialog"); + aItems.emplace_back("size", GetSizePixel().toString()); + if (!GetText().isEmpty()) + aItems.emplace_back("title", GetText().toUtf8()); + + if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier()) + { + pNotifier->notifyWindow(GetLOKWindowId(), "created", aItems); + pNotifier->notifyWindow(GetLOKWindowId(), "created", aItems); + } + else + { + vcl::ILibreOfficeKitNotifier* pViewShell = mpDialogImpl->m_aInstallLOKNotifierHdl.Call(nullptr); + if (pViewShell) + { + SetLOKNotifier(pViewShell); + pViewShell->notifyWindow(GetLOKWindowId(), "created", aItems); + } + } + } + + if ( !HasChildPathFocus() || HasFocus() ) + GrabFocusToFirstControl(); + if ( !(GetStyle() & WB_CLOSEABLE) ) + { + if ( ImplGetCancelButton( this ) || ImplGetOKButton( this ) ) + { + if ( ImplGetBorderWindow() ) + static_cast<ImplBorderWindow*>(ImplGetBorderWindow())->SetCloseButton(); + } + } + + ImplMouseAutoPos( this ); + } + else if (nType == StateChangedType::Text) + { + const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier(); + if (pNotifier && bTunnelingEnabled) + { + std::vector<vcl::LOKPayloadItem> aPayload; + aPayload.emplace_back("title", GetText().toUtf8()); + pNotifier->notifyWindow(GetLOKWindowId(), "title_changed", aPayload); + } + } + + SystemWindow::StateChanged( nType ); + + if (nType == StateChangedType::ControlBackground) + { + ImplInitSettings(); + Invalidate(); + } + + if (!mbModalMode && nType == StateChangedType::Visible) + { + const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier(); + if (pNotifier && bTunnelingEnabled) + { + std::vector<vcl::LOKPayloadItem> aPayload; + aPayload.emplace_back("title", GetText().toUtf8()); + pNotifier->notifyWindow(GetLOKWindowId(), IsVisible()? OUString("show"): OUString("hide"), aPayload); + } + } +} + +void Dialog::DataChanged( const DataChangedEvent& rDCEvt ) +{ + SystemWindow::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + ImplInitSettings(); + Invalidate(); + } +} + +bool Dialog::Close() +{ + VclPtr<vcl::Window> xWindow = this; + CallEventListeners( VclEventId::WindowClose ); + if ( xWindow->isDisposed() ) + return false; + + if ( mpWindowImpl->mxWindowPeer.is() && IsCreatedWithToolkit() && !IsInExecute() ) + return false; + + // If there's a cancel button with a custom handler, then always give it a chance to + // handle Dialog::Close + PushButton* pCustomCancelButton; + PushButton* pCancelButton = dynamic_cast<PushButton*>(get_widget_for_response(RET_CANCEL)); + if (!mbInClose && pCancelButton && pCancelButton->GetClickHdl().IsSet()) + pCustomCancelButton = pCancelButton; + else + pCustomCancelButton = nullptr; + + mbInClose = true; + + if (pCustomCancelButton) + { + pCustomCancelButton->Click(); + if (xWindow->isDisposed()) + return true; + mbInClose = false; + return false; + } + + if ( !(GetStyle() & WB_CLOSEABLE) ) + { + bool bRet = true; + PushButton* pButton = ImplGetCancelButton( this ); + if ( pButton ) + pButton->Click(); + else + { + pButton = ImplGetOKButton( this ); + if ( pButton ) + pButton->Click(); + else + bRet = false; + } + if ( xWindow->isDisposed() ) + return true; + return bRet; + } + + if (IsInExecute() || mpDialogImpl->maEndCtx.isSet()) + { + EndDialog(); + mbInClose = false; + return true; + } + else + { + mbInClose = false; + return SystemWindow::Close(); + } +} + +bool Dialog::ImplStartExecute() +{ + setDeferredProperties(); + + if (IsInExecute() || mpDialogImpl->maEndCtx.isSet()) + { +#ifdef DBG_UTIL + SAL_WARN( "vcl", "Dialog::StartExecuteModal() is called in Dialog::StartExecuteModal(): " + << ImplGetDialogText(this) ); +#endif + return false; + } + + ImplSVData* pSVData = ImplGetSVData(); + + const bool bKitActive = comphelper::LibreOfficeKit::isActive(); + + const bool bModal = GetType() != WindowType::MODELESSDIALOG; + + if (bModal) + { + if (bKitActive && !GetLOKNotifier()) + SetLOKNotifier(mpDialogImpl->m_aInstallLOKNotifierHdl.Call(nullptr)); + + switch ( Application::GetDialogCancelMode() ) + { + case DialogCancelMode::Off: + break; + case DialogCancelMode::Silent: + if (bModal && GetLOKNotifier()) + { + // check if there's already some dialog being ::Execute()d + const bool bDialogExecuting = std::any_of(pSVData->mpWinData->mpExecuteDialogs.begin(), + pSVData->mpWinData->mpExecuteDialogs.end(), + [](const Dialog* pDialog) { + return pDialog->IsInSyncExecute(); + }); + if (!(bDialogExecuting && IsInSyncExecute())) + break; + else + SAL_WARN("lok.dialog", "Dialog \"" << ImplGetDialogText(this) << "\" is being synchronously executed over an existing synchronously executing dialog."); + } + + SAL_INFO( + "vcl", + "Dialog \"" << ImplGetDialogText(this) + << "\"cancelled in silent mode"); + return false; + + case DialogCancelMode::LOKSilent: + return false; + + default: // default cannot happen + case DialogCancelMode::Fatal: + std::abort(); + } + +#ifdef DBG_UTIL + vcl::Window* pParent = GetParent(); + if ( pParent ) + { + pParent = pParent->ImplGetFirstOverlapWindow(); + if (pParent) + { + SAL_WARN_IF( !pParent->IsReallyVisible(), "vcl", + "Dialog::StartExecuteModal() - Parent not visible" ); + SAL_WARN_IF( !pParent->IsInputEnabled(), "vcl", + "Dialog::StartExecuteModal() - Parent input disabled, use another parent to ensure modality!" ); + SAL_WARN_IF( pParent->IsInModalMode(), "vcl", + "Dialog::StartExecuteModal() - Parent already modally disabled, use another parent to ensure modality!" ); + } + } +#endif + + // link all dialogs which are being executed + pSVData->mpWinData->mpExecuteDialogs.push_back(this); + + // stop capturing, in order to have control over the dialog + if (pSVData->mpWinData->mpTrackWin) + pSVData->mpWinData->mpTrackWin->EndTracking(TrackingEventFlags::Cancel); + if (pSVData->mpWinData->mpCaptureWin) + pSVData->mpWinData->mpCaptureWin->ReleaseMouse(); + EnableInput(); + } + + mbInExecute = true; + // no real modality in LibreOfficeKit + if (!bKitActive && bModal) + SetModalInputMode(true); + + // FIXME: no layouting, workaround some clipping issues + ImplAdjustNWFSizes(); + + css::uno::Reference< css::uno::XComponentContext > xContext( + comphelper::getProcessComponentContext()); + bool bForceFocusAndToFront(officecfg::Office::Common::View::NewDocumentHandling::ForceFocusAndToFront::get()); + ShowFlags showFlags = bForceFocusAndToFront ? ShowFlags::ForegroundTask : ShowFlags::NONE; + Show(true, showFlags); + + if (bModal) + pSVData->maAppData.mnModalMode++; + + css::uno::Reference<css::frame::XGlobalEventBroadcaster> xEventBroadcaster( + css::frame::theGlobalEventBroadcaster::get(xContext), css::uno::UNO_SET_THROW); + css::document::DocumentEvent aObject; + aObject.EventName = "DialogExecute"; + xEventBroadcaster->documentEventOccured(aObject); + if (bModal) + UITestLogger::getInstance().log(OUStringConcatenation("Open Modal " + get_id())); + else + UITestLogger::getInstance().log(OUStringConcatenation("Open Modeless " + get_id())); + + bool bTunnelingEnabled = mpDialogImpl->m_bLOKTunneling; + if (comphelper::LibreOfficeKit::isActive() && bTunnelingEnabled) + { + if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier()) + { + // Dialog boxes don't get the Resize call and they + // can have invalid size at 'created' message above. + // If there is no difference, the client should detect it and ignore us, + // otherwise, this should make sure that the window has the correct size. + std::vector<vcl::LOKPayloadItem> aItems; + aItems.emplace_back("size", GetSizePixel().toString()); + pNotifier->notifyWindow(GetLOKWindowId(), "size_changed", aItems); + } + } + + return true; +} + +void Dialog::ImplEndExecuteModal() +{ + ImplSVData* pSVData = ImplGetSVData(); + pSVData->maAppData.mnModalMode--; +} + +short Dialog::Execute() +{ + VclPtr<vcl::Window> xWindow = this; + + mbInSyncExecute = true; + comphelper::ScopeGuard aGuard([&]() { + mbInSyncExecute = false; + }); + + if ( !ImplStartExecute() ) + return 0; + + // Yield util EndDialog is called or dialog gets destroyed + // (the latter should not happen, but better safe than sorry + while ( !xWindow->isDisposed() && mbInExecute && !Application::IsQuit() ) + Application::Yield(); + + ImplEndExecuteModal(); +#ifdef DBG_UTIL + assert (!mpDialogParent || !mpDialogParent->isDisposed()); +#endif + if ( !xWindow->isDisposed() ) + xWindow.clear(); + else + { + OSL_FAIL( "Dialog::Execute() - Dialog destroyed in Execute()" ); + } + + assert(mpDialogImpl); + + if (mpDialogImpl) + { + tools::Long nRet = mpDialogImpl->mnResult; + mpDialogImpl->mnResult = -1; + + return static_cast<short>(nRet); + } + else + { + SAL_WARN( "vcl", "Dialog::Execute() : missing mpDialogImpl " ); + return 0; + } +} + +// virtual +bool Dialog::StartExecuteAsync( VclAbstractDialog::AsyncContext &rCtx ) +{ + const bool bModal = GetType() != WindowType::MODELESSDIALOG; + if (!ImplStartExecute()) + { + rCtx.mxOwner.disposeAndClear(); + rCtx.mxOwnerDialogController.reset(); + rCtx.mxOwnerSelf.reset(); + return false; + } + + mpDialogImpl->maEndCtx = rCtx; + mpDialogImpl->mbStartedModal = bModal; + + return true; +} + +void Dialog::RemoveFromDlgList() +{ + ImplSVData* pSVData = ImplGetSVData(); + auto& rExecuteDialogs = pSVData->mpWinData->mpExecuteDialogs; + + // remove dialog from the list of dialogs which are being executed + rExecuteDialogs.erase(std::remove_if(rExecuteDialogs.begin(), rExecuteDialogs.end(), [this](VclPtr<Dialog>& dialog){ return dialog.get() == this; }), rExecuteDialogs.end()); +} + +void Dialog::EndDialog( tools::Long nResult ) +{ + if (!mbInExecute || isDisposed()) + return; + + const bool bModal = GetType() != WindowType::MODELESSDIALOG; + + Hide(); + + if (comphelper::LibreOfficeKit::isActive()) + { + if(const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier()) + { + pNotifier->notifyWindow(GetLOKWindowId(), "close"); + ReleaseLOKNotifier(); + } + } + + if (bModal) + { + SetModalInputMode(false); + + RemoveFromDlgList(); + + // set focus to previous modal dialog if it is modal for + // the same frame parent (or NULL) + ImplSVData* pSVData = ImplGetSVData(); + if (!pSVData->mpWinData->mpExecuteDialogs.empty()) + { + VclPtr<Dialog> pPrevious = pSVData->mpWinData->mpExecuteDialogs.back(); + + vcl::Window* pFrameParent = ImplGetFrameWindow()->ImplGetParent(); + vcl::Window* pPrevFrameParent = pPrevious->ImplGetFrameWindow()? pPrevious->ImplGetFrameWindow()->ImplGetParent(): nullptr; + if( ( !pFrameParent && !pPrevFrameParent ) || + ( pFrameParent && pPrevFrameParent && pFrameParent->ImplGetFrame() == pPrevFrameParent->ImplGetFrame() ) + ) + { + pPrevious->GrabFocus(); + } + } + } + + mpDialogImpl->mnResult = nResult; + + if ( mpDialogImpl->mbStartedModal ) + ImplEndExecuteModal(); + + if ( mpDialogImpl && mpDialogImpl->maEndCtx.isSet() ) + { + auto fn = std::move(mpDialogImpl->maEndCtx.maEndDialogFn); + // std::move leaves maEndDialogFn in a valid state with unspecified + // value. For the SwSyncBtnDlg case gcc and msvc left maEndDialogFn + // unset, but clang left maEndDialogFn at its original value, keeping + // an extra reference to the DialogController in its lambda giving + // an inconsistent lifecycle for the dialog. Force it to be unset. + mpDialogImpl->maEndCtx.maEndDialogFn = nullptr; + fn(nResult); + } + + if ( mpDialogImpl && mpDialogImpl->mbStartedModal ) + { + mpDialogImpl->mbStartedModal = false; + mpDialogImpl->mnResult = -1; + } + mbInExecute = false; + + if ( mpDialogImpl ) + { + // Destroy ourselves (if we have a context with VclPtr owner) + std::shared_ptr<weld::DialogController> xOwnerDialogController = std::move(mpDialogImpl->maEndCtx.mxOwnerDialogController); + std::shared_ptr<weld::Dialog> xOwnerSelf = std::move(mpDialogImpl->maEndCtx.mxOwnerSelf); + mpDialogImpl->maEndCtx.mxOwner.disposeAndClear(); + xOwnerDialogController.reset(); + xOwnerSelf.reset(); + } +} + +namespace vcl +{ + void EndAllDialogs( vcl::Window const * pParent ) + { + ImplSVData* pSVData = ImplGetSVData(); + auto& rExecuteDialogs = pSVData->mpWinData->mpExecuteDialogs; + + for (auto it = rExecuteDialogs.rbegin(); it != rExecuteDialogs.rend(); ++it) + { + if (!pParent || pParent->IsWindowOrChild(*it, true)) + { + (*it)->EndDialog(); + (*it)->PostUserEvent(Link<void*, void>()); + } + } + } + + void EnableDialogInput(vcl::Window* pWindow) + { + if (Dialog* pDialog = dynamic_cast<Dialog*>(pWindow)) + { + pDialog->EnableInput(); + } + } + + void CloseTopLevel(vcl::Window* pWindow) + { + if (Dialog* pDialog = dynamic_cast<Dialog*>(pWindow)) + pDialog->Close(); + else if (FloatingWindow* pFloatWin = dynamic_cast<FloatingWindow*>(pWindow)) + pFloatWin->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll); + } +} + +void Dialog::SetModalInputMode( bool bModal ) +{ + if ( bModal == mbModalMode ) + return; + + ImplGetFrame()->SetModal(bModal); + + if (GetParent()) + { + SalFrame* pFrame = GetParent()->ImplGetFrame(); + pFrame->NotifyModalHierarchy(bModal); + } + + ImplSetModalInputMode(bModal); +} + +void Dialog::ImplSetModalInputMode( bool bModal ) +{ + if ( bModal == mbModalMode ) + return; + + // previously Execute()'d dialog - the one below the top-most one + VclPtr<Dialog> pPrevious; + ImplSVData* pSVData = ImplGetSVData(); + auto& rExecuteDialogs = pSVData->mpWinData->mpExecuteDialogs; + if (rExecuteDialogs.size() > 1) + pPrevious = rExecuteDialogs[rExecuteDialogs.size() - 2]; + + mbModalMode = bModal; + if ( bModal ) + { + // Disable the prev Modal Dialog, because our dialog must close at first, + // before the other dialog can be closed (because the other dialog + // is on stack since our dialog returns) + if (pPrevious && !pPrevious->IsWindowOrChild(this, true)) + pPrevious->EnableInput(false, this); + + // determine next overlap dialog parent + vcl::Window* pParent = GetParent(); + if ( pParent ) + { + // #103716# dialogs should always be modal to the whole frame window + // #115933# disable the whole frame hierarchy, useful if our parent + // is a modeless dialog + mpDialogParent = pParent->mpWindowImpl->mpFrameWindow; + mpDialogParent->IncModalCount(); + } + } + else + { + if ( mpDialogParent ) + { + // #115933# re-enable the whole frame hierarchy again (see above) + // note that code in getfocus assures that we do not accidentally enable + // windows that were disabled before + mpDialogParent->DecModalCount(); + } + + // Enable the prev Modal Dialog + if (pPrevious && !pPrevious->IsWindowOrChild(this, true)) + { + pPrevious->EnableInput(true, this); + + // ensure continued modality of prev dialog + // do not change modality counter + + // #i119994# need find the last modal dialog before reactive it + if (pPrevious->IsModalInputMode() || !pPrevious->IsWindowOrChild(this, true)) + { + pPrevious->ImplSetModalInputMode(false); + pPrevious->ImplSetModalInputMode(true); + } + } + } +} + +vcl::Window* Dialog::GetFirstControlForFocus() +{ + vcl::Window* pFocusControl = nullptr; + vcl::Window* pFirstOverlapWindow = ImplGetFirstOverlapWindow(); + + // find focus control, even if the dialog has focus + if (!HasFocus() && pFirstOverlapWindow && pFirstOverlapWindow->mpWindowImpl) + { + // prefer a child window which had focus before + pFocusControl = ImplGetFirstOverlapWindow()->mpWindowImpl->mpLastFocusWindow; + // find the control out of the dialog control + if ( pFocusControl ) + pFocusControl = ImplFindDlgCtrlWindow( pFocusControl ); + } + // no control had the focus before or the control is not + // part of the tab-control, now give focus to the + // first control in the tab-control + if ( !pFocusControl || + !(pFocusControl->GetStyle() & WB_TABSTOP) || + !isVisibleInLayout(pFocusControl) || + !isEnabledInLayout(pFocusControl) || !pFocusControl->IsInputEnabled() ) + { + pFocusControl = ImplGetDlgWindow( 0, GetDlgWindowType::First ); + } + + return pFocusControl; +} + +void Dialog::GrabFocusToFirstControl() +{ + vcl::Window* pFocusControl = GetFirstControlForFocus(); + if ( pFocusControl ) + pFocusControl->ImplControlFocus( GetFocusFlags::Init ); +} + +void Dialog::GetDrawWindowBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder, sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const +{ + ScopedVclPtrInstance<ImplBorderWindow> aImplWin( static_cast<vcl::Window*>(const_cast<Dialog *>(this)), WB_BORDER|WB_STDWORK, BorderWindowStyle::Overlap ); + aImplWin->GetBorder( rLeftBorder, rTopBorder, rRightBorder, rBottomBorder ); +} + +void Dialog::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags ) +{ + Point aPos = pDev->LogicToPixel( rPos ); + Size aSize = GetSizePixel(); + + Wallpaper aWallpaper = GetBackground(); + if ( !aWallpaper.IsBitmap() ) + ImplInitSettings(); + + pDev->Push(); + pDev->SetMapMode(); + pDev->SetLineColor(); + + if ( aWallpaper.IsBitmap() ) + pDev->DrawBitmapEx( aPos, aSize, aWallpaper.GetBitmap() ); + else + { + pDev->SetFillColor( aWallpaper.GetColor() ); + pDev->DrawRect( tools::Rectangle( aPos, aSize ) ); + } + + if (!( GetStyle() & WB_NOBORDER )) + { + ScopedVclPtrInstance< ImplBorderWindow > aImplWin( this, WB_BORDER|WB_STDWORK, BorderWindowStyle::Overlap ); + aImplWin->SetText( GetText() ); + aImplWin->setPosSizePixel( aPos.X(), aPos.Y(), aSize.Width(), aSize.Height() ); + aImplWin->SetDisplayActive( true ); + aImplWin->InitView(); + + aImplWin->Draw( pDev, aPos ); + } + + pDev->Pop(); +} + +void Dialog::queue_resize(StateChangedType eReason) +{ + if (IsInClose()) + return; + SystemWindow::queue_resize(eReason); +} + +void Dialog::Resize() +{ + SystemWindow::Resize(); + + if (comphelper::LibreOfficeKit::isDialogPainting()) + return; + + bool bTunnelingEnabled = mpDialogImpl->m_bLOKTunneling; + const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier(); + if (pNotifier && bTunnelingEnabled) + { + std::vector<vcl::LOKPayloadItem> aItems; + aItems.emplace_back("size", GetSizePixel().toString()); + pNotifier->notifyWindow(GetLOKWindowId(), "size_changed", aItems); + } +} + +bool Dialog::set_property(const OString &rKey, const OUString &rValue) +{ + if (rKey == "border-width") + set_border_width(rValue.toInt32()); + else + return SystemWindow::set_property(rKey, rValue); + return true; +} + +FactoryFunction Dialog::GetUITestFactory() const +{ + return DialogUIObject::create; +} + +IMPL_LINK(Dialog, ResponseHdl, Button*, pButton, void) +{ + auto aFind = mpDialogImpl->maResponses.find(pButton); + if (aFind == mpDialogImpl->maResponses.end()) + return; + short nResponse = aFind->second; + if (nResponse == RET_HELP) + { + vcl::Window* pFocusWin = Application::GetFocusWindow(); + if (!pFocusWin || comphelper::LibreOfficeKit::isActive()) + pFocusWin = pButton; + HelpEvent aEvt(pFocusWin->GetPointerPosPixel(), HelpEventMode::CONTEXT); + pFocusWin->RequestHelp(aEvt); + return; + } + EndDialog(nResponse); +} + +void Dialog::add_button(PushButton* pButton, int response, bool bTransferOwnership) +{ + if (bTransferOwnership) + mpDialogImpl->maOwnedButtons.push_back(pButton); + mpDialogImpl->maResponses[pButton] = response; + switch (pButton->GetType()) + { + case WindowType::PUSHBUTTON: + { + if (!pButton->GetClickHdl().IsSet()) + pButton->SetClickHdl(LINK(this, Dialog, ResponseHdl)); + break; + } + //insist that the response ids match the default actions for those + //widgets, and leave their default handlers in place + case WindowType::OKBUTTON: + assert(mpDialogImpl->get_response(pButton) == RET_OK); + break; + case WindowType::CANCELBUTTON: + assert(mpDialogImpl->get_response(pButton) == RET_CANCEL || mpDialogImpl->get_response(pButton) == RET_CLOSE); + break; + case WindowType::HELPBUTTON: + assert(mpDialogImpl->get_response(pButton) == RET_HELP); + break; + default: + SAL_WARN("vcl.layout", "The type of widget " << + pButton->GetHelpId() << " is currently not handled"); + break; + } +} + +vcl::Window* Dialog::get_widget_for_response(int response) +{ + //copy explicit responses + std::map<VclPtr<vcl::Window>, short> aResponses(mpDialogImpl->maResponses); + + if (mpActionArea) + { + //add implicit responses + for (vcl::Window* pChild = mpActionArea->GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (aResponses.find(pChild) != aResponses.end()) + continue; + switch (pChild->GetType()) + { + case WindowType::OKBUTTON: + aResponses[pChild] = RET_OK; + break; + case WindowType::CANCELBUTTON: + aResponses[pChild] = RET_CANCEL; + break; + case WindowType::HELPBUTTON: + aResponses[pChild] = RET_HELP; + break; + default: + break; + } + } + } + + for (const auto& a : aResponses) + { + if (a.second == response) + return a.first; + } + + return nullptr; +} + +int Dialog::get_default_response() const +{ + //copy explicit responses + std::map<VclPtr<vcl::Window>, short> aResponses(mpDialogImpl->maResponses); + + if (mpActionArea) + { + //add implicit responses + for (vcl::Window* pChild = mpActionArea->GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (aResponses.find(pChild) != aResponses.end()) + continue; + switch (pChild->GetType()) + { + case WindowType::OKBUTTON: + aResponses[pChild] = RET_OK; + break; + case WindowType::CANCELBUTTON: + aResponses[pChild] = RET_CANCEL; + break; + case WindowType::HELPBUTTON: + aResponses[pChild] = RET_HELP; + break; + default: + break; + } + } + } + + for (const auto& a : aResponses) + { + if (a.first->GetStyle() & WB_DEFBUTTON) + { + return a.second; + } + } + return RET_CANCEL; +} + +void Dialog::set_default_response(int response) +{ + //copy explicit responses + std::map<VclPtr<vcl::Window>, short> aResponses(mpDialogImpl->maResponses); + + if (mpActionArea) + { + //add implicit responses + for (vcl::Window* pChild = mpActionArea->GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (aResponses.find(pChild) != aResponses.end()) + continue; + switch (pChild->GetType()) + { + case WindowType::OKBUTTON: + aResponses[pChild] = RET_OK; + break; + case WindowType::CANCELBUTTON: + aResponses[pChild] = RET_CANCEL; + break; + case WindowType::HELPBUTTON: + aResponses[pChild] = RET_HELP; + break; + default: + break; + } + } + } + + for (auto& a : aResponses) + { + if (a.second == response) + { + a.first->SetStyle(a.first->GetStyle() | WB_DEFBUTTON); + a.first->GrabFocus(); + } + else + { + a.first->SetStyle(a.first->GetStyle() & ~WB_DEFBUTTON); + } + } +} + +VclBuilderContainer::VclBuilderContainer() +{ +} + +void VclBuilderContainer::setDeferredProperties() +{ + if (!m_pUIBuilder) + return; + m_pUIBuilder->setDeferredProperties(); +} + +VclBuilderContainer::~VclBuilderContainer() +{ +} + +void Dialog::Activate() +{ + if (GetType() == WindowType::MODELESSDIALOG) + { + css::uno::Reference< css::uno::XComponentContext > xContext( + comphelper::getProcessComponentContext() ); + css::uno::Reference<css::frame::XGlobalEventBroadcaster> xEventBroadcaster(css::frame::theGlobalEventBroadcaster::get(xContext), css::uno::UNO_SET_THROW); + css::document::DocumentEvent aObject; + aObject.EventName = "ModelessDialogVisible"; + xEventBroadcaster->documentEventOccured(aObject); + } + SystemWindow::Activate(); +} + +void Dialog::Command(const CommandEvent& rCEvt) +{ + if (mpDialogImpl && mpDialogImpl->m_aPopupMenuHdl.Call(rCEvt)) + return; + SystemWindow::Command(rCEvt); +} + +struct TopLevelWindowLockerImpl +{ + std::stack<std::vector<VclPtr<vcl::Window>>> m_aBusyStack; +}; + +TopLevelWindowLocker::TopLevelWindowLocker() + : m_xImpl(std::make_unique<TopLevelWindowLockerImpl>()) +{ +} + +void TopLevelWindowLocker::incBusy(const weld::Widget* pIgnore) +{ + // lock any toplevel windows from being closed until busy is over + std::vector<VclPtr<vcl::Window>> aTopLevels; + vcl::Window *pTopWin = Application::GetFirstTopLevelWindow(); + while (pTopWin) + { + vcl::Window* pCandidate = pTopWin; + if (pCandidate->GetType() == WindowType::BORDERWINDOW) + pCandidate = pCandidate->GetWindow(GetWindowType::FirstChild); + // tdf#125266 ignore HelpTextWindows + if (pCandidate && + pCandidate->GetType() != WindowType::HELPTEXTWINDOW && + pCandidate->GetType() != WindowType::FLOATINGWINDOW && + pCandidate->GetFrameWeld() != pIgnore) + { + aTopLevels.push_back(pCandidate); + } + pTopWin = Application::GetNextTopLevelWindow(pTopWin); + } + for (auto& a : aTopLevels) + { + a->IncModalCount(); + a->ImplGetFrame()->NotifyModalHierarchy(true); + } + m_xImpl->m_aBusyStack.push(aTopLevels); +} + +void TopLevelWindowLocker::decBusy() +{ + // unlock locked toplevel windows from being closed now busy is over + for (auto& a : m_xImpl->m_aBusyStack.top()) + { + if (a->isDisposed()) + continue; + a->DecModalCount(); + a->ImplGetFrame()->NotifyModalHierarchy(false); + } + m_xImpl->m_aBusyStack.pop(); +} + +bool TopLevelWindowLocker::isBusy() const +{ + return !m_xImpl->m_aBusyStack.empty(); +} + +TopLevelWindowLocker::~TopLevelWindowLocker() +{ +} + +void Dialog::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + SystemWindow::DumpAsPropertyTree(rJsonWriter); + rJsonWriter.put("title", GetText()); + if (vcl::Window* pActionArea = get_action_area()) + { + if (!pActionArea->IsVisible()) + rJsonWriter.put("collapsed", "true"); + } + + OUString sDialogId = OStringToOUString(GetHelpId(), RTL_TEXTENCODING_ASCII_US); + sal_Int32 nStartPos = sDialogId.lastIndexOf('/'); + nStartPos = nStartPos >= 0 ? nStartPos + 1 : 0; + rJsonWriter.put("dialogid", sDialogId.copy(nStartPos)); + + { + auto aResponses = rJsonWriter.startArray("responses"); + for (const auto& rResponse : mpDialogImpl->maResponses) + { + auto aResponse = rJsonWriter.startStruct(); + rJsonWriter.put("id", rResponse.first->get_id()); + rJsonWriter.put("response", rResponse.second); + } + } + + vcl::Window* pFocusControl = GetFirstControlForFocus(); + if (pFocusControl) + rJsonWriter.put("init_focus_id", pFocusControl->get_id()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/dlgctrl.cxx b/vcl/source/window/dlgctrl.cxx new file mode 100644 index 000000000..5aac19d02 --- /dev/null +++ b/vcl/source/window/dlgctrl.cxx @@ -0,0 +1,1159 @@ +/* -*- 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 <svdata.hxx> +#include <window.h> + +#include "dlgctrl.hxx" +#include <vcl/event.hxx> +#include <vcl/toolkit/fixed.hxx> +#include <vcl/layout.hxx> +#include <vcl/svapp.hxx> +#include <vcl/tabpage.hxx> +#include <vcl/tabctrl.hxx> +#include <vcl/toolkit/button.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/settings.hxx> +#include <sal/log.hxx> +#include <i18nlangtag/languagetag.hxx> + +#include <com/sun/star/i18n/XCharacterClassification.hpp> + +using namespace ::com::sun::star; + +static bool ImplHasIndirectTabParent( vcl::Window* pWindow ) +{ + // The window has indirect tab parent if it is included in tab hierarchy + // of the indirect parent window + + vcl::Window* pNonLayoutParent = getNonLayoutParent(pWindow); + return ( pNonLayoutParent + && ( pNonLayoutParent->ImplGetWindow()->GetStyle() & WB_CHILDDLGCTRL ) ); +} + +static vcl::Window* ImplGetTopParentOfTabHierarchy( vcl::Window* pParent ) +{ + // The method allows to find the most close parent containing all the + // window from the current tab-hierarchy + // The direct parent should be provided as a parameter here + + vcl::Window* pResult = pParent; + + if ( pResult ) + { + vcl::Window* pNonLayoutParent = getNonLayoutParent(pResult); + while ( pNonLayoutParent && ( pResult->ImplGetWindow()->GetStyle() & WB_CHILDDLGCTRL ) ) + { + pResult = pNonLayoutParent; + pNonLayoutParent = getNonLayoutParent(pResult); + } + } + + return pResult; +} + +static vcl::Window* ImplGetCurTabWindow(const vcl::Window* pWindow) +{ + assert(pWindow->GetType() == WindowType::TABCONTROL); + const TabControl* pTabControl = static_cast<const TabControl*>(pWindow); + // Check if the TabPage is a Child of the TabControl and still exists (by + // walking all child windows); because it could be that the TabPage has been + // destroyed already by a Dialog-Dtor, event that the TabControl still exists. + const TabPage* pTempTabPage = pTabControl->GetTabPage(pTabControl->GetCurPageId()); + if (pTempTabPage) + { + vcl::Window* pTempWindow = pTabControl->GetWindow(GetWindowType::FirstChild); + while (pTempWindow) + { + if (pTempWindow->ImplGetWindow() == pTempTabPage) + { + return const_cast<TabPage*>(pTempTabPage); + } + pTempWindow = nextLogicalChildOfParent(pTabControl, pTempWindow); + } + } + + return nullptr; +} + +static vcl::Window* ImplGetSubChildWindow( vcl::Window* pParent, sal_uInt16 n, sal_uInt16& nIndex ) +{ + // ignore all windows with mpClientWindow set + for (vcl::Window *pNewParent = pParent->ImplGetWindow(); + pParent != pNewParent; pParent = pNewParent); + + vcl::Window* pFoundWindow = nullptr; + vcl::Window* pWindow = firstLogicalChildOfParent(pParent); + vcl::Window* pNextWindow = pWindow; + + // process just the current page of a tab control + if (pWindow && pParent->GetType() == WindowType::TABCONTROL) + { + pWindow = ImplGetCurTabWindow(pParent); + pNextWindow = lastLogicalChildOfParent(pParent); + } + + while (pWindow) + { + pWindow = pWindow->ImplGetWindow(); + + // skip invisible and disabled windows + if (isVisibleInLayout(pWindow)) + { + // return the TabControl itself, before handling its page + if (pWindow->GetType() == WindowType::TABCONTROL) + { + if (n == nIndex) + return pWindow; + ++nIndex; + } + if (pWindow->GetStyle() & (WB_DIALOGCONTROL | WB_CHILDDLGCTRL)) + pFoundWindow = ImplGetSubChildWindow(pWindow, n, nIndex); + else + pFoundWindow = pWindow; + + if (n == nIndex) + return pFoundWindow; + ++nIndex; + } + + pWindow = nextLogicalChildOfParent(pParent, pNextWindow); + pNextWindow = pWindow; + } + + --nIndex; + assert(!pFoundWindow || (pFoundWindow == pFoundWindow->ImplGetWindow())); + return pFoundWindow; +} + +vcl::Window* ImplGetChildWindow( vcl::Window* pParent, sal_uInt16 n, sal_uInt16& nIndex, bool bTestEnable ) +{ + pParent = ImplGetTopParentOfTabHierarchy( pParent ); + + nIndex = 0; + vcl::Window* pWindow = ImplGetSubChildWindow( pParent, n, nIndex ); + if ( bTestEnable ) + { + sal_uInt16 n2 = nIndex; + while ( pWindow && (!isEnabledInLayout(pWindow) || !pWindow->IsInputEnabled()) ) + { + n2 = nIndex+1; + nIndex = 0; + pWindow = ImplGetSubChildWindow( pParent, n2, nIndex ); + if ( nIndex < n2 ) + break; + } + + if ( (nIndex < n2) && n ) + { + do + { + n--; + nIndex = 0; + pWindow = ImplGetSubChildWindow( pParent, n, nIndex ); + } + while ( pWindow && n && (!isEnabledInLayout(pWindow) || !pWindow->IsInputEnabled()) ); + } + } + return pWindow; +} + +static vcl::Window* ImplGetNextWindow( vcl::Window* pParent, sal_uInt16 n, sal_uInt16& nIndex, bool bTestEnable ) +{ + vcl::Window* pWindow = ImplGetChildWindow( pParent, n+1, nIndex, bTestEnable ); + if ( n == nIndex ) + { + n = 0; + pWindow = ImplGetChildWindow( pParent, n, nIndex, bTestEnable ); + } + return pWindow; +} + +namespace vcl { + +static bool lcl_ToolBoxTabStop( Window* pWindow ) +{ + ToolBox* pToolBoxWindow = static_cast<ToolBox*>( pWindow ); + + for ( ToolBox::ImplToolItems::size_type nPos = 0; nPos < pToolBoxWindow->GetItemCount(); nPos++ ) + { + ToolBoxItemId nId = pToolBoxWindow->GetItemId( nPos ); + if ( pToolBoxWindow->IsItemVisible( nId ) && pToolBoxWindow->IsItemEnabled( nId ) ) + return true; + } + + return false; +} + +vcl::Window* Window::ImplGetDlgWindow( sal_uInt16 nIndex, GetDlgWindowType nType, + sal_uInt16 nFormStart, sal_uInt16 nFormEnd, + sal_uInt16* pIndex ) +{ + SAL_WARN_IF( (nIndex < nFormStart) || (nIndex > nFormEnd), "vcl", + "Window::ImplGetDlgWindow() - nIndex not in Form" ); + + vcl::Window* pWindow = nullptr; + sal_uInt16 i; + sal_uInt16 nTemp; + sal_uInt16 nStartIndex; + + if ( nType == GetDlgWindowType::Prev ) + { + i = nIndex; + do + { + if ( i > nFormStart ) + i--; + else + i = nFormEnd; + pWindow = ImplGetChildWindow( this, i, nTemp, true ); + if ( !pWindow ) + break; + if ( (i == nTemp) && (pWindow->GetStyle() & WB_TABSTOP) ) + { + if ( WindowType::TOOLBOX == pWindow->GetType() ) + { + if ( lcl_ToolBoxTabStop( pWindow ) ) + break; + } + else + break; + } + } + while ( i != nIndex ); + } + else + { + i = nIndex; + pWindow = ImplGetChildWindow( this, i, i, (nType == GetDlgWindowType::First) ); + if ( pWindow ) + { + nStartIndex = i; + + if ( nType == GetDlgWindowType::Next ) + { + if ( i < nFormEnd ) + { + pWindow = ImplGetNextWindow( this, i, i, true ); + if ( (i > nFormEnd) || (i < nFormStart) ) + pWindow = ImplGetChildWindow( this, nFormStart, i, true ); + } + else + pWindow = ImplGetChildWindow( this, nFormStart, i, true ); + } + + if (i <= nFormEnd && pWindow) + { + // carry the 2nd index, in case all controls are disabled + sal_uInt16 nStartIndex2 = i; + sal_uInt16 nOldIndex = i+1; + + do + { + if ( pWindow->GetStyle() & WB_TABSTOP ) + { + if ( WindowType::TOOLBOX == pWindow->GetType() ) + { + if ( lcl_ToolBoxTabStop( pWindow ) ) + break; + } + else + break; + } + if( i == nOldIndex ) // only disabled controls ? + { + i = nStartIndex2; + break; + } + nOldIndex = i; + if ( (i > nFormEnd) || (i < nFormStart) ) + pWindow = ImplGetChildWindow( this, nFormStart, i, true ); + else + pWindow = ImplGetNextWindow( this, i, i, true ); + } + while (i != nStartIndex && i != nStartIndex2 && pWindow); + + if ( (i == nStartIndex2) && pWindow && + (!(pWindow->GetStyle() & WB_TABSTOP) || !isEnabledInLayout(pWindow)) ) + i = nStartIndex; + } + } + + if ( nType == GetDlgWindowType::First ) + { + if ( pWindow ) + { + if ( pWindow->GetType() == WindowType::TABCONTROL ) + { + vcl::Window* pNextWindow = ImplGetDlgWindow( i, GetDlgWindowType::Next ); + if ( pNextWindow ) + { + if ( pWindow->IsChild( pNextWindow ) ) + pWindow = pNextWindow; + } + } + + if ( !(pWindow->GetStyle() & WB_TABSTOP) ) + pWindow = nullptr; + } + } + } + + if ( pIndex ) + *pIndex = i; + + return pWindow; +} + +} /* namespace vcl */ + +vcl::Window* ImplFindDlgCtrlWindow( vcl::Window* pParent, vcl::Window* pWindow, sal_uInt16& rIndex, + sal_uInt16& rFormStart, sal_uInt16& rFormEnd ) +{ + vcl::Window* pSWindow; + vcl::Window* pSecondWindow = nullptr; + vcl::Window* pTempWindow = nullptr; + sal_uInt16 i; + sal_uInt16 nSecond_i = 0; + sal_uInt16 nFormStart = 0; + sal_uInt16 nSecondFormStart = 0; + sal_uInt16 nFormEnd; + + // find focus window in the child list + vcl::Window* pFirstChildWindow = pSWindow = ImplGetChildWindow( pParent, 0, i, false ); + + if( pWindow == nullptr ) + pWindow = pSWindow; + + while ( pSWindow ) + { + // the DialogControlStart mark is only accepted for the direct children + if ( !ImplHasIndirectTabParent( pSWindow ) + && pSWindow->ImplGetWindow()->IsDialogControlStart() ) + nFormStart = i; + + // SecondWindow for composite controls like ComboBoxes and arrays + if ( pSWindow->ImplIsWindowOrChild( pWindow ) ) + { + pSecondWindow = pSWindow; + nSecond_i = i; + nSecondFormStart = nFormStart; + if ( pSWindow == pWindow ) + break; + } + + pSWindow = ImplGetNextWindow( pParent, i, i, false ); + if ( !i ) + pSWindow = nullptr; + } + + if ( !pSWindow ) + { + // Window not found; we cannot handle it + if ( !pSecondWindow ) + return nullptr; + else + { + pSWindow = pSecondWindow; + i = nSecond_i; + nFormStart = nSecondFormStart; + } + } + + // initialize + rIndex = i; + rFormStart = nFormStart; + + // find end of template + sal_Int32 nIteration = 0; + do + { + nFormEnd = i; + pTempWindow = ImplGetNextWindow( pParent, i, i, false ); + + // the DialogControlStart mark is only accepted for the direct children + if ( !i + || ( pTempWindow && !ImplHasIndirectTabParent( pTempWindow ) + && pTempWindow->ImplGetWindow()->IsDialogControlStart() ) ) + break; + + if ( pTempWindow && pTempWindow == pFirstChildWindow ) + { + // It is possible to go through the begin of hierarchy once + // while looking for DialogControlStart mark. + // If it happens second time, it looks like an endless loop, + // that should be impossible, but just for the case... + nIteration++; + if ( nIteration >= 2 ) + { + // this is an unexpected scenario + SAL_WARN( "vcl", "It seems to be an endless loop!" ); + rFormStart = 0; + break; + } + } + } + while ( pTempWindow ); + rFormEnd = nFormEnd; + + return pSWindow; +} + +vcl::Window* ImplFindAccelWindow( vcl::Window* pParent, sal_uInt16& rIndex, sal_Unicode cCharCode, + sal_uInt16 nFormStart, sal_uInt16 nFormEnd, bool bCheckEnable ) +{ + SAL_WARN_IF( (rIndex < nFormStart) || (rIndex > nFormEnd), "vcl", + "Window::ImplFindAccelWindow() - rIndex not in Form" ); + + sal_Unicode cCompareChar; + sal_uInt16 nStart = rIndex; + sal_uInt16 i = rIndex; + vcl::Window* pWindow; + + uno::Reference<i18n::XCharacterClassification> const& xCharClass(ImplGetCharClass()); + + const css::lang::Locale& rLocale = Application::GetSettings().GetUILanguageTag().getLocale(); + cCharCode = xCharClass->toUpper( OUString(cCharCode), 0, 1, rLocale )[0]; + + if ( i < nFormEnd ) + pWindow = ImplGetNextWindow( pParent, i, i, true ); + else + pWindow = ImplGetChildWindow( pParent, nFormStart, i, true ); + while( pWindow ) + { + const OUString aStr = pWindow->GetText(); + sal_Int32 nPos = aStr.indexOf( '~' ); + while (nPos != -1) + { + cCompareChar = aStr[nPos+1]; + cCompareChar = xCharClass->toUpper( OUString(cCompareChar), 0, 1, rLocale )[0]; + if ( cCompareChar == cCharCode ) + { + if (pWindow->GetType() == WindowType::FIXEDTEXT) + { + FixedText *pFixedText = static_cast<FixedText*>(pWindow); + vcl::Window *pMnemonicWidget = pFixedText->get_mnemonic_widget(); + SAL_WARN_IF(isContainerWindow(pFixedText->GetParent()) && !pMnemonicWidget, + "vcl.a11y", "label missing mnemonic_widget?"); + if (pMnemonicWidget) + return pMnemonicWidget; + } + + // skip Static-Controls + if ( (pWindow->GetType() == WindowType::FIXEDTEXT) || + (pWindow->GetType() == WindowType::FIXEDLINE) || + (pWindow->GetType() == WindowType::GROUPBOX) ) + pWindow = pParent->ImplGetDlgWindow( i, GetDlgWindowType::Next ); + rIndex = i; + return pWindow; + } + nPos = aStr.indexOf( '~', nPos+1 ); + } + + // #i93011# it would have made sense to have this really recursive + // right from the start. However this would cause unpredictable side effects now + // so instead we have a style bit for some child windows, that want their + // children checked for accelerators + if( (pWindow->GetStyle() & WB_CHILDDLGCTRL) != 0 ) + { + sal_uInt16 nChildIndex; + sal_uInt16 nChildFormStart; + sal_uInt16 nChildFormEnd; + + // get form start and end + ::ImplFindDlgCtrlWindow( pWindow, nullptr, + nChildIndex, nChildFormStart, nChildFormEnd ); + vcl::Window* pAccelWin = ImplFindAccelWindow( pWindow, nChildIndex, cCharCode, + nChildFormStart, nChildFormEnd, + bCheckEnable ); + if( pAccelWin ) + return pAccelWin; + } + + if ( i == nStart ) + break; + + if ( i < nFormEnd ) + { + pWindow = ImplGetNextWindow( pParent, i, i, bCheckEnable ); + if( ! pWindow ) + pWindow = ImplGetChildWindow( pParent, nFormStart, i, bCheckEnable ); + } + else + pWindow = ImplGetChildWindow( pParent, nFormStart, i, bCheckEnable ); + } + + return nullptr; +} + +namespace vcl { + +void Window::SetMnemonicActivateHdl(const Link<vcl::Window&, bool>& rLink) +{ + if (mpWindowImpl) // may be called after dispose + { + mpWindowImpl->maMnemonicActivateHdl = rLink; + } +} + +void Window::ImplControlFocus( GetFocusFlags nFlags ) +{ + if ( nFlags & GetFocusFlags::Mnemonic ) + { + if (mpWindowImpl->maMnemonicActivateHdl.Call(*this)) + return; + + const bool bUniqueMnemonic(nFlags & GetFocusFlags::UniqueMnemonic); + + if ( GetType() == WindowType::RADIOBUTTON ) + { + if (bUniqueMnemonic && !static_cast<RadioButton*>(this)->IsChecked()) + static_cast<RadioButton*>(this)->ImplCallClick( true, nFlags ); + else + ImplGrabFocus( nFlags ); + } + else + { + ImplGrabFocus( nFlags ); + if (bUniqueMnemonic) + { + if ( GetType() == WindowType::CHECKBOX ) + static_cast<CheckBox*>(this)->ImplCheck(); + else if ( mpWindowImpl->mbPushButton ) + { + static_cast<PushButton*>(this)->SetPressed( true ); + static_cast<PushButton*>(this)->SetPressed( false ); + static_cast<PushButton*>(this)->Click(); + } + } + } + } + else + { + if ( GetType() == WindowType::RADIOBUTTON ) + { + if ( !static_cast<RadioButton*>(this)->IsChecked() ) + static_cast<RadioButton*>(this)->ImplCallClick( true, nFlags ); + else + ImplGrabFocus( nFlags ); + } + else + ImplGrabFocus( nFlags ); + } +} + +} /* namespace vcl */ + +namespace +{ + bool isSuitableDestination(vcl::Window const *pWindow) + { + return (pWindow && isVisibleInLayout(pWindow) && + isEnabledInLayout(pWindow) && pWindow->IsInputEnabled() && + //Pure window shouldn't get window after controls such as + //buttons. + (pWindow->GetType() != WindowType::WINDOW && + pWindow->GetType() != WindowType::WORKWINDOW && pWindow->GetType() != WindowType::CONTROL) + ); + } + + bool focusNextInGroup(const std::vector<VclPtr<RadioButton> >::iterator& aStart, std::vector<VclPtr<RadioButton> > &rGroup) + { + std::vector<VclPtr<RadioButton> >::iterator aI(aStart); + + if (aStart != rGroup.end()) + ++aI; + + aI = std::find_if(aI, rGroup.end(), isSuitableDestination); + if (aI != rGroup.end()) + { + vcl::Window *pWindow = *aI; + pWindow->ImplControlFocus( GetFocusFlags::CURSOR | GetFocusFlags::Forward ); + return true; + } + aI = std::find_if(rGroup.begin(), aStart, isSuitableDestination); + if (aI != aStart) + { + vcl::Window *pWindow = *aI; + pWindow->ImplControlFocus( GetFocusFlags::CURSOR | GetFocusFlags::Forward ); + return true; + } + return false; + } + + bool nextInGroup(RadioButton *pSourceWindow, bool bBackward) + { + std::vector<VclPtr<RadioButton> > aGroup(pSourceWindow->GetRadioButtonGroup()); + + if (aGroup.size() < 2) // have to have at last 2 buttons to be a useful group + return false; + + if (bBackward) + std::reverse(aGroup.begin(), aGroup.end()); + + auto aStart(std::find(aGroup.begin(), aGroup.end(), VclPtr<RadioButton>(pSourceWindow))); + + assert(aStart != aGroup.end()); + + return focusNextInGroup(aStart, aGroup); + } +} + +namespace vcl { + +bool Window::ImplDlgCtrl( const KeyEvent& rKEvt, bool bKeyInput ) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + sal_uInt16 nKeyCode = aKeyCode.GetCode(); + vcl::Window* pSWindow; + vcl::Window* pTempWindow; + vcl::Window* pButtonWindow; + sal_uInt16 i; + sal_uInt16 iButton; + sal_uInt16 iButtonStart; + sal_uInt16 iTemp; + sal_uInt16 nIndex; + sal_uInt16 nFormStart; + sal_uInt16 nFormEnd; + DialogControlFlags nDlgCtrlFlags; + + // we cannot take over control without Focus-window + vcl::Window* pFocusWindow = Application::GetFocusWindow(); + if ( !pFocusWindow || !ImplIsWindowOrChild( pFocusWindow ) ) + return false; + + // find Focus-Window in the child list + pSWindow = ::ImplFindDlgCtrlWindow( this, pFocusWindow, + nIndex, nFormStart, nFormEnd ); + if ( !pSWindow ) + return false; + i = nIndex; + + nDlgCtrlFlags = DialogControlFlags::NONE; + pTempWindow = pSWindow; + do + { + nDlgCtrlFlags |= pTempWindow->GetDialogControlFlags(); + if ( pTempWindow == this ) + break; + pTempWindow = pTempWindow->ImplGetParent(); + } + while ( pTempWindow ); + + pButtonWindow = nullptr; + + if ( nKeyCode == KEY_RETURN ) + { + // search first for a DefPushButton/CancelButton + pButtonWindow = ImplGetChildWindow( this, nFormStart, iButton, true ); + iButtonStart = iButton; + while ( pButtonWindow ) + { + if ( (pButtonWindow->GetStyle() & WB_DEFBUTTON) && + pButtonWindow->mpWindowImpl->mbPushButton ) + break; + + pButtonWindow = ImplGetNextWindow( this, iButton, iButton, true ); + if ( (iButton <= iButtonStart) || (iButton > nFormEnd) ) + pButtonWindow = nullptr; + } + + if ( bKeyInput && !pButtonWindow && (nDlgCtrlFlags & DialogControlFlags::Return) ) + { + GetDlgWindowType nType; + GetFocusFlags nGetFocusFlags = GetFocusFlags::Tab; + sal_uInt16 nNewIndex; + sal_uInt16 iStart; + if ( aKeyCode.IsShift() ) + { + nType = GetDlgWindowType::Prev; + nGetFocusFlags |= GetFocusFlags::Backward; + } + else + { + nType = GetDlgWindowType::Next; + nGetFocusFlags |= GetFocusFlags::Forward; + } + iStart = i; + pTempWindow = ImplGetDlgWindow( i, nType, nFormStart, nFormEnd, &nNewIndex ); + while ( pTempWindow && (pTempWindow != pSWindow) ) + { + if ( !pTempWindow->mpWindowImpl->mbPushButton ) + { + // get Around-Flag + if ( nType == GetDlgWindowType::Prev ) + { + if ( nNewIndex > iStart ) + nGetFocusFlags |= GetFocusFlags::Around; + } + else + { + if ( nNewIndex < iStart ) + nGetFocusFlags |= GetFocusFlags::Around; + } + pTempWindow->ImplControlFocus( nGetFocusFlags ); + return true; + } + else + { + i = nNewIndex; + pTempWindow = ImplGetDlgWindow( i, nType, nFormStart, nFormEnd, &nNewIndex ); + } + if ( (i <= iStart) || (i > nFormEnd) ) + pTempWindow = nullptr; + } + // if this is the same window, simulate a Get/LoseFocus, + // in case AROUND is being processed + if ( pTempWindow && (pTempWindow == pSWindow) ) + { + NotifyEvent aNEvt1( MouseNotifyEvent::LOSEFOCUS, pSWindow ); + if ( !ImplCallPreNotify( aNEvt1 ) ) + pSWindow->CompatLoseFocus(); + pSWindow->mpWindowImpl->mnGetFocusFlags = nGetFocusFlags | GetFocusFlags::Around; + NotifyEvent aNEvt2( MouseNotifyEvent::GETFOCUS, pSWindow ); + if ( !ImplCallPreNotify( aNEvt2 ) ) + pSWindow->CompatGetFocus(); + pSWindow->mpWindowImpl->mnGetFocusFlags = GetFocusFlags::NONE; + return true; + } + } + } + else if ( nKeyCode == KEY_ESCAPE ) + { + // search first for a DefPushButton/CancelButton + pButtonWindow = ImplGetChildWindow( this, nFormStart, iButton, true ); + iButtonStart = iButton; + while ( pButtonWindow ) + { + if ( pButtonWindow->GetType() == WindowType::CANCELBUTTON ) + break; + + pButtonWindow = ImplGetNextWindow( this, iButton, iButton, true ); + if ( (iButton <= iButtonStart) || (iButton > nFormEnd) ) + pButtonWindow = nullptr; + } + + if ( bKeyInput && mpWindowImpl->mpDlgCtrlDownWindow ) + { + if ( mpWindowImpl->mpDlgCtrlDownWindow.get() != pButtonWindow ) + { + static_cast<PushButton*>(mpWindowImpl->mpDlgCtrlDownWindow.get())->SetPressed( false ); + mpWindowImpl->mpDlgCtrlDownWindow = nullptr; + return true; + } + } + } + else if ( bKeyInput ) + { + if ( nKeyCode == KEY_TAB ) + { + // do not skip Alt key, for MS Windows + if ( !aKeyCode.IsMod2() ) + { + sal_uInt16 nNewIndex; + bool bForm = false; + + // for Ctrl-Tab check if we want to jump to next template + if ( aKeyCode.IsMod1() ) + { + // search group + vcl::Window* pFormFirstWindow = nullptr; + vcl::Window* pLastFormFirstWindow = nullptr; + pTempWindow = ImplGetChildWindow( this, 0, iTemp, false ); + vcl::Window* pPrevFirstFormFirstWindow = nullptr; + vcl::Window* pFirstFormFirstWindow = pTempWindow; + while ( pTempWindow ) + { + if ( pTempWindow->ImplGetWindow()->IsDialogControlStart() ) + { + if ( iTemp != 0 ) + bForm = true; + if ( aKeyCode.IsShift() ) + { + if ( iTemp <= nIndex ) + pFormFirstWindow = pPrevFirstFormFirstWindow; + pPrevFirstFormFirstWindow = pTempWindow; + } + else + { + if ( (iTemp > nIndex) && !pFormFirstWindow ) + pFormFirstWindow = pTempWindow; + } + pLastFormFirstWindow = pTempWindow; + } + + pTempWindow = ImplGetNextWindow( this, iTemp, iTemp, false ); + if ( !iTemp ) + pTempWindow = nullptr; + } + + if ( bForm ) + { + if ( !pFormFirstWindow ) + { + if ( aKeyCode.IsShift() ) + pFormFirstWindow = pLastFormFirstWindow; + else + pFormFirstWindow = pFirstFormFirstWindow; + } + + sal_uInt16 nFoundFormStart = 0; + sal_uInt16 nFoundFormEnd = 0; + sal_uInt16 nTempIndex = 0; + if ( ::ImplFindDlgCtrlWindow( this, pFormFirstWindow, nTempIndex, + nFoundFormStart, nFoundFormEnd ) ) + { + nTempIndex = nFoundFormStart; + pFormFirstWindow = ImplGetDlgWindow( nTempIndex, GetDlgWindowType::First, nFoundFormStart, nFoundFormEnd ); + if ( pFormFirstWindow ) + { + pFormFirstWindow->ImplControlFocus(); + return true; + } + } + } + } + + if ( !bForm ) + { + // Only use Ctrl-TAB if it was allowed for the whole + // dialog or for the current control (#103667#) + if (!aKeyCode.IsMod1() || (pSWindow->GetStyle() & WB_NODIALOGCONTROL)) + { + GetDlgWindowType nType; + GetFocusFlags nGetFocusFlags = GetFocusFlags::Tab; + if ( aKeyCode.IsShift() ) + { + nType = GetDlgWindowType::Prev; + nGetFocusFlags |= GetFocusFlags::Backward; + } + else + { + nType = GetDlgWindowType::Next; + nGetFocusFlags |= GetFocusFlags::Forward; + } + vcl::Window* pWindow = ImplGetDlgWindow( i, nType, nFormStart, nFormEnd, &nNewIndex ); + // if this is the same window, simulate a Get/LoseFocus, + // in case AROUND is being processed + if ( pWindow == pSWindow ) + { + NotifyEvent aNEvt1( MouseNotifyEvent::LOSEFOCUS, pSWindow ); + if ( !ImplCallPreNotify( aNEvt1 ) ) + pSWindow->CompatLoseFocus(); + pSWindow->mpWindowImpl->mnGetFocusFlags = nGetFocusFlags | GetFocusFlags::Around; + NotifyEvent aNEvt2( MouseNotifyEvent::GETFOCUS, pSWindow ); + if ( !ImplCallPreNotify( aNEvt2 ) ) + pSWindow->CompatGetFocus(); + pSWindow->mpWindowImpl->mnGetFocusFlags = GetFocusFlags::NONE; + return true; + } + else if ( pWindow ) + { + // get Around-Flag + if ( nType == GetDlgWindowType::Prev ) + { + if ( nNewIndex > i ) + nGetFocusFlags |= GetFocusFlags::Around; + } + else + { + if ( nNewIndex < i ) + nGetFocusFlags |= GetFocusFlags::Around; + } + pWindow->ImplControlFocus( nGetFocusFlags ); + return true; + } + } + } + } + } + else if ( (nKeyCode == KEY_LEFT) || (nKeyCode == KEY_UP) ) + { + if (pSWindow->GetType() == WindowType::RADIOBUTTON) + return nextInGroup(static_cast<RadioButton*>(pSWindow), true); + else + { + WinBits nStyle = pSWindow->GetStyle(); + if ( !(nStyle & WB_GROUP) ) + { + vcl::Window* pWindow = prevLogicalChildOfParent(this, pSWindow); + while ( pWindow ) + { + pWindow = pWindow->ImplGetWindow(); + + nStyle = pWindow->GetStyle(); + + if (isSuitableDestination(pWindow)) + { + if ( pWindow != pSWindow ) + pWindow->ImplControlFocus( GetFocusFlags::CURSOR | GetFocusFlags::Backward ); + return true; + } + + if ( nStyle & WB_GROUP ) + break; + + pWindow = prevLogicalChildOfParent(this, pWindow); + } + } + } + } + else if ( (nKeyCode == KEY_RIGHT) || (nKeyCode == KEY_DOWN) ) + { + if (pSWindow->GetType() == WindowType::RADIOBUTTON) + return nextInGroup(static_cast<RadioButton*>(pSWindow), false); + else + { + vcl::Window* pWindow = nextLogicalChildOfParent(this, pSWindow); + while ( pWindow ) + { + pWindow = pWindow->ImplGetWindow(); + + WinBits nStyle = pWindow->GetStyle(); + + if ( nStyle & WB_GROUP ) + break; + + if (isSuitableDestination(pWindow)) + { + pWindow->ImplControlFocus( GetFocusFlags::CURSOR | GetFocusFlags::Backward ); + return true; + } + + pWindow = nextLogicalChildOfParent(this, pWindow); + } + } + } + else if (aKeyCode.IsMod2()) // tdf#151385 + { + sal_Unicode c = rKEvt.GetCharCode(); + if ( c ) + { + pSWindow = ::ImplFindAccelWindow( this, i, c, nFormStart, nFormEnd ); + if ( pSWindow ) + { + GetFocusFlags nGetFocusFlags = GetFocusFlags::Mnemonic; + if ( pSWindow == ::ImplFindAccelWindow( this, i, c, nFormStart, nFormEnd ) ) + nGetFocusFlags |= GetFocusFlags::UniqueMnemonic; + pSWindow->ImplControlFocus( nGetFocusFlags ); + return true; + } + } + } + } + + if (isSuitableDestination(pButtonWindow)) + { + if ( bKeyInput ) + { + if ( mpWindowImpl->mpDlgCtrlDownWindow && (mpWindowImpl->mpDlgCtrlDownWindow.get() != pButtonWindow) ) + { + static_cast<PushButton*>(mpWindowImpl->mpDlgCtrlDownWindow.get())->SetPressed( false ); + mpWindowImpl->mpDlgCtrlDownWindow = nullptr; + } + + static_cast<PushButton*>(pButtonWindow)->SetPressed( true ); + mpWindowImpl->mpDlgCtrlDownWindow = pButtonWindow; + } + else if ( mpWindowImpl->mpDlgCtrlDownWindow.get() == pButtonWindow ) + { + mpWindowImpl->mpDlgCtrlDownWindow = nullptr; + static_cast<PushButton*>(pButtonWindow)->SetPressed( false ); + static_cast<PushButton*>(pButtonWindow)->Click(); + } + + return true; + } + + return false; +} + +// checks if this window has dialog control +bool Window::ImplHasDlgCtrl() const +{ + vcl::Window* pDlgCtrlParent; + + // lookup window for dialog control + pDlgCtrlParent = ImplGetParent(); + while ( pDlgCtrlParent && + !pDlgCtrlParent->ImplIsOverlapWindow() && + ((pDlgCtrlParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) != WB_DIALOGCONTROL) ) + pDlgCtrlParent = pDlgCtrlParent->ImplGetParent(); + + return pDlgCtrlParent && ((pDlgCtrlParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL); +} + +void Window::ImplDlgCtrlNextWindow() +{ + vcl::Window* pDlgCtrlParent; + vcl::Window* pDlgCtrl; + vcl::Window* pSWindow; + sal_uInt16 nIndex; + sal_uInt16 nFormStart; + sal_uInt16 nFormEnd; + + // lookup window for dialog control + pDlgCtrl = this; + pDlgCtrlParent = ImplGetParent(); + while ( pDlgCtrlParent && + !pDlgCtrlParent->ImplIsOverlapWindow() && + ((pDlgCtrlParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) != WB_DIALOGCONTROL) ) + pDlgCtrlParent = pDlgCtrlParent->ImplGetParent(); + + if ( !pDlgCtrlParent || (GetStyle() & WB_NODIALOGCONTROL) || ((pDlgCtrlParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) != WB_DIALOGCONTROL) ) + return; + + // lookup window in child list + pSWindow = ::ImplFindDlgCtrlWindow( pDlgCtrlParent, pDlgCtrl, + nIndex, nFormStart, nFormEnd ); + if ( !pSWindow ) + return; + + vcl::Window* pWindow = pDlgCtrlParent->ImplGetDlgWindow( nIndex, GetDlgWindowType::Next, nFormStart, nFormEnd ); + if ( pWindow && (pWindow != pSWindow) ) + pWindow->ImplControlFocus(); +} + +static void ImplDlgCtrlUpdateDefButton( vcl::Window* pParent, vcl::Window* pFocusWindow, + bool bGetFocus ) +{ + PushButton* pOldDefButton = nullptr; + PushButton* pNewDefButton = nullptr; + vcl::Window* pSWindow; + sal_uInt16 i; + sal_uInt16 nFormStart; + sal_uInt16 nFormEnd; + + // find template + pSWindow = ::ImplFindDlgCtrlWindow( pParent, pFocusWindow, i, nFormStart, nFormEnd ); + if ( !pSWindow ) + { + nFormStart = 0; + nFormEnd = 0xFFFF; + } + + pSWindow = ImplGetChildWindow( pParent, nFormStart, i, false ); + while ( pSWindow ) + { + if ( pSWindow->ImplIsPushButton() ) + { + PushButton* pPushButton = static_cast<PushButton*>(pSWindow); + if ( pPushButton->ImplIsDefButton() ) + pOldDefButton = pPushButton; + if ( pPushButton->HasChildPathFocus() ) + pNewDefButton = pPushButton; + else if ( !pNewDefButton && (pPushButton->GetStyle() & WB_DEFBUTTON) ) + pNewDefButton = pPushButton; + } + + pSWindow = ImplGetNextWindow( pParent, i, i, false ); + if ( !i || (i > nFormEnd) ) + pSWindow = nullptr; + } + + if ( !bGetFocus ) + { + sal_uInt16 nDummy; + vcl::Window* pNewFocusWindow = Application::GetFocusWindow(); + if ( !pNewFocusWindow || !pParent->ImplIsWindowOrChild( pNewFocusWindow ) ) + pNewDefButton = nullptr; + else if ( !::ImplFindDlgCtrlWindow( pParent, pNewFocusWindow, i, nDummy, nDummy ) || + (i < nFormStart) || (i > nFormEnd) ) + pNewDefButton = nullptr; + } + + if ( pOldDefButton != pNewDefButton ) + { + if ( pOldDefButton ) + pOldDefButton->ImplSetDefButton( false ); + if ( pNewDefButton ) + pNewDefButton->ImplSetDefButton( true ); + } +} + +void Window::ImplDlgCtrlFocusChanged( vcl::Window* pWindow, bool bGetFocus ) +{ + if ( mpWindowImpl->mpDlgCtrlDownWindow && !bGetFocus ) + { + static_cast<PushButton*>(mpWindowImpl->mpDlgCtrlDownWindow.get())->SetPressed( false ); + mpWindowImpl->mpDlgCtrlDownWindow = nullptr; + } + + ImplDlgCtrlUpdateDefButton( this, pWindow, bGetFocus ); +} + +vcl::Window* Window::ImplFindDlgCtrlWindow( vcl::Window* pWindow ) +{ + sal_uInt16 nIndex; + sal_uInt16 nFormStart; + sal_uInt16 nFormEnd; + + // find Focus-Window in the Child-List and return + return ::ImplFindDlgCtrlWindow( this, pWindow, nIndex, nFormStart, nFormEnd ); +} + +KeyEvent Window::GetActivationKey() const +{ + KeyEvent aKeyEvent; + + sal_Unicode nAccel = getAccel( GetText() ); + if( ! nAccel ) + { + vcl::Window* pWindow = GetAccessibleRelationLabeledBy(); + if( pWindow ) + nAccel = getAccel( pWindow->GetText() ); + } + if( nAccel ) + { + sal_uInt16 nCode = 0; + if( nAccel >= 'a' && nAccel <= 'z' ) + nCode = KEY_A + (nAccel-'a'); + else if( nAccel >= 'A' && nAccel <= 'Z' ) + nCode = KEY_A + (nAccel-'A'); + else if( nAccel >= '0' && nAccel <= '9' ) + nCode = KEY_0 + (nAccel-'0'); + else if( nAccel == '.' ) + nCode = KEY_POINT; + else if( nAccel == '-' ) + nCode = KEY_SUBTRACT; + vcl::KeyCode aKeyCode( nCode, false, false, true, false ); + aKeyEvent = KeyEvent( nAccel, aKeyCode ); + } + return aKeyEvent; +} + +} /* namespace vcl */ + +sal_Unicode getAccel( const OUString& rStr ) +{ + sal_Unicode nChar = 0; + sal_Int32 nPos = 0; + do + { + nPos = rStr.indexOf( '~', nPos ); + if( nPos != -1 && nPos < rStr.getLength() ) + nChar = rStr[ ++nPos ]; + else + nChar = 0; + } while( nChar == '~' ); + return nChar; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/dlgctrl.hxx b/vcl/source/window/dlgctrl.hxx new file mode 100644 index 000000000..805099b69 --- /dev/null +++ b/vcl/source/window/dlgctrl.hxx @@ -0,0 +1,34 @@ +/* -*- 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> + +vcl::Window* ImplGetChildWindow( vcl::Window* pParent, sal_uInt16 n, sal_uInt16& nIndex, bool bTestEnable ); + +vcl::Window* ImplFindDlgCtrlWindow( vcl::Window* pParent, vcl::Window* pWindow, sal_uInt16& rIndex, + sal_uInt16& rFormStart, sal_uInt16& rFormEnd ); + +vcl::Window* ImplFindAccelWindow( vcl::Window* pParent, sal_uInt16& rIndex, sal_Unicode cCharCode, + sal_uInt16 nFormStart, sal_uInt16 nFormEnd, bool bCheckEnable = true ); + +sal_Unicode getAccel( const OUString& rStr ); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/dndeventdispatcher.cxx b/vcl/source/window/dndeventdispatcher.cxx new file mode 100644 index 000000000..b8190a118 --- /dev/null +++ b/vcl/source/window/dndeventdispatcher.cxx @@ -0,0 +1,412 @@ +/* -*- 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 <dndeventdispatcher.hxx> +#include <dndlistenercontainer.hxx> +#include <sal/log.hxx> + +#include <osl/mutex.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> + +using namespace ::cppu; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::datatransfer; +using namespace ::com::sun::star::datatransfer::dnd; + +DNDEventDispatcher::DNDEventDispatcher( vcl::Window * pTopWindow ): + m_pTopWindow( pTopWindow ), + m_pCurrentWindow( nullptr ) +{ +} + +DNDEventDispatcher::~DNDEventDispatcher() +{ + designate_currentwindow(nullptr); +} + +vcl::Window* DNDEventDispatcher::findTopLevelWindow(Point location) +{ + SolarMutexGuard aSolarGuard; + + // find the window that is toplevel for this coordinates + // because those coordinates come from outside, they must be mirrored if RTL layout is active + if( AllSettings::GetLayoutRTL() ) + m_pTopWindow->ImplMirrorFramePos( location ); + vcl::Window * pChildWindow = m_pTopWindow->ImplFindWindow( location ); + + if( nullptr == pChildWindow ) + pChildWindow = m_pTopWindow; + + while( pChildWindow->ImplGetClientWindow() ) + pChildWindow = pChildWindow->ImplGetClientWindow(); + + if( pChildWindow->GetOutDev()->ImplIsAntiparallel() ) + { + const OutputDevice *pChildWinOutDev = pChildWindow->GetOutDev(); + pChildWinOutDev->ReMirror( location ); + } + + return pChildWindow; +} + +IMPL_LINK(DNDEventDispatcher, WindowEventListener, VclWindowEvent&, rEvent, void) +{ + if (rEvent.GetId() == VclEventId::ObjectDying) + { + designate_currentwindow(nullptr); + } +} + +void DNDEventDispatcher::designate_currentwindow(vcl::Window *pWindow) +{ + if (m_pCurrentWindow) + m_pCurrentWindow->RemoveEventListener(LINK(this, DNDEventDispatcher, WindowEventListener)); + m_pCurrentWindow = pWindow; + if (m_pCurrentWindow) + m_pCurrentWindow->AddEventListener(LINK(this, DNDEventDispatcher, WindowEventListener)); +} + +void SAL_CALL DNDEventDispatcher::drop( const DropTargetDropEvent& dtde ) +{ + std::scoped_lock aImplGuard( m_aMutex ); + + Point location( dtde.LocationX, dtde.LocationY ); + + vcl::Window* pChildWindow = findTopLevelWindow(location); + + // handle the case that drop is in another vcl window than the last dragOver + if( pChildWindow != m_pCurrentWindow.get() ) + { + // fire dragExit on listeners of previous window + fireDragExitEvent( m_pCurrentWindow ); + + fireDragEnterEvent( pChildWindow, static_cast < XDropTargetDragContext * > (this), + dtde.DropAction, location, dtde.SourceActions, m_aDataFlavorList ); + } + + // send drop event to the child window + sal_Int32 nListeners = fireDropEvent( pChildWindow, dtde.Context, dtde.DropAction, + location, dtde.SourceActions, dtde.Transferable ); + + // reject drop if no listeners found + if( nListeners == 0 ) { + SAL_WARN( "vcl", "rejecting drop due to missing listeners." ); + dtde.Context->rejectDrop(); + } + + // this is a drop -> no further drag overs + designate_currentwindow(nullptr); + m_aDataFlavorList.realloc( 0 ); +} + +void SAL_CALL DNDEventDispatcher::dragEnter( const DropTargetDragEnterEvent& dtdee ) +{ + std::scoped_lock aImplGuard( m_aMutex ); + Point location( dtdee.LocationX, dtdee.LocationY ); + + vcl::Window * pChildWindow = findTopLevelWindow(location); + + // assume pointer write operation to be atomic + designate_currentwindow(pChildWindow); + m_aDataFlavorList = dtdee.SupportedDataFlavors; + + // fire dragEnter on listeners of current window + sal_Int32 nListeners = fireDragEnterEvent( pChildWindow, dtdee.Context, dtdee.DropAction, location, + dtdee.SourceActions, dtdee.SupportedDataFlavors ); + + // reject drag if no listener found + if( nListeners == 0 ) { + SAL_WARN( "vcl", "rejecting drag enter due to missing listeners." ); + dtdee.Context->rejectDrag(); + } + +} + +void SAL_CALL DNDEventDispatcher::dragExit( const DropTargetEvent& /*dte*/ ) +{ + std::scoped_lock aImplGuard( m_aMutex ); + + fireDragExitEvent( m_pCurrentWindow ); + + // reset member values + designate_currentwindow(nullptr); + m_aDataFlavorList.realloc( 0 ); +} + +void SAL_CALL DNDEventDispatcher::dragOver( const DropTargetDragEvent& dtde ) +{ + std::scoped_lock aImplGuard( m_aMutex ); + + Point location( dtde.LocationX, dtde.LocationY ); + sal_Int32 nListeners; + + vcl::Window * pChildWindow = findTopLevelWindow(location); + + if( pChildWindow != m_pCurrentWindow.get() ) + { + // fire dragExit on listeners of previous window + fireDragExitEvent( m_pCurrentWindow ); + + // remember new window + designate_currentwindow(pChildWindow); + + // fire dragEnter on listeners of current window + nListeners = fireDragEnterEvent( pChildWindow, dtde.Context, dtde.DropAction, location, + dtde.SourceActions, m_aDataFlavorList ); + } + else + { + // fire dragOver on listeners of current window + nListeners = fireDragOverEvent( pChildWindow, dtde.Context, dtde.DropAction, location, + dtde.SourceActions ); + } + + // reject drag if no listener found + if( nListeners == 0 ) + { + SAL_WARN( "vcl", "rejecting drag over due to missing listeners." ); + dtde.Context->rejectDrag(); + } +} + +void SAL_CALL DNDEventDispatcher::dropActionChanged( const DropTargetDragEvent& dtde ) +{ + std::scoped_lock aImplGuard( m_aMutex ); + + Point location( dtde.LocationX, dtde.LocationY ); + sal_Int32 nListeners; + + vcl::Window* pChildWindow = findTopLevelWindow(location); + + if( pChildWindow != m_pCurrentWindow.get() ) + { + // fire dragExit on listeners of previous window + fireDragExitEvent( m_pCurrentWindow ); + + // remember new window + designate_currentwindow(pChildWindow); + + // fire dragEnter on listeners of current window + nListeners = fireDragEnterEvent( pChildWindow, dtde.Context, dtde.DropAction, location, + dtde.SourceActions, m_aDataFlavorList ); + } + else + { + // fire dropActionChanged on listeners of current window + nListeners = fireDropActionChangedEvent( pChildWindow, dtde.Context, dtde.DropAction, location, + dtde.SourceActions ); + } + + // reject drag if no listener found + if( nListeners == 0 ) + { + SAL_WARN( "vcl", "rejecting dropActionChanged due to missing listeners." ); + dtde.Context->rejectDrag(); + } +} + +void SAL_CALL DNDEventDispatcher::dragGestureRecognized( const DragGestureEvent& dge ) +{ + std::scoped_lock aImplGuard( m_aMutex ); + + Point origin( dge.DragOriginX, dge.DragOriginY ); + + vcl::Window* pChildWindow = findTopLevelWindow(origin); + + fireDragGestureEvent( pChildWindow, dge.DragSource, dge.Event, origin, dge.DragAction ); +} + +void SAL_CALL DNDEventDispatcher::disposing( const EventObject& ) +{ +} + +void SAL_CALL DNDEventDispatcher::acceptDrag( sal_Int8 /*dropAction*/ ) +{ +} + +void SAL_CALL DNDEventDispatcher::rejectDrag() +{ +} + +sal_Int32 DNDEventDispatcher::fireDragEnterEvent( vcl::Window *pWindow, + const Reference< XDropTargetDragContext >& xContext, const sal_Int8 nDropAction, + const Point& rLocation, const sal_Int8 nSourceActions, const Sequence< DataFlavor >& aFlavorList +) +{ + sal_Int32 n = 0; + + if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() ) + { + SolarMutexClearableGuard aSolarGuard; + + // query DropTarget from window + Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget(); + + if( xDropTarget.is() ) + { + // retrieve relative mouse position + Point relLoc = pWindow->ImplFrameToOutput( rLocation ); + aSolarGuard.clear(); + + n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDragEnterEvent( + xContext, nDropAction, relLoc.X(), relLoc.Y(), nSourceActions, aFlavorList ); + } + } + + return n; +} + +sal_Int32 DNDEventDispatcher::fireDragOverEvent( vcl::Window *pWindow, + const Reference< XDropTargetDragContext >& xContext, const sal_Int8 nDropAction, + const Point& rLocation, const sal_Int8 nSourceActions +) +{ + sal_Int32 n = 0; + + if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() ) + { + SolarMutexClearableGuard aSolarGuard; + + // query DropTarget from window + Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget(); + + if( xDropTarget.is() ) + { + // retrieve relative mouse position + Point relLoc = pWindow->ImplFrameToOutput( rLocation ); + aSolarGuard.clear(); + + n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDragOverEvent( + xContext, nDropAction, relLoc.X(), relLoc.Y(), nSourceActions ); + } + } + + return n; +} + +sal_Int32 DNDEventDispatcher::fireDragExitEvent( vcl::Window *pWindow ) +{ + sal_Int32 n = 0; + + if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() ) + { + SolarMutexClearableGuard aGuard; + + // query DropTarget from window + Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget(); + + aGuard.clear(); + + if( xDropTarget.is() ) + n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDragExitEvent(); + } + + return n; +} + +sal_Int32 DNDEventDispatcher::fireDropActionChangedEvent( vcl::Window *pWindow, + const Reference< XDropTargetDragContext >& xContext, const sal_Int8 nDropAction, + const Point& rLocation, const sal_Int8 nSourceActions +) +{ + sal_Int32 n = 0; + + if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() ) + { + SolarMutexClearableGuard aGuard; + + // query DropTarget from window + Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget(); + + if( xDropTarget.is() ) + { + // retrieve relative mouse position + Point relLoc = pWindow->ImplFrameToOutput( rLocation ); + aGuard.clear(); + + n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDropActionChangedEvent( + xContext, nDropAction, relLoc.X(), relLoc.Y(), nSourceActions ); + } + } + + return n; +} + +sal_Int32 DNDEventDispatcher::fireDropEvent( vcl::Window *pWindow, + const Reference< XDropTargetDropContext >& xContext, const sal_Int8 nDropAction, const Point& rLocation, + const sal_Int8 nSourceActions, const Reference< XTransferable >& xTransferable +) +{ + sal_Int32 n = 0; + + if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() ) + { + SolarMutexClearableGuard aGuard; + + // query DropTarget from window + Reference< XDropTarget > xDropTarget = pWindow->GetDropTarget(); + + // window may be destroyed in drop event handler + VclPtr<vcl::Window> xPreventDelete = pWindow; + + if( xDropTarget.is() ) + { + // retrieve relative mouse position + Point relLoc = pWindow->ImplFrameToOutput( rLocation ); + aGuard.clear(); + + n = static_cast < DNDListenerContainer * > ( xDropTarget.get() )->fireDropEvent( + xContext, nDropAction, relLoc.X(), relLoc.Y(), nSourceActions, xTransferable ); + } + } + + return n; +} + +sal_Int32 DNDEventDispatcher::fireDragGestureEvent( vcl::Window *pWindow, + const Reference< XDragSource >& xSource, const Any& event, + const Point& rOrigin, const sal_Int8 nDragAction +) +{ + sal_Int32 n = 0; + + if( pWindow && pWindow->IsInputEnabled() && ! pWindow->IsInModalMode() ) + { + SolarMutexClearableGuard aGuard; + + // query DropTarget from window + Reference< XDragGestureRecognizer > xDragGestureRecognizer = pWindow->GetDragGestureRecognizer(); + + if( xDragGestureRecognizer.is() ) + { + // retrieve relative mouse position + Point relLoc = pWindow->ImplFrameToOutput( rOrigin ); + aGuard.clear(); + + n = static_cast < DNDListenerContainer * > ( xDragGestureRecognizer.get() )->fireDragGestureEvent( + nDragAction, relLoc.X(), relLoc.Y(), xSource, event ); + } + } + + return n; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/dndlistenercontainer.cxx b/vcl/source/window/dndlistenercontainer.cxx new file mode 100644 index 000000000..d3147aac5 --- /dev/null +++ b/vcl/source/window/dndlistenercontainer.cxx @@ -0,0 +1,487 @@ +/* -*- 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 <dndlistenercontainer.hxx> + +using namespace ::cppu; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::datatransfer; +using namespace ::com::sun::star::datatransfer::dnd; + +DNDListenerContainer::DNDListenerContainer( sal_Int8 nDefaultActions ) + : WeakComponentImplHelper< XDragGestureRecognizer, XDropTargetDragContext, XDropTargetDropContext, XDropTarget >(m_aMutex) +{ + m_bActive = true; + m_nDefaultActions = nDefaultActions; +} + +DNDListenerContainer::~DNDListenerContainer() +{ +} + +void SAL_CALL DNDListenerContainer::addDragGestureListener( const Reference< XDragGestureListener >& dgl ) +{ + rBHelper.addListener( cppu::UnoType<XDragGestureListener>::get(), dgl ); +} + +void SAL_CALL DNDListenerContainer::removeDragGestureListener( const Reference< XDragGestureListener >& dgl ) +{ + rBHelper.removeListener( cppu::UnoType<XDragGestureListener>::get(), dgl ); +} + +void SAL_CALL DNDListenerContainer::resetRecognizer( ) +{ +} + +void SAL_CALL DNDListenerContainer::addDropTargetListener( const Reference< XDropTargetListener >& dtl ) +{ + rBHelper.addListener( cppu::UnoType<XDropTargetListener>::get(), dtl ); +} + +void SAL_CALL DNDListenerContainer::removeDropTargetListener( const Reference< XDropTargetListener >& dtl ) +{ + rBHelper.removeListener( cppu::UnoType<XDropTargetListener>::get(), dtl ); +} + +sal_Bool SAL_CALL DNDListenerContainer::isActive( ) +{ + return m_bActive; +} + +void SAL_CALL DNDListenerContainer::setActive( sal_Bool active ) +{ + m_bActive = active; +} + +sal_Int8 SAL_CALL DNDListenerContainer::getDefaultActions( ) +{ + return m_nDefaultActions; +} + +void SAL_CALL DNDListenerContainer::setDefaultActions( sal_Int8 actions ) +{ + m_nDefaultActions = actions; +} + +sal_uInt32 DNDListenerContainer::fireDropEvent( const Reference< XDropTargetDropContext >& context, + sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions, + const Reference< XTransferable >& transferable ) +{ + sal_uInt32 nRet = 0; + + // fire DropTargetDropEvent on all XDropTargetListeners + OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get()); + + if( pContainer && m_bActive ) + { + OInterfaceIteratorHelper aIterator( *pContainer ); + + // remember context to use in own context methods + m_xDropTargetDropContext = context; + + // do not construct the event before you are sure at least one listener is registered + DropTargetDropEvent aEvent( static_cast < XDropTarget * > (this), 0, + static_cast < XDropTargetDropContext * > (this), dropAction, + locationX, locationY, sourceActions, transferable ); + + while (aIterator.hasMoreElements()) + { + // FIXME: this can be simplified as soon as the Iterator has a remove method + Reference< XInterface > xElement( aIterator.next() ); + + try + { + // this may result in a runtime exception + Reference < XDropTargetListener > xListener( xElement, UNO_QUERY ); + + if( xListener.is() ) + { + // fire drop until the first one has accepted + if( m_xDropTargetDropContext.is() ) + xListener->drop( aEvent ); + else + { + DropTargetEvent aDTEvent( static_cast < XDropTarget * > (this), 0 ); + xListener->dragExit( aDTEvent ); + } + + nRet++; + } + } + catch (const RuntimeException&) + { + pContainer->removeInterface( xElement ); + } + } + + // if context still valid, then reject drop + if( m_xDropTargetDropContext.is() ) + { + m_xDropTargetDropContext.clear(); + + try + { + context->rejectDrop(); + } + catch (const RuntimeException&) + { + } + } + } + + return nRet; +} + +sal_uInt32 DNDListenerContainer::fireDragExitEvent() +{ + sal_uInt32 nRet = 0; + + // fire DropTargetDropEvent on all XDropTargetListeners + OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get()); + + if( pContainer && m_bActive ) + { + OInterfaceIteratorHelper aIterator( *pContainer ); + + // do not construct the event before you are sure at least one listener is registered + DropTargetEvent aEvent( static_cast < XDropTarget * > (this), 0 ); + + while (aIterator.hasMoreElements()) + { + // FIXME: this can be simplified as soon as the Iterator has a remove method + Reference< XInterface > xElement( aIterator.next() ); + + try + { + // this may result in a runtime exception + Reference < XDropTargetListener > xListener( xElement, UNO_QUERY ); + + if( xListener.is() ) + { + xListener->dragExit( aEvent ); + nRet++; + } + } + catch (const RuntimeException&) + { + pContainer->removeInterface( xElement ); + } + } + } + + return nRet; +} + +sal_uInt32 DNDListenerContainer::fireDragOverEvent( const Reference< XDropTargetDragContext >& context, + sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions ) +{ + sal_uInt32 nRet = 0; + + // fire DropTargetDropEvent on all XDropTargetListeners + OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get()); + + if( pContainer && m_bActive ) + { + OInterfaceIteratorHelper aIterator( *pContainer ); + + // remember context to use in own context methods + m_xDropTargetDragContext = context; + + // do not construct the event before you are sure at least one listener is registered + DropTargetDragEvent aEvent( static_cast < XDropTarget * > (this), 0, + static_cast < XDropTargetDragContext * > (this), + dropAction, locationX, locationY, sourceActions ); + + while (aIterator.hasMoreElements()) + { + // FIXME: this can be simplified as soon as the Iterator has a remove method + Reference< XInterface > xElement( aIterator.next() ); + + try + { + // this may result in a runtime exception + Reference < XDropTargetListener > xListener( xElement, UNO_QUERY ); + + if( xListener.is() ) + { + if( m_xDropTargetDragContext.is() ) + xListener->dragOver( aEvent ); + nRet++; + } + } + catch (const RuntimeException&) + { + pContainer->removeInterface( xElement ); + } + } + + // if context still valid, then reject drag + if( m_xDropTargetDragContext.is() ) + { + m_xDropTargetDragContext.clear(); + + try + { + context->rejectDrag(); + } + catch (const RuntimeException&) + { + } + } + } + + return nRet; +} + +sal_uInt32 DNDListenerContainer::fireDragEnterEvent( const Reference< XDropTargetDragContext >& context, + sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions, + const Sequence< DataFlavor >& dataFlavors ) +{ + sal_uInt32 nRet = 0; + + // fire DropTargetDropEvent on all XDropTargetListeners + OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get()); + + if( pContainer && m_bActive ) + { + OInterfaceIteratorHelper aIterator( *pContainer ); + + // remember context to use in own context methods + m_xDropTargetDragContext = context; + + // do not construct the event before you are sure at least one listener is registered + DropTargetDragEnterEvent aEvent( static_cast < XDropTarget * > (this), 0, + static_cast < XDropTargetDragContext * > (this), + dropAction, locationX, locationY, sourceActions, dataFlavors ); + + while (aIterator.hasMoreElements()) + { + // FIXME: this can be simplified as soon as the Iterator has a remove method + Reference< XInterface > xElement( aIterator.next() ); + + try + { + // this may result in a runtime exception + Reference < XDropTargetListener > xListener( xElement, UNO_QUERY ); + + if( xListener.is() ) + { + if( m_xDropTargetDragContext.is() ) + xListener->dragEnter( aEvent ); + nRet++; + } + } + catch (const RuntimeException&) + { + pContainer->removeInterface( xElement ); + } + } + + // if context still valid, then reject drag + if( m_xDropTargetDragContext.is() ) + { + m_xDropTargetDragContext.clear(); + + try + { + context->rejectDrag(); + } + catch (const RuntimeException&) + { + } + } + } + + return nRet; +} + +sal_uInt32 DNDListenerContainer::fireDropActionChangedEvent( const Reference< XDropTargetDragContext >& context, + sal_Int8 dropAction, sal_Int32 locationX, sal_Int32 locationY, sal_Int8 sourceActions ) +{ + sal_uInt32 nRet = 0; + + // fire DropTargetDropEvent on all XDropTargetListeners + OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get()); + + if( pContainer && m_bActive ) + { + OInterfaceIteratorHelper aIterator( *pContainer ); + + // remember context to use in own context methods + m_xDropTargetDragContext = context; + + // do not construct the event before you are sure at least one listener is registered + DropTargetDragEvent aEvent( static_cast < XDropTarget * > (this), 0, + static_cast < XDropTargetDragContext * > (this), + dropAction, locationX, locationY, sourceActions ); + + while (aIterator.hasMoreElements()) + { + // FIXME: this can be simplified as soon as the Iterator has a remove method + Reference< XInterface > xElement( aIterator.next() ); + + try + { + // this may result in a runtime exception + Reference < XDropTargetListener > xListener( xElement, UNO_QUERY ); + + if( xListener.is() ) + { + if( m_xDropTargetDragContext.is() ) + xListener->dropActionChanged( aEvent ); + nRet++; + } + } + catch (const RuntimeException&) + { + pContainer->removeInterface( xElement ); + } + } + + // if context still valid, then reject drag + if( m_xDropTargetDragContext.is() ) + { + m_xDropTargetDragContext.clear(); + + try + { + context->rejectDrag(); + } + catch (const RuntimeException&) + { + } + } + } + + return nRet; +} + +sal_uInt32 DNDListenerContainer::fireDragGestureEvent( sal_Int8 dragAction, sal_Int32 dragOriginX, + sal_Int32 dragOriginY, const Reference< XDragSource >& dragSource, const Any& triggerEvent ) +{ + sal_uInt32 nRet = 0; + + // fire DropTargetDropEvent on all XDropTargetListeners + OInterfaceContainerHelper *pContainer = rBHelper.getContainer( cppu::UnoType<XDragGestureListener>::get()); + + if( pContainer ) + { + OInterfaceIteratorHelper aIterator( *pContainer ); + + // do not construct the event before you are sure at least one listener is registered + DragGestureEvent aEvent( static_cast < XDragGestureRecognizer * > (this), dragAction, + dragOriginX, dragOriginY, dragSource, triggerEvent ); + + while( aIterator.hasMoreElements() ) + { + // FIXME: this can be simplified as soon as the Iterator has a remove method + Reference< XInterface > xElement( aIterator.next() ); + + try + { + // this may result in a runtime exception + Reference < XDragGestureListener > xListener( xElement, UNO_QUERY ); + + if( xListener.is() ) + { + xListener->dragGestureRecognized( aEvent ); + nRet++; + } + } + catch (const RuntimeException&) + { + pContainer->removeInterface( xElement ); + } + } + } + + return nRet; +} + +void SAL_CALL DNDListenerContainer::acceptDrag( sal_Int8 dragOperation ) +{ + if( m_xDropTargetDragContext.is() ) + { + m_xDropTargetDragContext->acceptDrag( dragOperation ); + m_xDropTargetDragContext.clear(); + } +} + +void SAL_CALL DNDListenerContainer::rejectDrag( ) +{ + // nothing to do here +} + +void SAL_CALL DNDListenerContainer::acceptDrop( sal_Int8 dropOperation ) +{ + if( m_xDropTargetDropContext.is() ) + m_xDropTargetDropContext->acceptDrop( dropOperation ); +} + +void SAL_CALL DNDListenerContainer::rejectDrop( ) +{ + // nothing to do here +} + +void SAL_CALL DNDListenerContainer::dropComplete( sal_Bool success ) +{ + if( m_xDropTargetDropContext.is() ) + { + m_xDropTargetDropContext->dropComplete( success ); + m_xDropTargetDropContext.clear(); + } +} + +/* + * GenericDropTargetDropContext + */ + +GenericDropTargetDropContext::GenericDropTargetDropContext() +{ +} + +void GenericDropTargetDropContext::acceptDrop( sal_Int8 /*dragOperation*/ ) +{ +} + +void GenericDropTargetDropContext::rejectDrop() +{ +} + +void GenericDropTargetDropContext::dropComplete( sal_Bool /*success*/ ) +{ +} + +/* + * GenericDropTargetDragContext + */ + +GenericDropTargetDragContext::GenericDropTargetDragContext() +{ +} + +void GenericDropTargetDragContext::acceptDrag( sal_Int8 /*dragOperation*/ ) +{ +} + +void GenericDropTargetDragContext::rejectDrag() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/dockingarea.cxx b/vcl/source/window/dockingarea.cxx new file mode 100644 index 000000000..be3f6ef99 --- /dev/null +++ b/vcl/source/window/dockingarea.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 <vcl/dockingarea.hxx> +#include <vcl/syswin.hxx> +#include <vcl/menu.hxx> +#include <vcl/settings.hxx> +#include <vcl/event.hxx> +#include <toolbarvalue.hxx> + +#include <svdata.hxx> + +#include <map> + +class DockingAreaWindow::ImplData +{ +public: + ImplData(); + + WindowAlign meAlign; +}; + +DockingAreaWindow::ImplData::ImplData() +{ + meAlign = WindowAlign::Top; +} + +DockingAreaWindow::DockingAreaWindow( vcl::Window* pParent ) : + Window( WindowType::DOCKINGAREA ) +{ + ImplInit( pParent, WB_CLIPCHILDREN|WB_3DLOOK, nullptr ); + + mpImplData.reset(new ImplData); +} + +DockingAreaWindow::~DockingAreaWindow() +{ + disposeOnce(); +} + +void DockingAreaWindow::dispose() +{ + mpImplData.reset(); + Window::dispose(); +} + +void DockingAreaWindow::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + Invalidate(); + } +} + +static void ImplInvalidateMenubar( DockingAreaWindow const * pThis ) +{ + // due to a possible common gradient covering menubar and top dockingarea + // the menubar must be repainted if the top dockingarea changes size or visibility + if( ImplGetSVData()->maNWFData.mbMenuBarDockingAreaCommonBG && + (pThis->GetAlign() == WindowAlign::Top) + && pThis->IsNativeControlSupported( ControlType::Toolbar, ControlPart::Entire ) + && pThis->IsNativeControlSupported( ControlType::Menubar, ControlPart::Entire ) ) + { + SystemWindow *pSysWin = pThis->GetSystemWindow(); + if( pSysWin && pSysWin->GetMenuBar() ) + { + vcl::Window *pMenubarWin = pSysWin->GetMenuBar()->GetWindow(); + if( pMenubarWin ) + pMenubarWin->Invalidate(); + } + } +} + +void DockingAreaWindow::StateChanged( StateChangedType nType ) +{ + Window::StateChanged( nType ); + + if ( nType == StateChangedType::Visible ) + ImplInvalidateMenubar( this ); +} + +bool DockingAreaWindow::IsHorizontal() const +{ + return ( mpImplData->meAlign == WindowAlign::Top || mpImplData->meAlign == WindowAlign::Bottom ); +} + +void DockingAreaWindow::SetAlign( WindowAlign eNewAlign ) +{ + if( eNewAlign != mpImplData->meAlign ) + { + mpImplData->meAlign = eNewAlign; + Invalidate(); + } +} + +WindowAlign DockingAreaWindow::GetAlign() const +{ + return mpImplData->meAlign; +} + +void DockingAreaWindow::ApplySettings(vcl::RenderContext& rRenderContext) +{ + const StyleSettings rSetting = rRenderContext.GetSettings().GetStyleSettings(); + const BitmapEx& rPersonaBitmap = (GetAlign() == WindowAlign::Top) ? rSetting.GetPersonaHeader() : rSetting.GetPersonaFooter(); + + if (!rPersonaBitmap.IsEmpty() && (GetAlign() == WindowAlign::Top || GetAlign()==WindowAlign::Bottom)) + { + Wallpaper aWallpaper(rPersonaBitmap); + if (GetAlign() == WindowAlign::Top) + aWallpaper.SetStyle(WallpaperStyle::TopRight); + else + aWallpaper.SetStyle(WallpaperStyle::BottomRight); + aWallpaper.SetColor(rSetting.GetWorkspaceColor()); + + // we need to shift the bitmap vertically so that it spans over the + // menubar conveniently + SystemWindow* pSysWin = GetSystemWindow(); + MenuBar* pMenuBar = pSysWin ? pSysWin->GetMenuBar() : nullptr; + int nMenubarHeight = pMenuBar ? pMenuBar->GetMenuBarHeight() : 0; + aWallpaper.SetRect(tools::Rectangle(Point(0, -nMenubarHeight), + Size(rRenderContext.GetOutputWidthPixel(), + rRenderContext.GetOutputHeightPixel() + nMenubarHeight))); + + rRenderContext.SetBackground(aWallpaper); + } + else if (!rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Entire)) + { + Wallpaper aWallpaper; + aWallpaper.SetStyle(WallpaperStyle::ApplicationGradient); + rRenderContext.SetBackground(aWallpaper); + } + else + rRenderContext.SetBackground(Wallpaper(rSetting.GetFaceColor())); + +} + +void DockingAreaWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + const StyleSettings rSetting = rRenderContext.GetSettings().GetStyleSettings(); + + EnableNativeWidget(); // only required because the toolkit currently switches this flag off + if (!rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Entire)) + return; + + ToolbarValue aControlValue; + + if (GetAlign() == WindowAlign::Top && ImplGetSVData()->maNWFData.mbMenuBarDockingAreaCommonBG) + { + // give NWF a hint that this dockingarea is adjacent to the menubar + // useful for special gradient effects that should cover both windows + aControlValue.mbIsTopDockingArea = true; + } + + ControlState nState = ControlState::ENABLED; + const bool isFooter = GetAlign() == WindowAlign::Bottom && !rSetting.GetPersonaFooter().IsEmpty(); + + if ((GetAlign() == WindowAlign::Top && !rSetting.GetPersonaHeader().IsEmpty() ) || isFooter) + Erase(rRenderContext); + else if (!ImplGetSVData()->maNWFData.mbDockingAreaSeparateTB) + { + // draw a single toolbar background covering the whole docking area + tools::Rectangle aCtrlRegion(Point(), GetOutputSizePixel()); + + rRenderContext.DrawNativeControl(ControlType::Toolbar, IsHorizontal() ? ControlPart::DrawBackgroundHorz : ControlPart::DrawBackgroundVert, + aCtrlRegion, nState, aControlValue, OUString() ); + + if (!ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames) + { + // each toolbar gets a thin border to better recognize its borders on the homogeneous docking area + sal_uInt16 nChildren = GetChildCount(); + for (sal_uInt16 n = 0; n < nChildren; n++) + { + vcl::Window* pChild = GetChild(n); + if (pChild->IsVisible()) + { + Point aPos = pChild->GetPosPixel(); + Size aSize = pChild->GetSizePixel(); + tools::Rectangle aRect(aPos, aSize); + + rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetLightColor()); + rRenderContext.DrawLine(aRect.TopLeft(), aRect.TopRight()); + rRenderContext.DrawLine(aRect.TopLeft(), aRect.BottomLeft()); + + rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetSeparatorColor()); + rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight()); + rRenderContext.DrawLine(aRect.TopRight(), aRect.BottomRight()); + } + } + } + } + else + { + // create map to find toolbar lines + Size aOutSz(GetOutputSizePixel()); + std::map<int, int> ranges; + sal_uInt16 nChildren = GetChildCount(); + for (sal_uInt16 n = 0; n < nChildren; n++) + { + vcl::Window* pChild = GetChild(n); + Point aPos = pChild->GetPosPixel(); + Size aSize = pChild->GetSizePixel(); + if (IsHorizontal()) + ranges[aPos.Y()] = aSize.Height(); + else + ranges[aPos.X()] = aSize.Width(); + } + + // draw multiple toolbar backgrounds, i.e., one for each toolbar line + for (auto const& range : ranges) + { + tools::Rectangle aTBRect; + if (IsHorizontal()) + { + aTBRect.SetLeft( 0 ); + aTBRect.SetRight( aOutSz.Width() - 1 ); + aTBRect.SetTop( range.first ); + aTBRect.SetBottom( range.first + range.second - 1 ); + } + else + { + aTBRect.SetLeft( range.first ); + aTBRect.SetRight( range.first + range.second - 1 ); + aTBRect.SetTop( 0 ); + aTBRect.SetBottom( aOutSz.Height() - 1 ); + } + rRenderContext.DrawNativeControl(ControlType::Toolbar, IsHorizontal() ? ControlPart::DrawBackgroundHorz : ControlPart::DrawBackgroundVert, + aTBRect, nState, aControlValue, OUString()); + } + } +} + +void DockingAreaWindow::Resize() +{ + ImplInvalidateMenubar( this ); + if (IsNativeControlSupported(ControlType::Toolbar, ControlPart::Entire)) + Invalidate(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/dockmgr.cxx b/vcl/source/window/dockmgr.cxx new file mode 100644 index 000000000..f836ff2f1 --- /dev/null +++ b/vcl/source/window/dockmgr.cxx @@ -0,0 +1,1075 @@ +/* -*- 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/time.hxx> +#include <sal/log.hxx> +#include <o3tl/deleter.hxx> + +#include <brdwin.hxx> +#include <svdata.hxx> +#include <window.h> + +#include <vcl/event.hxx> +#include <vcl/toolkit/floatwin.hxx> +#include <vcl/dockwin.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/svapp.hxx> +#include <vcl/timer.hxx> +#include <vcl/settings.hxx> + +#include "impldockingwrapper.hxx" + +#define DOCKWIN_FLOATSTYLES (WB_SIZEABLE | WB_MOVEABLE | WB_CLOSEABLE | WB_STANDALONE) + +namespace { + +class ImplDockFloatWin2 : public FloatingWindow +{ +private: + ImplDockingWindowWrapper* mpDockWin; + sal_uInt64 mnLastTicks; + Timer m_aDockTimer; + Timer m_aEndDockTimer; + Point maDockPos; + tools::Rectangle maDockRect; + bool mbInMove; + ImplSVEvent * mnLastUserEvent; + + DECL_LINK(DockingHdl, void *, void); + DECL_LINK(DockTimerHdl, Timer *, void); + DECL_LINK(EndDockTimerHdl, Timer *, void); +public: + ImplDockFloatWin2( vcl::Window* pParent, WinBits nWinBits, + ImplDockingWindowWrapper* pDockingWin ); + virtual ~ImplDockFloatWin2() override; + virtual void dispose() override; + + virtual void Move() override; + virtual void Resize() override; + virtual void TitleButtonClick( TitleButton nButton ) override; + virtual void Resizing( Size& rSize ) override; + virtual bool Close() override; +}; + +} + +ImplDockFloatWin2::ImplDockFloatWin2( vcl::Window* pParent, WinBits nWinBits, + ImplDockingWindowWrapper* pDockingWin ) : + FloatingWindow( pParent, nWinBits ), + mpDockWin( pDockingWin ), + mnLastTicks( tools::Time::GetSystemTicks() ), + m_aDockTimer("vcl::ImplDockFloatWin2 m_aDockTimer"), + m_aEndDockTimer( "vcl::ImplDockFloatWin2 m_aEndDockTimer" ), + mbInMove( false ), + mnLastUserEvent( nullptr ) +{ + // copy state of DockingWindow + if ( pDockingWin ) + { + GetOutDev()->SetSettings( pDockingWin->GetWindow()->GetSettings() ); + Enable( pDockingWin->GetWindow()->IsEnabled(), false ); + EnableInput( pDockingWin->GetWindow()->IsInputEnabled(), false ); + AlwaysEnableInput( pDockingWin->GetWindow()->IsAlwaysEnableInput(), false ); + EnableAlwaysOnTop( pDockingWin->GetWindow()->IsAlwaysOnTopEnabled() ); + SetActivateMode( pDockingWin->GetWindow()->GetActivateMode() ); + } + + SetBackground( GetSettings().GetStyleSettings().GetFaceColor() ); + + m_aDockTimer.SetInvokeHandler( LINK( this, ImplDockFloatWin2, DockTimerHdl ) ); + m_aDockTimer.SetPriority( TaskPriority::HIGH_IDLE ); + m_aDockTimer.SetTimeout( 50 ); + + m_aEndDockTimer.SetInvokeHandler( LINK( this, ImplDockFloatWin2, EndDockTimerHdl ) ); + m_aEndDockTimer.SetPriority( TaskPriority::HIGH_IDLE ); + m_aEndDockTimer.SetTimeout( 50 ); +} + +ImplDockFloatWin2::~ImplDockFloatWin2() +{ + disposeOnce(); +} + +void ImplDockFloatWin2::dispose() +{ + if( mnLastUserEvent ) + Application::RemoveUserEvent( mnLastUserEvent ); + FloatingWindow::dispose(); +} + +IMPL_LINK_NOARG(ImplDockFloatWin2, DockTimerHdl, Timer *, void) +{ + SAL_WARN_IF( !mpDockWin->IsFloatingMode(), "vcl", "docktimer called but not floating" ); + + PointerState aState = GetPointerState(); + + if( aState.mnState & KEY_MOD1 ) + { + // i43499 CTRL disables docking now + mpDockWin->GetWindow()->GetParent()->ImplGetFrameWindow()->HideTracking(); + if( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) ) + m_aDockTimer.Start(); + } + else if( ! ( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) ) ) + { + mpDockWin->GetWindow()->GetParent()->ImplGetFrameWindow()->HideTracking(); + mpDockWin->EndDocking( maDockRect, false ); + } + else + { + mpDockWin->GetWindow()->GetParent()->ImplGetFrameWindow()->ShowTracking( maDockRect, ShowTrackFlags::Big | ShowTrackFlags::TrackWindow ); + m_aDockTimer.Start(); + } +} + +IMPL_LINK_NOARG(ImplDockFloatWin2, EndDockTimerHdl, Timer *, void) +{ + SAL_WARN_IF( !mpDockWin->IsFloatingMode(), "vcl", "enddocktimer called but not floating" ); + + PointerState aState = GetPointerState(); + if( ! ( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) ) ) + { + mpDockWin->GetWindow()->GetParent()->ImplGetFrameWindow()->HideTracking(); + mpDockWin->EndDocking( maDockRect, true ); + } + else + m_aEndDockTimer.Start(); +} + +IMPL_LINK_NOARG(ImplDockFloatWin2, DockingHdl, void*, void) +{ + // called during move of a floating window + mnLastUserEvent = nullptr; + + vcl::Window *pDockingArea = mpDockWin->GetWindow()->GetParent(); + PointerState aState = pDockingArea->GetPointerState(); + + bool bRealMove = true; + if( GetStyle() & WB_OWNERDRAWDECORATION ) + { + // for windows with ownerdraw decoration + // we allow docking only when the window was moved + // by dragging its caption + // and ignore move request due to resizing + vcl::Window *pBorder = GetWindow( GetWindowType::Border ); + if( pBorder != this ) + { + tools::Rectangle aBorderRect( Point(), pBorder->GetSizePixel() ); + sal_Int32 nLeft, nTop, nRight, nBottom; + GetBorder( nLeft, nTop, nRight, nBottom ); + // limit borderrect to the caption part only and without the resizing borders + aBorderRect.SetBottom( aBorderRect.Top() + nTop ); + aBorderRect.AdjustLeft(nLeft ); + aBorderRect.AdjustRight( -nRight ); + + PointerState aBorderState = pBorder->GetPointerState(); + bRealMove = aBorderRect.Contains( aBorderState.maPos ); + } + } + + if( mpDockWin->GetWindow()->IsVisible() && + (tools::Time::GetSystemTicks() - mnLastTicks > 500) && + ( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) ) && + !(aState.mnState & KEY_MOD1) && // i43499 CTRL disables docking now + bRealMove ) + { + maDockPos = pDockingArea->OutputToScreenPixel( pDockingArea->AbsoluteScreenToOutputPixel( OutputToAbsoluteScreenPixel( Point() ) ) ); + maDockRect = tools::Rectangle( maDockPos, mpDockWin->GetSizePixel() ); + + // mouse pos in screen pixels + Point aMousePos = pDockingArea->OutputToScreenPixel( aState.maPos ); + + if( ! mpDockWin->IsDocking() ) + mpDockWin->StartDocking( aMousePos, maDockRect ); + + bool bFloatMode = mpDockWin->Docking( aMousePos, maDockRect ); + + if( ! bFloatMode ) + { + // indicates that the window could be docked at maDockRect + maDockRect.SetPos( mpDockWin->GetWindow()->GetParent()->ImplGetFrameWindow()->ScreenToOutputPixel( + maDockRect.TopLeft() ) ); + mpDockWin->GetWindow()->GetParent()->ImplGetFrameWindow()->ShowTracking( maDockRect, ShowTrackFlags::Big | ShowTrackFlags::TrackWindow ); + m_aEndDockTimer.Stop(); + m_aDockTimer.Invoke(); + } + else + { + mpDockWin->GetWindow()->GetParent()->ImplGetFrameWindow()->HideTracking(); + m_aDockTimer.Stop(); + m_aEndDockTimer.Invoke(); + } + } + mbInMove = false; +} + +void ImplDockFloatWin2::Move() +{ + if( mbInMove ) + return; + + mbInMove = true; + FloatingWindow::Move(); + mpDockWin->GetWindow()->Move(); + + /* + * note: the window should only dock if KEY_MOD1 is pressed + * and the user releases all mouse buttons. The real problem here + * is that we don't get mouse events (at least not on X) + * if the mouse is on the decoration. So we have to start an + * awkward timer based process that polls the modifier/buttons + * to see whether they are in the right condition shortly after the + * last Move message. + */ + if( ! mnLastUserEvent ) + mnLastUserEvent = Application::PostUserEvent( LINK( this, ImplDockFloatWin2, DockingHdl ), nullptr, true ); +} + +void ImplDockFloatWin2::Resize() +{ + // forwarding of resize only required if we have no borderwindow ( GetWindow() then returns 'this' ) + if( GetWindow( GetWindowType::Border ) == this ) + { + FloatingWindow::Resize(); + Size aSize( GetSizePixel() ); + mpDockWin->GetWindow()->ImplPosSizeWindow( 0, 0, aSize.Width(), aSize.Height(), PosSizeFlags::PosSize ); // TODO: is this needed ??? + } +} + +void ImplDockFloatWin2::TitleButtonClick( TitleButton nButton ) +{ + FloatingWindow::TitleButtonClick( nButton ); + mpDockWin->TitleButtonClick( nButton ); +} + +void ImplDockFloatWin2::Resizing( Size& rSize ) +{ + FloatingWindow::Resizing( rSize ); + mpDockWin->Resizing( rSize ); +} + +bool ImplDockFloatWin2::Close() +{ + return true; +} + +DockingManager::DockingManager() +{ +} + +DockingManager::~DockingManager() +{ +} + +ImplDockingWindowWrapper* DockingManager::GetDockingWindowWrapper( const vcl::Window *pWindow ) +{ + for( const auto& xWrapper : mvDockingWindows ) + { + if (xWrapper && xWrapper->mpDockingWindow == pWindow) + return xWrapper.get(); + } + return nullptr; +} + +bool DockingManager::IsDockable( const vcl::Window *pWindow ) +{ + ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow ); + + /* + if( pWindow->HasDockingHandler() ) + return true; + */ + return (pWrapper != nullptr); +} + +bool DockingManager::IsFloating( const vcl::Window *pWindow ) +{ + ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow ); + if( pWrapper ) + return pWrapper->IsFloatingMode(); + else + return false; +} + +bool DockingManager::IsLocked( const vcl::Window *pWindow ) +{ + ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow ); + return pWrapper && pWrapper->IsLocked(); +} + +void DockingManager::Lock( const vcl::Window *pWindow ) +{ + ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow ); + if( pWrapper ) + pWrapper->Lock(); +} + +void DockingManager::Unlock( const vcl::Window *pWindow ) +{ + ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow ); + if( pWrapper ) + pWrapper->Unlock(); +} + +void DockingManager::SetFloatingMode( const vcl::Window *pWindow, bool bFloating ) +{ + ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow ); + if( pWrapper ) + pWrapper->SetFloatingMode( bFloating ); +} + +void DockingManager::StartPopupMode( const vcl::Window *pWindow, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags ) +{ + ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow ); + if( pWrapper ) + pWrapper->StartPopupMode( rRect, nFlags ); +} + +void DockingManager::StartPopupMode( ToolBox *pParentToolBox, const vcl::Window *pWindow, FloatWinPopupFlags nFlags ) +{ + ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow ); + if( pWrapper ) + pWrapper->StartPopupMode( pParentToolBox, nFlags ); +} + +void DockingManager::StartPopupMode( ToolBox *pParentToolBox, const vcl::Window *pWindow ) +{ + StartPopupMode( pParentToolBox, pWindow, FloatWinPopupFlags::AllowTearOff | + FloatWinPopupFlags::AllMouseButtonClose | + FloatWinPopupFlags::NoMouseUpClose ); +} + +bool DockingManager::IsInPopupMode( const vcl::Window *pWindow ) +{ + ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow ); + return pWrapper && pWrapper->IsInPopupMode(); +} + +void DockingManager::EndPopupMode( const vcl::Window *pWin ) +{ + ImplDockingWindowWrapper *pWrapper = GetDockingWindowWrapper( pWin ); + if( pWrapper && pWrapper->GetFloatingWindow() && static_cast<FloatingWindow*>(pWrapper->GetFloatingWindow())->IsInPopupMode() ) + static_cast<FloatingWindow*>(pWrapper->GetFloatingWindow())->EndPopupMode(); +} + +SystemWindow* DockingManager::GetFloatingWindow(const vcl::Window *pWin) +{ + ImplDockingWindowWrapper *pWrapper = GetDockingWindowWrapper( pWin ); + if (pWrapper) + return pWrapper->GetFloatingWindow(); + return nullptr; +} + +void DockingManager::SetPopupModeEndHdl( const vcl::Window *pWindow, const Link<FloatingWindow*,void>& rLink ) +{ + ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow ); + if( pWrapper ) + pWrapper->SetPopupModeEndHdl(rLink); +} + +void DockingManager::AddWindow( const vcl::Window *pWindow ) +{ + ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow ); + if( pWrapper ) + return; + mvDockingWindows.emplace_back( new ImplDockingWindowWrapper( pWindow ) ); +} + +void DockingManager::RemoveWindow( const vcl::Window *pWindow ) +{ + for( auto it = mvDockingWindows.begin(); it != mvDockingWindows.end(); ++it ) + { + const auto& xWrapper = *it; + if (xWrapper && xWrapper->mpDockingWindow == pWindow) + { + // deleting wrappers calls set of actions which may want to use + // wrapper we want to delete - avoid crash using temporary owner + // while erasing + auto pTemporaryOwner = std::move(*it); + mvDockingWindows.erase( it ); + break; + } + } +} + +void DockingManager::SetPosSizePixel( vcl::Window const *pWindow, tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + PosSizeFlags nFlags ) +{ + ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow ); + if( pWrapper ) + pWrapper->setPosSizePixel( nX, nY, nWidth, nHeight, nFlags ); +} + +tools::Rectangle DockingManager::GetPosSizePixel( const vcl::Window *pWindow ) +{ + tools::Rectangle aRect; + ImplDockingWindowWrapper* pWrapper = GetDockingWindowWrapper( pWindow ); + if( pWrapper ) + aRect = tools::Rectangle( pWrapper->GetPosPixel(), pWrapper->GetSizePixel() ); + + return aRect; +} + +class ImplPopupFloatWin : public FloatingWindow +{ +private: + bool mbToolBox; + +public: + ImplPopupFloatWin( vcl::Window* pParent, bool bToolBox ); + virtual ~ImplPopupFloatWin() override; + virtual css::uno::Reference< css::accessibility::XAccessible > CreateAccessible() override; +}; + +ImplPopupFloatWin::ImplPopupFloatWin( vcl::Window* pParent, bool bToolBox ) : + FloatingWindow( pParent, bToolBox ? WB_BORDER | WB_POPUP | WB_SYSTEMWINDOW | WB_NOSHADOW : WB_STDPOPUP ), + mbToolBox( bToolBox ) +{ + if ( bToolBox ) + { + // indicate window type, required for accessibility + // which should not see this window as a toplevel window + mpWindowImpl->mbToolbarFloatingWindow = true; + } +} + +ImplPopupFloatWin::~ImplPopupFloatWin() +{ + disposeOnce(); +} + +css::uno::Reference< css::accessibility::XAccessible > ImplPopupFloatWin::CreateAccessible() +{ + if ( !mbToolBox ) + return FloatingWindow::CreateAccessible(); + + // switch off direct accessibility support for this window + + // this is to avoid appearance of this window as standalone window in the accessibility hierarchy + // as this window is only used as a helper for subtoolbars that are not teared-off, the parent toolbar + // has to provide accessibility support (as implemented in the toolkit) + // so the contained toolbar should appear as child of the corresponding toolbar item of the parent toolbar + return css::uno::Reference< css::accessibility::XAccessible >(); +} + +ImplDockingWindowWrapper::ImplDockingWindowWrapper( const vcl::Window *pWindow ) + : mpDockingWindow(const_cast<vcl::Window*>(pWindow)) + , mpFloatWin(nullptr) + , mpOldBorderWin(nullptr) + , mpParent(pWindow->GetParent()) + , maMaxOutSize( SHRT_MAX, SHRT_MAX ) + , mnTrackX(0) + , mnTrackY(0) + , mnTrackWidth(0) + , mnTrackHeight(0) + , mnDockLeft(0) + , mnDockTop(0) + , mnDockRight(0) + , mnDockBottom(0) + , mnFloatBits(WB_BORDER | WB_CLOSEABLE | WB_SIZEABLE | (pWindow->GetStyle() & DOCKWIN_FLOATSTYLES)) + , mbDockCanceled(false) + , mbDocking(false) + , mbLastFloatMode(false) + , mbDockBtn(false) + , mbHideBtn(false) + // must be enabled in Window::Notify to prevent permanent docking during mouse move + , mbStartDockingEnabled(false) + , mbLocked(false) +{ + assert(mpDockingWindow); + DockingWindow *pDockWin = dynamic_cast< DockingWindow* > ( mpDockingWindow.get() ); + if( pDockWin ) + mnFloatBits = pDockWin->GetFloatStyle(); +} + +ImplDockingWindowWrapper::~ImplDockingWindowWrapper() +{ + if ( IsFloatingMode() ) + { + GetWindow()->Show( false, ShowFlags::NoFocusChange ); + SetFloatingMode(false); + } +} + +void ImplDockingWindowWrapper::ImplStartDocking( const Point& rPos ) +{ + if( !mbStartDockingEnabled ) + return; + + maMouseOff = rPos; + mbDocking = true; + mbLastFloatMode = IsFloatingMode(); + + // calculate FloatingBorder + VclPtr<FloatingWindow> pWin; + if ( mpFloatWin ) + pWin = mpFloatWin; + else + pWin = VclPtr<ImplDockFloatWin2>::Create( mpParent, mnFloatBits, nullptr ); + pWin->GetBorder( mnDockLeft, mnDockTop, mnDockRight, mnDockBottom ); + if ( !mpFloatWin ) + pWin.disposeAndClear(); + + Point aPos = GetWindow()->ImplOutputToFrame( Point() ); + Size aSize = GetWindow()->GetOutputSizePixel(); + mnTrackX = aPos.X(); + mnTrackY = aPos.Y(); + mnTrackWidth = aSize.Width(); + mnTrackHeight = aSize.Height(); + + if ( mbLastFloatMode ) + { + maMouseOff.AdjustX(mnDockLeft ); + maMouseOff.AdjustY(mnDockTop ); + mnTrackX -= mnDockLeft; + mnTrackY -= mnDockTop; + mnTrackWidth += mnDockLeft+mnDockRight; + mnTrackHeight += mnDockTop+mnDockBottom; + } + + vcl::Window *pDockingArea = GetWindow()->GetParent(); + vcl::Window::PointerState aState = pDockingArea->GetPointerState(); + + // mouse pos in screen pixels + Point aMousePos = pDockingArea->OutputToScreenPixel( aState.maPos ); + Point aDockPos = pDockingArea->AbsoluteScreenToOutputPixel( GetWindow()->OutputToAbsoluteScreenPixel( GetWindow()->GetPosPixel() ) ); + tools::Rectangle aDockRect( aDockPos, GetWindow()->GetSizePixel() ); + StartDocking( aMousePos, aDockRect ); + + GetWindow()->ImplUpdateAll(); + GetWindow()->ImplGetFrameWindow()->ImplUpdateAll(); + + GetWindow()->StartTracking( StartTrackingFlags::KeyMod ); +} + +void ImplDockingWindowWrapper::Tracking( const TrackingEvent& rTEvt ) +{ + // used during docking of a currently docked window + if ( !mbDocking ) + return; + + if ( rTEvt.IsTrackingEnded() ) + { + mbDocking = false; + GetWindow()->HideTracking(); + if ( rTEvt.IsTrackingCanceled() ) + { + mbDockCanceled = true; + EndDocking( tools::Rectangle( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) ), mbLastFloatMode ); + mbDockCanceled = false; + } + else + EndDocking( tools::Rectangle( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) ), mbLastFloatMode ); + } + // Docking only upon non-synthetic MouseEvents + else if ( !rTEvt.GetMouseEvent().IsSynthetic() || rTEvt.GetMouseEvent().IsModifierChanged() ) + { + Point aMousePos = rTEvt.GetMouseEvent().GetPosPixel(); + Point aFrameMousePos = GetWindow()->ImplOutputToFrame( aMousePos ); + Size aFrameSize = GetWindow()->ImplGetFrameWindow()->GetOutputSizePixel(); + if ( aFrameMousePos.X() < 0 ) + aFrameMousePos.setX( 0 ); + if ( aFrameMousePos.Y() < 0 ) + aFrameMousePos.setY( 0 ); + if ( aFrameMousePos.X() > aFrameSize.Width()-1 ) + aFrameMousePos.setX( aFrameSize.Width()-1 ); + if ( aFrameMousePos.Y() > aFrameSize.Height()-1 ) + aFrameMousePos.setY( aFrameSize.Height()-1 ); + aMousePos = GetWindow()->ImplFrameToOutput( aFrameMousePos ); + aMousePos.AdjustX( -(maMouseOff.X()) ); + aMousePos.AdjustY( -(maMouseOff.Y()) ); + Point aPos = GetWindow()->ImplOutputToFrame( aMousePos ); + tools::Rectangle aTrackRect( aPos, Size( mnTrackWidth, mnTrackHeight ) ); + tools::Rectangle aCompRect = aTrackRect; + aPos.AdjustX(maMouseOff.X() ); + aPos.AdjustY(maMouseOff.Y() ); + + bool bFloatMode = Docking( aPos, aTrackRect ); + + if ( mbLastFloatMode != bFloatMode ) + { + if ( bFloatMode ) + { + aTrackRect.AdjustLeft( -mnDockLeft ); + aTrackRect.AdjustTop( -mnDockTop ); + aTrackRect.AdjustRight(mnDockRight ); + aTrackRect.AdjustBottom(mnDockBottom ); + } + else + { + if ( aCompRect == aTrackRect ) + { + aTrackRect.AdjustLeft(mnDockLeft ); + aTrackRect.AdjustTop(mnDockTop ); + aTrackRect.AdjustRight( -mnDockRight ); + aTrackRect.AdjustBottom( -mnDockBottom ); + } + } + mbLastFloatMode = bFloatMode; + } + + ShowTrackFlags nTrackStyle; + if ( bFloatMode ) + nTrackStyle = ShowTrackFlags::Object; + else + nTrackStyle = ShowTrackFlags::Big; + tools::Rectangle aShowTrackRect = aTrackRect; + aShowTrackRect.SetPos( GetWindow()->ImplFrameToOutput( aShowTrackRect.TopLeft() ) ); + + GetWindow()->ShowTracking( aShowTrackRect, nTrackStyle ); + + // calculate mouse offset again, as the rectangle was changed + maMouseOff.setX( aPos.X() - aTrackRect.Left() ); + maMouseOff.setY( aPos.Y() - aTrackRect.Top() ); + + mnTrackX = aTrackRect.Left(); + mnTrackY = aTrackRect.Top(); + mnTrackWidth = aTrackRect.GetWidth(); + mnTrackHeight = aTrackRect.GetHeight(); + } +} + +void ImplDockingWindowWrapper::StartDocking( const Point& rPoint, tools::Rectangle const & rRect ) +{ + DockingData data( rPoint, rRect, IsFloatingMode() ); + + GetWindow()->CallEventListeners( VclEventId::WindowStartDocking, &data ); + mbDocking = true; +} + +bool ImplDockingWindowWrapper::Docking( const Point& rPoint, tools::Rectangle& rRect ) +{ + DockingData data( rPoint, rRect, IsFloatingMode() ); + + GetWindow()->CallEventListeners( VclEventId::WindowDocking, &data ); + rRect = data.maTrackRect; + return data.mbFloating; +} + +void ImplDockingWindowWrapper::EndDocking( const tools::Rectangle& rRect, bool bFloatMode ) +{ + tools::Rectangle aRect( rRect ); + + bool bOrigDockCanceled = mbDockCanceled; + if (bFloatMode && !StyleSettings::GetDockingFloatsSupported()) + mbDockCanceled = true; + + if ( !IsDockingCanceled() ) + { + bool bShow = false; + if ( bFloatMode != IsFloatingMode() ) + { + GetWindow()->Show( false, ShowFlags::NoFocusChange ); + SetFloatingMode( bFloatMode ); + bShow = true; + if ( bFloatMode ) + { + // #i44800# always use outputsize - as in all other places + mpFloatWin->SetOutputSizePixel( aRect.GetSize() ); + mpFloatWin->SetPosPixel( aRect.TopLeft() ); + } + } + if ( !bFloatMode ) + { + Point aPos = aRect.TopLeft(); + aPos = GetWindow()->GetParent()->ScreenToOutputPixel( aPos ); + GetWindow()->SetPosSizePixel( aPos, aRect.GetSize() ); + } + + if ( bShow ) + GetWindow()->Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate ); + } + + EndDockingData data( aRect, IsFloatingMode(), IsDockingCanceled() ); + GetWindow()->CallEventListeners( VclEventId::WindowEndDocking, &data ); + + mbDocking = false; + + // must be enabled in Window::Notify to prevent permanent docking during mouse move + mbStartDockingEnabled = false; + + mbDockCanceled = bOrigDockCanceled; +} + +bool ImplDockingWindowWrapper::PrepareToggleFloatingMode() +{ + bool bFloating = true; + GetWindow()->CallEventListeners( VclEventId::WindowPrepareToggleFloating, &bFloating ); + return bFloating; +} + +void ImplDockingWindowWrapper::ToggleFloatingMode() +{ + // notify dockingwindow/toolbox + // note: this must be done *before* notifying the + // listeners to have the toolbox in the proper state + if( GetWindow()->IsDockingWindow() ) + static_cast<DockingWindow*>(GetWindow())->ToggleFloatingMode(); + + // now notify listeners + GetWindow()->CallEventListeners( VclEventId::WindowToggleFloating ); + + // must be enabled in Window::Notify to prevent permanent docking during mouse move + mbStartDockingEnabled = false; +} + +void ImplDockingWindowWrapper::TitleButtonClick( TitleButton nType ) +{ + if( nType == TitleButton::Menu ) + { + ToolBox *pToolBox = dynamic_cast< ToolBox* >( GetWindow() ); + if( pToolBox ) + { + pToolBox->ExecuteCustomMenu(); + } + } + if( nType == TitleButton::Docking ) + { + SetFloatingMode( !IsFloatingMode() ); + } +} + +void ImplDockingWindowWrapper::Resizing( Size& rSize ) +{ + // TODO: add virtual Resizing() to class Window, so we can get rid of class DockingWindow + DockingWindow *pDockingWindow = dynamic_cast< DockingWindow* >( GetWindow() ); + if( pDockingWindow ) + pDockingWindow->Resizing( rSize ); +} + +void ImplDockingWindowWrapper::ShowMenuTitleButton( bool bVisible ) +{ + if ( mpFloatWin ) + mpFloatWin->ShowTitleButton( TitleButton::Menu, bVisible ); +} + +void ImplDockingWindowWrapper::ImplPreparePopupMode() +{ + VclPtr<vcl::Window> xWindow = GetWindow(); + xWindow->Show( false, ShowFlags::NoFocusChange ); + + // prepare reparenting + vcl::Window* pRealParent = xWindow->GetWindow( GetWindowType::Parent ); + mpOldBorderWin = xWindow->GetWindow( GetWindowType::Border ); + if( mpOldBorderWin.get() == xWindow ) + mpOldBorderWin = nullptr; // no border window found + + // the new parent for popup mode + VclPtrInstance<ImplPopupFloatWin> pWin( mpParent, xWindow->GetType() == WindowType::TOOLBOX ); + pWin->SetPopupModeEndHdl( LINK( this, ImplDockingWindowWrapper, PopupModeEnd ) ); + + // At least for DockingWindow, GetText() has a side effect of setting deferred + // properties. This must be done before setting the border window (see below), + // so that the border width will end up in mpWindowImpl->mnBorderWidth, not in + // the border window (See DockingWindow::setPosSizeOnContainee() and + // DockingWindow::GetOptimalSize()). + pWin->SetText( xWindow->GetText() ); + pWin->SetOutputSizePixel( xWindow->GetSizePixel() ); + + xWindow->mpWindowImpl->mpBorderWindow = nullptr; + xWindow->mpWindowImpl->mnLeftBorder = 0; + xWindow->mpWindowImpl->mnTopBorder = 0; + xWindow->mpWindowImpl->mnRightBorder = 0; + xWindow->mpWindowImpl->mnBottomBorder = 0; + + // reparent borderwindow and window + if ( mpOldBorderWin ) + mpOldBorderWin->SetParent( pWin ); + xWindow->SetParent( pWin ); + + // correct border window pointers + xWindow->mpWindowImpl->mpBorderWindow = pWin; + pWin->mpWindowImpl->mpClientWindow = xWindow; + xWindow->mpWindowImpl->mpRealParent = pRealParent; + + // set mpFloatWin not until all window positioning is done !!! + // (SetPosPixel etc. check for valid mpFloatWin pointer) + mpFloatWin = pWin; +} + +void ImplDockingWindowWrapper::StartPopupMode( ToolBox *pParentToolBox, FloatWinPopupFlags nFlags ) +{ + // do nothing if window is floating + if( IsFloatingMode() ) + return; + + ImplPreparePopupMode(); + + // don't allow tearoff, if globally disabled + if( !StyleSettings::GetDockingFloatsSupported() ) + nFlags &= ~FloatWinPopupFlags::AllowTearOff; + + // if the subtoolbar was opened via keyboard make sure that key events + // will go into subtoolbar + if( pParentToolBox->IsKeyEvent() ) + nFlags |= FloatWinPopupFlags::GrabFocus; + + mpFloatWin->StartPopupMode( pParentToolBox, nFlags ); + GetWindow()->Show(); + + if( pParentToolBox->IsKeyEvent() ) + { + // send HOME key to subtoolbar in order to select first item + KeyEvent aEvent( 0, vcl::KeyCode( KEY_HOME ) ); + GetWindow()->KeyInput(aEvent); + } +} + +void ImplDockingWindowWrapper::StartPopupMode( const tools::Rectangle& rRect, FloatWinPopupFlags nFlags ) +{ + // do nothing if window is floating + if( IsFloatingMode() ) + return; + + ImplPreparePopupMode(); + mpFloatWin->StartPopupMode( rRect, nFlags ); + GetWindow()->Show(); +} + +IMPL_LINK_NOARG(ImplDockingWindowWrapper, PopupModeEnd, FloatingWindow*, void) +{ + VclPtr<vcl::Window> xWindow = GetWindow(); + xWindow->Show( false, ShowFlags::NoFocusChange ); + + // set parameter for handler before destroying floating window + EndPopupModeData aData( mpFloatWin->GetWindow( GetWindowType::Border )->GetPosPixel(), mpFloatWin->IsPopupModeTearOff() ); + + // before deleting change parent back, so we can delete the floating window alone + vcl::Window* pRealParent = xWindow->GetWindow( GetWindowType::Parent ); + xWindow->mpWindowImpl->mpBorderWindow = nullptr; + if ( mpOldBorderWin ) + { + xWindow->SetParent( mpOldBorderWin ); + static_cast<ImplBorderWindow*>(mpOldBorderWin.get())->GetBorder( + xWindow->mpWindowImpl->mnLeftBorder, xWindow->mpWindowImpl->mnTopBorder, + xWindow->mpWindowImpl->mnRightBorder, xWindow->mpWindowImpl->mnBottomBorder ); + mpOldBorderWin->Resize(); + } + xWindow->mpWindowImpl->mpBorderWindow = mpOldBorderWin; + xWindow->SetParent( pRealParent ); + xWindow->mpWindowImpl->mpRealParent = pRealParent; + + // take ownership to local variable to protect against maPopupModeEndHdl destroying this object + auto xFloatWin = std::move(mpFloatWin); + maPopupModeEndHdl.Call(xFloatWin); + xFloatWin.disposeAndClear(); + + // call handler - which will destroy the window and thus the wrapper as well ! + xWindow->CallEventListeners( VclEventId::WindowEndPopupMode, &aData ); +} + +bool ImplDockingWindowWrapper::IsInPopupMode() const +{ + if( GetFloatingWindow() ) + return static_cast<FloatingWindow*>(GetFloatingWindow())->IsInPopupMode(); + else + return false; +} + +void ImplDockingWindowWrapper::SetFloatingMode( bool bFloatMode ) +{ + // do nothing if window is docked and locked + if( !IsFloatingMode() && IsLocked() ) + return; + + if ( IsFloatingMode() == bFloatMode ) + return; + + if ( !PrepareToggleFloatingMode() ) + return; + + bool bVisible = GetWindow()->IsVisible(); + + if ( bFloatMode ) + { + GetWindow()->Show( false, ShowFlags::NoFocusChange ); + + maDockPos = GetWindow()->GetPosPixel(); + + vcl::Window* pRealParent = GetWindow()->GetWindow( GetWindowType::Parent ); + mpOldBorderWin = GetWindow()->GetWindow( GetWindowType::Border ); + if( mpOldBorderWin == mpDockingWindow ) + mpOldBorderWin = nullptr; // no border window found + + VclPtrInstance<ImplDockFloatWin2> pWin( + mpParent, + mnFloatBits & ( WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE ) ? + mnFloatBits | WB_SYSTEMWINDOW + | WB_OWNERDRAWDECORATION + : mnFloatBits, + this ); + + // At least for DockingWindow, GetText() has a side effect of setting deferred + // properties. This must be done before setting the border window (see below), + // so that the border width will end up in mpWindowImpl->mnBorderWidth, not in + // the border window (See DockingWindow::setPosSizeOnContainee() and + // DockingWindow::GetOptimalSize()). + pWin->SetText( GetWindow()->GetText() ); + + GetWindow()->mpWindowImpl->mpBorderWindow = nullptr; + GetWindow()->mpWindowImpl->mnLeftBorder = 0; + GetWindow()->mpWindowImpl->mnTopBorder = 0; + GetWindow()->mpWindowImpl->mnRightBorder = 0; + GetWindow()->mpWindowImpl->mnBottomBorder = 0; + + // if the parent gets destroyed, we also have to reset the parent of the BorderWindow + if ( mpOldBorderWin ) + mpOldBorderWin->SetParent( pWin ); + GetWindow()->SetParent( pWin ); + pWin->SetPosPixel( Point() ); + + GetWindow()->mpWindowImpl->mpBorderWindow = pWin; + pWin->mpWindowImpl->mpClientWindow = mpDockingWindow; + GetWindow()->mpWindowImpl->mpRealParent = pRealParent; + + pWin->SetOutputSizePixel( GetWindow()->GetSizePixel() ); + pWin->SetPosPixel( maFloatPos ); + // pass on DockingData to FloatingWindow + pWin->ShowTitleButton( TitleButton::Docking, mbDockBtn ); + pWin->ShowTitleButton( TitleButton::Hide, mbHideBtn ); + pWin->SetMinOutputSizePixel( maMinOutSize ); + pWin->SetMaxOutputSizePixel( maMaxOutSize ); + + mpFloatWin = pWin; + + if ( bVisible ) + GetWindow()->Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate ); + + ToggleFloatingMode(); + } + else + { + GetWindow()->Show( false, ShowFlags::NoFocusChange ); + + // store FloatingData in FloatingWindow + maFloatPos = mpFloatWin->GetPosPixel(); + mbDockBtn = mpFloatWin->IsTitleButtonVisible( TitleButton::Docking ); + mbHideBtn = mpFloatWin->IsTitleButtonVisible( TitleButton::Hide ); + maMinOutSize = mpFloatWin->GetMinOutputSizePixel(); + maMaxOutSize = mpFloatWin->GetMaxOutputSizePixel(); + + vcl::Window* pRealParent = GetWindow()->GetWindow( GetWindowType::Parent ); //mpWindowImpl->mpRealParent; + GetWindow()->mpWindowImpl->mpBorderWindow = nullptr; + if ( mpOldBorderWin ) + { + GetWindow()->SetParent( mpOldBorderWin ); + static_cast<ImplBorderWindow*>(mpOldBorderWin.get())->GetBorder( + GetWindow()->mpWindowImpl->mnLeftBorder, GetWindow()->mpWindowImpl->mnTopBorder, + GetWindow()->mpWindowImpl->mnRightBorder, GetWindow()->mpWindowImpl->mnBottomBorder ); + mpOldBorderWin->Resize(); + } + GetWindow()->mpWindowImpl->mpBorderWindow = mpOldBorderWin; + GetWindow()->SetParent( pRealParent ); + GetWindow()->mpWindowImpl->mpRealParent = pRealParent; + + mpFloatWin.disposeAndClear(); + GetWindow()->SetPosPixel( maDockPos ); + + if ( bVisible ) + GetWindow()->Show(); + + ToggleFloatingMode(); + + } +} + +void ImplDockingWindowWrapper::SetFloatStyle( WinBits nStyle ) +{ + mnFloatBits = nStyle; +} + + +void ImplDockingWindowWrapper::setPosSizePixel( tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + PosSizeFlags nFlags ) +{ + if ( mpFloatWin ) + mpFloatWin->setPosSizePixel( nX, nY, nWidth, nHeight, nFlags ); + else + GetWindow()->setPosSizePixel( nX, nY, nWidth, nHeight, nFlags ); +} + +Point ImplDockingWindowWrapper::GetPosPixel() const +{ + if ( mpFloatWin ) + return mpFloatWin->GetPosPixel(); + else + return mpDockingWindow->GetPosPixel(); +} + +Size ImplDockingWindowWrapper::GetSizePixel() const +{ + if ( mpFloatWin ) + return mpFloatWin->GetSizePixel(); + else + return mpDockingWindow->GetSizePixel(); +} + +// old inlines from DockingWindow + +void ImplDockingWindowWrapper::SetMinOutputSizePixel( const Size& rSize ) +{ + if ( mpFloatWin ) + mpFloatWin->SetMinOutputSizePixel( rSize ); + maMinOutSize = rSize; +} + +void ImplDockingWindowWrapper::SetMaxOutputSizePixel( const Size& rSize ) +{ + if ( mpFloatWin ) + mpFloatWin->SetMaxOutputSizePixel( rSize ); + maMaxOutSize = rSize; +} + +bool ImplDockingWindowWrapper::IsFloatingMode() const +{ + return (mpFloatWin != nullptr); +} + +void ImplDockingWindowWrapper::SetDragArea( const tools::Rectangle& rRect ) +{ + maDragArea = rRect; +} + + +void ImplDockingWindowWrapper::Lock() +{ + mbLocked = true; + // only toolbars support locking + ToolBox *pToolBox = dynamic_cast< ToolBox * >( GetWindow() ); + if( pToolBox ) + pToolBox->Lock( mbLocked ); +} + +void ImplDockingWindowWrapper::Unlock() +{ + mbLocked = false; + // only toolbars support locking + ToolBox *pToolBox = dynamic_cast< ToolBox * >( GetWindow() ); + if( pToolBox ) + pToolBox->Lock( mbLocked ); +} + +SystemWindow* ImplDockingWindowWrapper::GetFloatingWindow() const +{ + return mpFloatWin; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/dockwin.cxx b/vcl/source/window/dockwin.cxx new file mode 100644 index 000000000..fb216913b --- /dev/null +++ b/vcl/source/window/dockwin.cxx @@ -0,0 +1,1147 @@ +/* -*- 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/time.hxx> +#include <sal/log.hxx> +#include <vcl/event.hxx> +#include <vcl/toolkit/floatwin.hxx> +#include <vcl/layout.hxx> +#include <vcl/dockwin.hxx> +#include <vcl/svapp.hxx> +#include <vcl/timer.hxx> +#include <vcl/idle.hxx> +#include <vcl/settings.hxx> +#include <comphelper/lok.hxx> + +#include <accel.hxx> +#include <svdata.hxx> +#include <window.h> +#include <brdwin.hxx> + +#include "impldockingwrapper.hxx" + +#define DOCKWIN_FLOATSTYLES (WB_SIZEABLE | WB_MOVEABLE | WB_CLOSEABLE | WB_STANDALONE) + +class DockingWindow::ImplData +{ +public: + ImplData(); + + VclPtr<vcl::Window> mpParent; + Size maMaxOutSize; +}; + +DockingWindow::ImplData::ImplData() +{ + mpParent = nullptr; + maMaxOutSize = Size( SHRT_MAX, SHRT_MAX ); +} + +namespace { + +class ImplDockFloatWin : public FloatingWindow +{ +private: + VclPtr<DockingWindow> mpDockWin; + sal_uInt64 mnLastTicks; + Idle maDockIdle; + Point maDockPos; + tools::Rectangle maDockRect; + bool mbInMove; + ImplSVEvent * mnLastUserEvent; + + DECL_LINK(DockingHdl, void *, void); + DECL_LINK(DockTimerHdl, Timer *, void); +public: + ImplDockFloatWin( vcl::Window* pParent, WinBits nWinBits, + DockingWindow* pDockingWin ); + virtual ~ImplDockFloatWin() override; + virtual void dispose() override; + + virtual void Move() override; + virtual void Resize() override; + virtual void Resizing( Size& rSize ) override; + virtual bool Close() override; +}; + +} + +ImplDockFloatWin::ImplDockFloatWin( vcl::Window* pParent, WinBits nWinBits, + DockingWindow* pDockingWin ) : + FloatingWindow( pParent, nWinBits ), + mpDockWin( pDockingWin ), + mnLastTicks( tools::Time::GetSystemTicks() ), + maDockIdle( "vcl::ImplDockFloatWin maDockIdle" ), + mbInMove( false ), + mnLastUserEvent( nullptr ) +{ + // copy settings of DockingWindow + if ( pDockingWin ) + { + GetOutDev()->SetSettings( pDockingWin->GetSettings() ); + Enable( pDockingWin->IsEnabled(), false ); + EnableInput( pDockingWin->IsInputEnabled(), false ); + AlwaysEnableInput( pDockingWin->IsAlwaysEnableInput(), false ); + EnableAlwaysOnTop( pDockingWin->IsAlwaysOnTopEnabled() ); + SetActivateMode( pDockingWin->GetActivateMode() ); + } + + SetBackground(); + + maDockIdle.SetInvokeHandler( LINK( this, ImplDockFloatWin, DockTimerHdl ) ); + maDockIdle.SetPriority( TaskPriority::HIGH_IDLE ); +} + +ImplDockFloatWin::~ImplDockFloatWin() +{ + disposeOnce(); +} + +void ImplDockFloatWin::dispose() +{ + if( mnLastUserEvent ) + Application::RemoveUserEvent( mnLastUserEvent ); + + disposeBuilder(); + + mpDockWin.clear(); + FloatingWindow::dispose(); +} + +IMPL_LINK_NOARG(ImplDockFloatWin, DockTimerHdl, Timer *, void) +{ + SAL_WARN_IF( !mpDockWin->IsFloatingMode(), "vcl", "docktimer called but not floating" ); + + maDockIdle.Stop(); + PointerState aState = GetPointerState(); + + if( aState.mnState & KEY_MOD1 ) + { + // i43499 CTRL disables docking now + mpDockWin->GetParent()->ImplGetFrameWindow()->HideTracking(); + mpDockWin->EndDocking( maDockRect, true ); + if( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) ) + maDockIdle.Start(); + } + else if( ! ( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) ) ) + { + mpDockWin->GetParent()->ImplGetFrameWindow()->HideTracking(); + mpDockWin->EndDocking( maDockRect, false ); + } + else + { + mpDockWin->GetParent()->ImplGetFrameWindow()->ShowTracking( maDockRect, ShowTrackFlags::Big | ShowTrackFlags::TrackWindow ); + maDockIdle.Start(); + } +} + +IMPL_LINK_NOARG(ImplDockFloatWin, DockingHdl, void*, void) +{ + PointerState aState = mpDockWin->GetParent()->GetPointerState(); + + mnLastUserEvent = nullptr; + if( mpDockWin->IsDockable() && + (tools::Time::GetSystemTicks() - mnLastTicks > 500) && + ( aState.mnState & ( MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT ) ) && + !(aState.mnState & KEY_MOD1) ) // i43499 CTRL disables docking now + { + maDockPos = mpDockWin->GetParent()->AbsoluteScreenToOutputPixel( OutputToAbsoluteScreenPixel( Point() ) ); + maDockPos = mpDockWin->GetParent()->OutputToScreenPixel( maDockPos ); // sfx expects screen coordinates + + if( ! mpDockWin->IsDocking() ) + mpDockWin->StartDocking(); + maDockRect = tools::Rectangle( maDockPos, mpDockWin->GetSizePixel() ); + + // mouse pos also in screen pixels + Point aMousePos = mpDockWin->GetParent()->OutputToScreenPixel( aState.maPos ); + + bool bFloatMode = mpDockWin->Docking( aMousePos, maDockRect ); + if( ! bFloatMode ) + { + mpDockWin->GetParent()->ImplGetFrameWindow()->ShowTracking( maDockRect, ShowTrackFlags::Object | ShowTrackFlags::TrackWindow ); + DockTimerHdl( nullptr ); + } + else + { + mpDockWin->GetParent()->ImplGetFrameWindow()->HideTracking(); + maDockIdle.Stop(); + mpDockWin->EndDocking( maDockRect, true ); + } + } + mbInMove = false; +} + +void ImplDockFloatWin::Move() +{ + if( mbInMove ) + return; + + mbInMove = true; + FloatingWindow::Move(); + mpDockWin->Move(); + + /* + * note: the window should only dock if + * the user releases all mouse buttons. The real problem here + * is that we don't get mouse events (at least not on X) + * if the mouse is on the decoration. So we have to start an + * awkward timer based process that polls the modifier/buttons + * to see whether they are in the right condition shortly after the + * last Move message. + */ + if( ! mnLastUserEvent ) + mnLastUserEvent = Application::PostUserEvent( LINK( this, ImplDockFloatWin, DockingHdl ), nullptr, true ); +} + +void ImplDockFloatWin::Resize() +{ + FloatingWindow::Resize(); + Size aSize( GetSizePixel() ); + mpDockWin->ImplPosSizeWindow( 0, 0, aSize.Width(), aSize.Height(), PosSizeFlags::PosSize ); +} + +void ImplDockFloatWin::Resizing( Size& rSize ) +{ + FloatingWindow::Resizing( rSize ); + mpDockWin->Resizing( rSize ); +} + +bool ImplDockFloatWin::Close() +{ + return mpDockWin->Close(); +} + +void DockingWindow::ImplStartDocking( const Point& rPos ) +{ + if ( !mbDockable ) + return; + + maMouseOff = rPos; + mbDocking = true; + mbLastFloatMode = IsFloatingMode(); + mbStartFloat = mbLastFloatMode; + + // calculate FloatingBorder + VclPtr<FloatingWindow> pWin; + if ( mpFloatWin ) + pWin = mpFloatWin; + else + pWin = VclPtr<ImplDockFloatWin>::Create( mpImplData->mpParent, mnFloatBits, nullptr ); + pWin->GetBorder( mnDockLeft, mnDockTop, mnDockRight, mnDockBottom ); + if ( !mpFloatWin ) + pWin.disposeAndClear(); + + Point aPos = ImplOutputToFrame( Point() ); + Size aSize = Window::GetOutputSizePixel(); + mnTrackX = aPos.X(); + mnTrackY = aPos.Y(); + mnTrackWidth = aSize.Width(); + mnTrackHeight = aSize.Height(); + + if ( mbLastFloatMode ) + { + maMouseOff.AdjustX(mnDockLeft ); + maMouseOff.AdjustY(mnDockTop ); + mnTrackX -= mnDockLeft; + mnTrackY -= mnDockTop; + mnTrackWidth += mnDockLeft+mnDockRight; + mnTrackHeight += mnDockTop+mnDockBottom; + } + + if ( GetSettings().GetStyleSettings().GetDragFullOptions() & DragFullOptions::Docking && + !( mnFloatBits & ( WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE ) ) ) // no full drag when migrating to system window + mbDragFull = true; + else + { + StartDocking(); + mbDragFull = false; + ImplUpdateAll(); + ImplGetFrameWindow()->ImplUpdateAll(); + } + + StartTracking( StartTrackingFlags::KeyMod ); +} + +void DockingWindow::ImplInitDockingWindowData() +{ + mpWindowImpl->mbDockWin = true; + mpFloatWin = nullptr; + mpOldBorderWin = nullptr; + mpImplData.reset(new ImplData); + mnTrackX = 0; + mnTrackY = 0; + mnTrackWidth = 0; + mnTrackHeight = 0; + mnDockLeft = 0; + mnDockTop = 0; + mnDockRight = 0; + mnDockBottom = 0; + mnFloatBits = 0; + mbDockCanceled = false; + mbDockable = false; + mbDocking = false; + mbDragFull = false; + mbLastFloatMode = false; + mbStartFloat = false; + mbDockBtn = false; + mbHideBtn = false; + mbIsDeferredInit = false; + mbIsCalculatingInitialLayoutSize = false; + mpDialogParent = nullptr; + + //To-Do, reuse maResizeTimer + maLayoutIdle.SetPriority(TaskPriority::RESIZE); + maLayoutIdle.SetInvokeHandler( LINK( this, DockingWindow, ImplHandleLayoutTimerHdl ) ); +} + +void DockingWindow::ImplInit( vcl::Window* pParent, WinBits nStyle ) +{ + if ( !(nStyle & WB_NODIALOGCONTROL) ) + nStyle |= WB_DIALOGCONTROL; + + mpImplData->mpParent = pParent; + mbDockable = (nStyle & WB_DOCKABLE) != 0; + mnFloatBits = WB_BORDER | (nStyle & DOCKWIN_FLOATSTYLES); + nStyle &= ~(DOCKWIN_FLOATSTYLES | WB_BORDER); + + Window::ImplInit( pParent, nStyle, nullptr ); + + ImplInitSettings(); +} + +void DockingWindow::ImplInitSettings() +{ + // Hack: to be able to build DockingWindows w/o background before switching + // TODO: Hack + if ( !IsBackground() ) + return; + + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + + Color aColor; + if ( IsControlBackground() ) + aColor = GetControlBackground(); + else if ( Window::GetStyle() & WB_3DLOOK ) + aColor = rStyleSettings.GetFaceColor(); + else + aColor = rStyleSettings.GetWindowColor(); + SetBackground( aColor ); +} + +DockingWindow::DockingWindow( WindowType nType, const char* pIdleDebugName ) : + Window(nType), + maLayoutIdle( pIdleDebugName ) +{ + ImplInitDockingWindowData(); +} + +DockingWindow::DockingWindow( vcl::Window* pParent, WinBits nStyle, const char* pIdleDebugName ) : + Window( WindowType::DOCKINGWINDOW ), + maLayoutIdle( pIdleDebugName ) +{ + ImplInitDockingWindowData(); + ImplInit( pParent, nStyle ); +} + +//Find the real parent stashed in mpDialogParent. +void DockingWindow::doDeferredInit(WinBits nBits) +{ + vcl::Window *pParent = mpDialogParent; + mpDialogParent = nullptr; + ImplInit(pParent, nBits); + mbIsDeferredInit = false; +} + +void DockingWindow::loadUI(vcl::Window* pParent, const OString& rID, const OUString& rUIXMLDescription, + const css::uno::Reference<css::frame::XFrame> &rFrame) +{ + mbIsDeferredInit = true; + mpDialogParent = pParent; //should be unset in doDeferredInit + m_pUIBuilder.reset( new VclBuilder(this, AllSettings::GetUIRootDir(), rUIXMLDescription, rID, rFrame) ); +} + +DockingWindow::DockingWindow(vcl::Window* pParent, const OString& rID, + const OUString& rUIXMLDescription, const char* pIdleDebugName, + const css::uno::Reference<css::frame::XFrame> &rFrame) + : Window(WindowType::DOCKINGWINDOW), + maLayoutIdle( pIdleDebugName ) +{ + ImplInitDockingWindowData(); + + loadUI(pParent, rID, rUIXMLDescription, rFrame); +} + +DockingWindow::~DockingWindow() +{ + disposeOnce(); +} + +void DockingWindow::dispose() +{ + if ( IsFloatingMode() ) + { + Show( false, ShowFlags::NoFocusChange ); + SetFloatingMode(false); + } + mpImplData.reset(); + mpFloatWin.clear(); + mpOldBorderWin.clear(); + mpDialogParent.clear(); + disposeBuilder(); + Window::dispose(); +} + +void DockingWindow::Tracking( const TrackingEvent& rTEvt ) +{ + if( GetDockingManager()->IsDockable( this ) ) // new docking interface + return Window::Tracking( rTEvt ); + + if ( !mbDocking ) + return; + + if ( rTEvt.IsTrackingEnded() ) + { + mbDocking = false; + if ( mbDragFull ) + { + // reset old state on Cancel + if ( rTEvt.IsTrackingCanceled() ) + { + StartDocking(); + tools::Rectangle aRect( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) ); + EndDocking( aRect, mbStartFloat ); + } + } + else + { + HideTracking(); + if ( rTEvt.IsTrackingCanceled() ) + { + mbDockCanceled = true; + EndDocking( tools::Rectangle( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) ), mbLastFloatMode ); + mbDockCanceled = false; + } + else + EndDocking( tools::Rectangle( Point( mnTrackX, mnTrackY ), Size( mnTrackWidth, mnTrackHeight ) ), mbLastFloatMode ); + } + } + // dock only for non-synthetic MouseEvents + else if ( !rTEvt.GetMouseEvent().IsSynthetic() || rTEvt.GetMouseEvent().IsModifierChanged() ) + { + Point aMousePos = rTEvt.GetMouseEvent().GetPosPixel(); + Point aFrameMousePos = ImplOutputToFrame( aMousePos ); + Size aFrameSize = mpWindowImpl->mpFrameWindow->GetOutputSizePixel(); + if ( aFrameMousePos.X() < 0 ) + aFrameMousePos.setX( 0 ); + if ( aFrameMousePos.Y() < 0 ) + aFrameMousePos.setY( 0 ); + if ( aFrameMousePos.X() > aFrameSize.Width()-1 ) + aFrameMousePos.setX( aFrameSize.Width()-1 ); + if ( aFrameMousePos.Y() > aFrameSize.Height()-1 ) + aFrameMousePos.setY( aFrameSize.Height()-1 ); + aMousePos = ImplFrameToOutput( aFrameMousePos ); + aMousePos.AdjustX( -(maMouseOff.X()) ); + aMousePos.AdjustY( -(maMouseOff.Y()) ); + Point aFramePos = ImplOutputToFrame( aMousePos ); + tools::Rectangle aTrackRect( aFramePos, Size( mnTrackWidth, mnTrackHeight ) ); + tools::Rectangle aCompRect = aTrackRect; + aFramePos.AdjustX(maMouseOff.X() ); + aFramePos.AdjustY(maMouseOff.Y() ); + if ( mbDragFull ) + StartDocking(); + bool bFloatMode = Docking( aFramePos, aTrackRect ); + if ( mbLastFloatMode != bFloatMode ) + { + if ( bFloatMode ) + { + aTrackRect.AdjustLeft( -mnDockLeft ); + aTrackRect.AdjustTop( -mnDockTop ); + aTrackRect.AdjustRight(mnDockRight ); + aTrackRect.AdjustBottom(mnDockBottom ); + } + else + { + if ( aCompRect == aTrackRect ) + { + aTrackRect.AdjustLeft(mnDockLeft ); + aTrackRect.AdjustTop(mnDockTop ); + aTrackRect.AdjustRight( -mnDockRight ); + aTrackRect.AdjustBottom( -mnDockBottom ); + } + } + mbLastFloatMode = bFloatMode; + } + if ( mbDragFull ) + { + Point aOldPos = OutputToScreenPixel( Point() ); + EndDocking( aTrackRect, mbLastFloatMode ); + // repaint if state or position has changed + if ( aOldPos != OutputToScreenPixel( Point() ) ) + { + ImplUpdateAll(); + ImplGetFrameWindow()->ImplUpdateAll(); + } +// EndDocking( aTrackRect, mbLastFloatMode ); + } + else + { + ShowTrackFlags nTrackStyle; + if ( bFloatMode ) + nTrackStyle = ShowTrackFlags::Big; + else + nTrackStyle = ShowTrackFlags::Object; + tools::Rectangle aShowTrackRect = aTrackRect; + aShowTrackRect.SetPos( ImplFrameToOutput( aShowTrackRect.TopLeft() ) ); + ShowTracking( aShowTrackRect, nTrackStyle ); + + // recalculate mouse offset, as the rectangle was changed + maMouseOff.setX( aFramePos.X() - aTrackRect.Left() ); + maMouseOff.setY( aFramePos.Y() - aTrackRect.Top() ); + } + + mnTrackX = aTrackRect.Left(); + mnTrackY = aTrackRect.Top(); + mnTrackWidth = aTrackRect.GetWidth(); + mnTrackHeight = aTrackRect.GetHeight(); + } +} + +bool DockingWindow::EventNotify( NotifyEvent& rNEvt ) +{ + if( GetDockingManager()->IsDockable( this ) ) // new docking interface + return Window::EventNotify( rNEvt ); + + if ( mbDockable ) + { + const bool bDockingSupportCrippled = !StyleSettings::GetDockingFloatsSupported(); + + if ( rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONDOWN ) + { + const MouseEvent* pMEvt = rNEvt.GetMouseEvent(); + if ( pMEvt->IsLeft() ) + { + if (!bDockingSupportCrippled && pMEvt->IsMod1() && (pMEvt->GetClicks() == 2) ) + { + SetFloatingMode( !IsFloatingMode() ); + if ( IsFloatingMode() ) + ToTop( ToTopFlags::GrabFocusOnly ); + return true; + } + else if ( pMEvt->GetClicks() == 1 ) + { + // check if window is floating standalone (IsFloating()) + // or only partially floating and still docked with one border + // ( !mpWindowImpl->mbFrame) + if( ! IsFloatingMode() || ! mpFloatWin->mpWindowImpl->mbFrame ) + { + Point aPos = pMEvt->GetPosPixel(); + vcl::Window* pWindow = rNEvt.GetWindow(); + if ( pWindow != this ) + { + aPos = pWindow->OutputToScreenPixel( aPos ); + aPos = ScreenToOutputPixel( aPos ); + } + ImplStartDocking( aPos ); + } + return true; + } + } + } + else if( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) + { + const vcl::KeyCode& rKey = rNEvt.GetKeyEvent()->GetKeyCode(); + if( rKey.GetCode() == KEY_F10 && rKey.GetModifier() && + rKey.IsShift() && rKey.IsMod1() && !bDockingSupportCrippled ) + { + SetFloatingMode( !IsFloatingMode() ); + if ( IsFloatingMode() ) + ToTop( ToTopFlags::GrabFocusOnly ); + return true; + } + } + } + + return Window::EventNotify( rNEvt ); +} + +void DockingWindow::StartDocking() +{ + mbDocking = true; +} + +bool DockingWindow::Docking( const Point&, tools::Rectangle& ) +{ + return IsFloatingMode(); +} + +void DockingWindow::EndDocking( const tools::Rectangle& rRect, bool bFloatMode ) +{ + bool bOrigDockCanceled = mbDockCanceled; + if (bFloatMode && !StyleSettings::GetDockingFloatsSupported()) + mbDockCanceled = true; + + if ( !IsDockingCanceled() ) + { + if ( bFloatMode != IsFloatingMode() ) + { + SetFloatingMode( bFloatMode ); + if ( IsFloatingMode() ) + ToTop( ToTopFlags::GrabFocusOnly ); + if ( bFloatMode && mpFloatWin ) + mpFloatWin->SetPosSizePixel( rRect.TopLeft(), rRect.GetSize() ); + } + if ( !bFloatMode ) + { + Point aPos = rRect.TopLeft(); + aPos = GetParent()->ScreenToOutputPixel( aPos ); + Window::SetPosSizePixel( aPos, rRect.GetSize() ); + } + } + mbDocking = false; + mbDockCanceled = bOrigDockCanceled; +} + +bool DockingWindow::PrepareToggleFloatingMode() +{ + return true; +} + +bool DockingWindow::Close() +{ + VclPtr<vcl::Window> xWindow = this; + CallEventListeners( VclEventId::WindowClose ); + if ( xWindow->isDisposed() ) + return false; + + if ( mpWindowImpl->mxWindowPeer.is() && IsCreatedWithToolkit() ) + return false; + + Show( false, ShowFlags::NoFocusChange ); + return true; +} + +void DockingWindow::ToggleFloatingMode() +{ +} + +void DockingWindow::Resizing( Size& ) +{ +} + +void DockingWindow::DoInitialLayout() +{ + if (GetSettings().GetStyleSettings().GetAutoMnemonic()) + GenerateAutoMnemonicsOnHierarchy(this); + + if (isLayoutEnabled()) + { + mbIsCalculatingInitialLayoutSize = true; + setDeferredProperties(); + if (IsFloatingMode()) + setOptimalLayoutSize(); + mbIsCalculatingInitialLayoutSize = false; + } +} + +void DockingWindow::StateChanged( StateChangedType nType ) +{ + switch(nType) + { + case StateChangedType::InitShow: + DoInitialLayout(); + break; + + case StateChangedType::ControlBackground: + ImplInitSettings(); + Invalidate(); + break; + + case StateChangedType::Style: + mbDockable = (GetStyle() & WB_DOCKABLE) != 0; + break; + + default: + break; + } + + Window::StateChanged( nType ); +} + +void DockingWindow::DataChanged( const DataChangedEvent& rDCEvt ) +{ + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + ImplInitSettings(); + Invalidate(); + } + else + Window::DataChanged( rDCEvt ); +} + +void DockingWindow::SetFloatingMode( bool bFloatMode ) +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper ) + { + pWrapper->SetFloatingMode( bFloatMode ); + return; + } + if ( IsFloatingMode() == bFloatMode ) + return; + + if ( !PrepareToggleFloatingMode() ) // changes to floating mode can be vetoed + return; + + bool bVisible = IsVisible(); + + if ( bFloatMode ) + { + // set deferred properties early, so border width will end up + // in our mpWindowImpl->mnBorderWidth, not in mpBorderWindow. + // (see its usage in setPosSizeOnContainee and GetOptimalSize.) + setDeferredProperties(); + + Show( false, ShowFlags::NoFocusChange ); + + maDockPos = Window::GetPosPixel(); + + vcl::Window* pRealParent = mpWindowImpl->mpRealParent; + mpOldBorderWin = mpWindowImpl->mpBorderWindow; + + VclPtrInstance<ImplDockFloatWin> pWin( + mpImplData->mpParent, + mnFloatBits & ( WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE ) ? mnFloatBits | WB_SYSTEMWINDOW : mnFloatBits, + this ); + mpFloatWin = pWin; + mpWindowImpl->mpBorderWindow = nullptr; + mpWindowImpl->mnLeftBorder = 0; + mpWindowImpl->mnTopBorder = 0; + mpWindowImpl->mnRightBorder = 0; + mpWindowImpl->mnBottomBorder = 0; + // if the parent gets destroyed, we also have to reset the parent of the BorderWindow + if ( mpOldBorderWin ) + mpOldBorderWin->SetParent( pWin ); + + // #i123765# reset the buffered DropTargets when undocking, else it may not + // be correctly initialized + mpWindowImpl->mxDNDListenerContainer.clear(); + + SetParent( pWin ); + SetPosPixel( Point() ); + mpWindowImpl->mpBorderWindow = pWin; + pWin->mpWindowImpl->mpClientWindow = this; + mpWindowImpl->mpRealParent = pRealParent; + pWin->SetText( Window::GetText() ); + Size aSize(Window::GetSizePixel()); + pWin->SetOutputSizePixel(aSize); + pWin->SetPosPixel( maFloatPos ); + // pass on DockingData to FloatingWindow + pWin->ShowTitleButton( TitleButton::Docking, mbDockBtn ); + pWin->ShowTitleButton( TitleButton::Hide, mbHideBtn ); + pWin->SetMinOutputSizePixel( maMinOutSize ); + + pWin->SetMaxOutputSizePixel( mpImplData->maMaxOutSize ); + + ToggleFloatingMode(); + + if ( bVisible ) + Show(); + } + else + { + Show( false, ShowFlags::NoFocusChange ); + + // store FloatingData in FloatingWindow + maFloatPos = mpFloatWin->GetPosPixel(); + mbDockBtn = mpFloatWin->IsTitleButtonVisible( TitleButton::Docking ); + mbHideBtn = mpFloatWin->IsTitleButtonVisible( TitleButton::Hide ); + maMinOutSize = mpFloatWin->GetMinOutputSizePixel(); + mpImplData->maMaxOutSize = mpFloatWin->GetMaxOutputSizePixel(); + + vcl::Window* pRealParent = mpWindowImpl->mpRealParent; + mpWindowImpl->mpBorderWindow = nullptr; + if ( mpOldBorderWin ) + { + SetParent( mpOldBorderWin ); + static_cast<ImplBorderWindow*>(mpOldBorderWin.get())->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder ); + mpOldBorderWin->Resize(); + } + mpWindowImpl->mpBorderWindow = mpOldBorderWin; + SetParent( pRealParent ); + mpWindowImpl->mpRealParent = pRealParent; + mpFloatWin.disposeAndClear(); + SetPosPixel( maDockPos ); + + ToggleFloatingMode(); + + if ( bVisible ) + Show(); + } +} + +void DockingWindow::SetFloatStyle( WinBits nStyle ) +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper ) + { + pWrapper->SetFloatStyle( nStyle ); + return; + } + + mnFloatBits = nStyle; +} + +WinBits DockingWindow::GetFloatStyle() const +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper ) + { + return pWrapper->GetFloatStyle(); + } + + return mnFloatBits; +} + +void DockingWindow::setPosSizePixel( tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + PosSizeFlags nFlags ) +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if (pWrapper) + { + if (!pWrapper->mpFloatWin) + Window::setPosSizePixel( nX, nY, nWidth, nHeight, nFlags ); + } + else + { + if (!mpFloatWin) + Window::setPosSizePixel( nX, nY, nWidth, nHeight, nFlags ); + else if (comphelper::LibreOfficeKit::isActive()) + { + mpFloatWin->SetOutputSizePixel(Size(nWidth, nHeight)); + mpFloatWin->SetPosPixel(Point(nX, nY)); + } + } + + if (::isLayoutEnabled(this)) + setPosSizeOnContainee(); +} + +Point DockingWindow::GetPosPixel() const +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper ) + { + if ( pWrapper->mpFloatWin ) + return pWrapper->mpFloatWin->GetPosPixel(); + else + return Window::GetPosPixel(); + } + + if ( mpFloatWin ) + return mpFloatWin->GetPosPixel(); + else + return Window::GetPosPixel(); +} + +Size DockingWindow::GetSizePixel() const +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper ) + { + if ( pWrapper->mpFloatWin ) + return pWrapper->mpFloatWin->GetSizePixel(); + else + return Window::GetSizePixel(); + } + + if ( mpFloatWin ) + return mpFloatWin->GetSizePixel(); + else + return Window::GetSizePixel(); +} + +void DockingWindow::SetOutputSizePixel( const Size& rNewSize ) +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper ) + { + if ( pWrapper->mpFloatWin ) + pWrapper->mpFloatWin->SetOutputSizePixel( rNewSize ); + else + Window::SetOutputSizePixel( rNewSize ); + return; + } + + if ( mpFloatWin ) + mpFloatWin->SetOutputSizePixel( rNewSize ); + else + Window::SetOutputSizePixel( rNewSize ); +} + +Size DockingWindow::GetOutputSizePixel() const +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper ) + { + if ( pWrapper->mpFloatWin ) + return pWrapper->mpFloatWin->GetOutputSizePixel(); + else + return Window::GetOutputSizePixel(); + } + + if ( mpFloatWin ) + return mpFloatWin->GetOutputSizePixel(); + else + return Window::GetOutputSizePixel(); +} + +Point DockingWindow::GetFloatingPos() const +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper ) + { + if ( pWrapper->mpFloatWin ) + { + WindowStateData aData; + aData.SetMask( WindowStateMask::Pos ); + pWrapper->mpFloatWin->GetWindowStateData( aData ); + Point aPos( aData.GetX(), aData.GetY() ); + // LOK needs logic coordinates not absolute screen position for autofilter menu + if (!comphelper::LibreOfficeKit::isActive() || get_id() != "check_list_menu") + aPos = pWrapper->mpFloatWin->GetParent()->ImplGetFrameWindow()->AbsoluteScreenToOutputPixel( aPos ); + return aPos; + } + else + return maFloatPos; + } + + if ( mpFloatWin ) + { + WindowStateData aData; + aData.SetMask( WindowStateMask::Pos ); + mpFloatWin->GetWindowStateData( aData ); + Point aPos( aData.GetX(), aData.GetY() ); + aPos = mpFloatWin->GetParent()->ImplGetFrameWindow()->AbsoluteScreenToOutputPixel( aPos ); + return aPos; + } + else + return maFloatPos; +} + +bool DockingWindow::IsFloatingMode() const +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper ) + return pWrapper->IsFloatingMode(); + else + return (mpFloatWin != nullptr); +} + +void DockingWindow::SetMaxOutputSizePixel( const Size& rSize ) +{ + if ( mpFloatWin ) + mpFloatWin->SetMaxOutputSizePixel( rSize ); + mpImplData->maMaxOutSize = rSize; +} + +void DockingWindow::SetText(const OUString& rStr) +{ + setDeferredProperties(); + Window::SetText(rStr); +} + +OUString DockingWindow::GetText() const +{ + const_cast<DockingWindow*>(this)->setDeferredProperties(); + return Window::GetText(); +} + +bool DockingWindow::isLayoutEnabled() const +{ + //pre dtor called, and single child is a container => we're layout enabled + return mpImplData && ::isLayoutEnabled(this); +} + +void DockingWindow::setOptimalLayoutSize() +{ + maLayoutIdle.Stop(); + + //resize DockingWindow to fit requisition on initial show + Size aSize = get_preferred_size(); + + Size aMax(bestmaxFrameSizeForScreenSize(GetDesktopRectPixel().GetSize())); + + aSize.setWidth( std::min(aMax.Width(), aSize.Width()) ); + aSize.setHeight( std::min(aMax.Height(), aSize.Height()) ); + + SetMinOutputSizePixel(aSize); + setPosSizeOnContainee(); +} + +void DockingWindow::setPosSizeOnContainee() +{ + Size aSize = GetOutputSizePixel(); + + // Don't make the border width accessible via get_border_width(), + // otherwise the floating window will handle the border as well. + sal_Int32 nBorderWidth = mpWindowImpl->mnBorderWidth; + + aSize.AdjustWidth( -(2 * nBorderWidth) ); + aSize.AdjustHeight( -(2 * nBorderWidth) ); + + Window* pBox = GetWindow(GetWindowType::FirstChild); + assert(pBox); + VclContainer::setLayoutAllocation(*pBox, Point(nBorderWidth, nBorderWidth), aSize); +} + +Size DockingWindow::GetOptimalSize() const +{ + if (!isLayoutEnabled()) + return Window::GetOptimalSize(); + + Size aSize = VclContainer::getLayoutRequisition(*GetWindow(GetWindowType::FirstChild)); + + // Don't make the border width accessible via get_border_width(), + // otherwise the floating window will handle the border as well. + sal_Int32 nBorderWidth = mpWindowImpl->mnBorderWidth; + + aSize.AdjustHeight(2 * nBorderWidth ); + aSize.AdjustWidth(2 * nBorderWidth ); + + return aSize; +} + +void DockingWindow::queue_resize(StateChangedType eReason) +{ + bool bTriggerLayout = true; + if (maLayoutIdle.IsActive() || mbIsCalculatingInitialLayoutSize) + { + bTriggerLayout = false; + } + if (!isLayoutEnabled()) + { + bTriggerLayout = false; + } + if (bTriggerLayout) + { + InvalidateSizeCache(); + maLayoutIdle.Start(); + } + vcl::Window::queue_resize(eReason); +} + +IMPL_LINK_NOARG(DockingWindow, ImplHandleLayoutTimerHdl, Timer*, void) +{ + if (!isLayoutEnabled()) + { + SAL_WARN_IF(GetWindow(GetWindowType::FirstChild), "vcl.layout", "DockingWindow has become non-layout because extra children have been added directly to it."); + return; + } + setPosSizeOnContainee(); +} + +void DockingWindow::SetMinOutputSizePixel( const Size& rSize ) +{ + if ( mpFloatWin ) + mpFloatWin->SetMinOutputSizePixel( rSize ); + maMinOutSize = rSize; +} + +const Size& DockingWindow::GetMinOutputSizePixel() const +{ + if ( mpFloatWin ) + return mpFloatWin->GetMinOutputSizePixel(); + return maMinOutSize; +} + +void DockingWindow::SetFloatingPos( const Point& rNewPos ) +{ + if ( mpFloatWin ) + mpFloatWin->SetPosPixel( rNewPos ); + else + maFloatPos = rNewPos; +} + +SystemWindow* DockingWindow::GetFloatingWindow() const +{ + return mpFloatWin; +} + +DropdownDockingWindow::DropdownDockingWindow(vcl::Window* pParent, const css::uno::Reference<css::frame::XFrame>& rFrame, bool bTearable) + : DockingWindow(pParent, + !bTearable ? OString("InterimDockParent") : OString("InterimTearableParent"), + !bTearable ? OUString("vcl/ui/interimdockparent.ui") : OUString("vcl/ui/interimtearableparent.ui"), + "vcl::DropdownDockingWindow maLayoutIdle", + rFrame) + , m_xBox(m_pUIBuilder->get("box")) +{ +} + +DropdownDockingWindow::~DropdownDockingWindow() +{ + disposeOnce(); +} + +void DropdownDockingWindow::dispose() +{ + m_xBox.clear(); + DockingWindow::dispose(); +} + +ResizableDockingWindow::ResizableDockingWindow(vcl::Window* pParent, const css::uno::Reference<css::frame::XFrame>& rFrame) + : DockingWindow(pParent, "DockingWindow", "vcl/ui/dockingwindow.ui", "vcl::ResizableDockingWindow maLayoutIdle", rFrame) + , m_xBox(m_pUIBuilder->get("box")) +{ +} + +ResizableDockingWindow::ResizableDockingWindow(vcl::Window* pParent, WinBits nStyle) + : DockingWindow(pParent, nStyle, "vcl::ResizableDockingWindow maLayoutIdle") +{ +} + +// needed to blow away the cached size of the boundary between vcl and hosted child widget +void ResizableDockingWindow::InvalidateChildSizeCache() +{ + // find the bottom vcl::Window of the hierarchy and queue_resize on that + // one will invalidate all the size caches upwards + vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); + while (true) + { + vcl::Window* pSubChild = pChild->GetWindow(GetWindowType::FirstChild); + if (!pSubChild) + break; + pChild = pSubChild; + } + pChild->queue_resize(); +} + +ResizableDockingWindow::~ResizableDockingWindow() +{ + disposeOnce(); +} + +void ResizableDockingWindow::dispose() +{ + m_xBox.clear(); + DockingWindow::dispose(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/errinf.cxx b/vcl/source/window/errinf.cxx new file mode 100644 index 000000000..71191122b --- /dev/null +++ b/vcl/source/window/errinf.cxx @@ -0,0 +1,329 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include <tools/debug.hxx> +#include <vcl/errinf.hxx> + +#include <algorithm> +#include <vector> + +class ErrorHandler; + +namespace { + + ErrorRegistry& GetErrorRegistry() + { + static ErrorRegistry gErrorRegistry; + return gErrorRegistry; + } + +} + +bool ErrorStringFactory::CreateString(const ErrorInfo* pInfo, OUString& rStr) +{ + for(const ErrorHandler *pHdlr : GetErrorRegistry().errorHandlers) + { + if(pHdlr->CreateString(pInfo, rStr)) + return true; + } + return false; +} + +ErrorRegistry::ErrorRegistry() + : pDsp(nullptr) + , bIsWindowDsp(false) + , m_bLock(false) + , nNextError(0) +{ + for(DynamicErrorInfo*& rp : ppDynErrInfo) + rp = nullptr; +} + +void ErrorRegistry::RegisterDisplay(BasicDisplayErrorFunc *aDsp) +{ + ErrorRegistry &rData = GetErrorRegistry(); + rData.bIsWindowDsp = false; + rData.pDsp = reinterpret_cast< DisplayFnPtr >(aDsp); +} + +void ErrorRegistry::RegisterDisplay(WindowDisplayErrorFunc *aDsp) +{ + ErrorRegistry &rData = GetErrorRegistry(); + rData.bIsWindowDsp = true; + rData.pDsp = reinterpret_cast< DisplayFnPtr >(aDsp); +} + +void ErrorRegistry::SetLock(bool bLock) +{ + ErrorRegistry& rData = GetErrorRegistry(); + rData.m_bLock = bLock; +} + +bool ErrorRegistry::GetLock() +{ + ErrorRegistry& rData = GetErrorRegistry(); + return rData.m_bLock; +} + +void ErrorRegistry::Reset() +{ + ErrorRegistry &rData = GetErrorRegistry(); + rData = ErrorRegistry(); +} + +static void aDspFunc(const OUString &rErr, const OUString &rAction) +{ + SAL_WARN("vcl", "Action: " << rAction << " Error: " << rErr); +} + +ErrorHandler::ErrorHandler() +{ + ErrorRegistry &rData = GetErrorRegistry(); + rData.errorHandlers.insert(rData.errorHandlers.begin(), this); + + if(!rData.pDsp) + ErrorRegistry::RegisterDisplay(&aDspFunc); +} + +ErrorHandler::~ErrorHandler() +{ + auto &rErrorHandlers = GetErrorRegistry().errorHandlers; + rErrorHandlers.erase( ::std::remove(rErrorHandlers.begin(), rErrorHandlers.end(), this), + rErrorHandlers.end()); +} + +bool ErrorHandler::GetErrorString(ErrCode nErrCodeId, OUString& rErrStr) +{ + OUString aErr; + + if(!nErrCodeId || nErrCodeId == ERRCODE_ABORT) + return false; + + std::unique_ptr<ErrorInfo> pInfo = ErrorInfo::GetErrorInfo(nErrCodeId); + + if (ErrorStringFactory::CreateString(pInfo.get(),aErr)) + { + rErrStr = aErr; + return true; + } + + return false; +} + +DialogMask ErrorHandler::HandleError(ErrCode nErrCodeId, weld::Window *pParent, DialogMask nFlags) +{ + if (nErrCodeId == ERRCODE_NONE || nErrCodeId == ERRCODE_ABORT) + return DialogMask::NONE; + + ErrorRegistry &rData = GetErrorRegistry(); + std::unique_ptr<ErrorInfo> pInfo = ErrorInfo::GetErrorInfo(nErrCodeId); + OUString aAction; + + if (!rData.contexts.empty()) + { + rData.contexts.front()->GetString(pInfo->GetErrorCode(), aAction); + + for(ErrorContext *pCtx : rData.contexts) + { + if(pCtx->GetParent()) + { + pParent = pCtx->GetParent(); + break; + } + } + } + + bool bWarning = nErrCodeId.IsWarning(); + DialogMask nErrFlags = DialogMask::ButtonDefaultsOk | DialogMask::ButtonsOk; + if (bWarning) + nErrFlags |= DialogMask::MessageWarning; + else + nErrFlags |= DialogMask::MessageError; + + DynamicErrorInfo* pDynPtr = dynamic_cast<DynamicErrorInfo*>(pInfo.get()); + if(pDynPtr) + { + DialogMask nDynFlags = pDynPtr->GetDialogMask(); + if( nDynFlags != DialogMask::NONE ) + nErrFlags = nDynFlags; + } + + OUString aErr; + if (ErrorStringFactory::CreateString(pInfo.get(), aErr)) + { + if (!rData.pDsp || rData.m_bLock) + { + SAL_WARN( "vcl", "Action: " << aAction << "Error: " << aErr); + } + else + { + if(!rData.bIsWindowDsp) + { + (*reinterpret_cast<BasicDisplayErrorFunc*>(rData.pDsp))(aErr,aAction); + return DialogMask::NONE; + } + else + { + if (nFlags != DialogMask::MAX) + nErrFlags = nFlags; + + return (*reinterpret_cast<WindowDisplayErrorFunc*>(rData.pDsp))( + pParent, nErrFlags, aErr, aAction); + } + } + } + + SAL_WARN( "vcl", "Error not handled " << pInfo->GetErrorCode()); + // Error 1 (ERRCODE_ABORT) is classified as a General Error in sfx + if (pInfo->GetErrorCode() != ERRCODE_ABORT) + HandleError(ERRCODE_ABORT); + else + OSL_FAIL("ERRCODE_ABORT not handled"); + + return DialogMask::NONE; +} + +struct ImplErrorContext +{ + weld::Window *pWin; +}; + +ErrorContext::ErrorContext(weld::Window *pWinP) + : pImpl( new ImplErrorContext ) +{ + pImpl->pWin = pWinP; + GetErrorRegistry().contexts.insert(GetErrorRegistry().contexts.begin(), this); +} + +ErrorContext::~ErrorContext() +{ + auto &rContexts = GetErrorRegistry().contexts; + rContexts.erase( ::std::remove(rContexts.begin(), rContexts.end(), this), rContexts.end()); +} + +ErrorContext *ErrorContext::GetContext() +{ + return GetErrorRegistry().contexts.empty() ? nullptr : GetErrorRegistry().contexts.front(); +} + +weld::Window* ErrorContext::GetParent() +{ + return pImpl ? pImpl->pWin : nullptr; +} + +class ImplDynamicErrorInfo +{ + friend class DynamicErrorInfo; + friend class ErrorInfo; + +private: + explicit ImplDynamicErrorInfo(DialogMask nInMask) + : nMask(nInMask) + { + } + void RegisterError(DynamicErrorInfo *); + static void UnRegisterError(DynamicErrorInfo const *); + static std::unique_ptr<ErrorInfo> GetDynamicErrorInfo(ErrCode nId); + + ErrCode nErrId; + DialogMask nMask; + +}; + +void ImplDynamicErrorInfo::RegisterError(DynamicErrorInfo *pDynErrInfo) +{ + // Register dynamic identifier + ErrorRegistry& rData = GetErrorRegistry(); + nErrId = ErrCode(((sal_uInt32(rData.nNextError) + 1) << ERRCODE_DYNAMIC_SHIFT) + + sal_uInt32(pDynErrInfo->GetErrorCode())); + + if(rData.ppDynErrInfo[rData.nNextError]) + delete rData.ppDynErrInfo[rData.nNextError]; + + rData.ppDynErrInfo[rData.nNextError] = pDynErrInfo; + + if(++rData.nNextError>=ERRCODE_DYNAMIC_COUNT) + rData.nNextError=0; +} + +void ImplDynamicErrorInfo::UnRegisterError(DynamicErrorInfo const *pDynErrInfo) +{ + DynamicErrorInfo **ppDynErrInfo = GetErrorRegistry().ppDynErrInfo; + sal_uInt32 nIdx = ErrCode(*pDynErrInfo).GetDynamic() - 1; + DBG_ASSERT(ppDynErrInfo[nIdx] == pDynErrInfo, "ErrHdl: Error not found"); + + if(ppDynErrInfo[nIdx]==pDynErrInfo) + ppDynErrInfo[nIdx]=nullptr; +} + +std::unique_ptr<ErrorInfo> ImplDynamicErrorInfo::GetDynamicErrorInfo(ErrCode nId) +{ + sal_uInt32 nIdx = nId.GetDynamic() - 1; + DynamicErrorInfo* pDynErrInfo = GetErrorRegistry().ppDynErrInfo[nIdx]; + + if(pDynErrInfo && ErrCode(*pDynErrInfo)==nId) + return std::unique_ptr<ErrorInfo>(pDynErrInfo); + else + return std::make_unique<ErrorInfo>(nId.StripDynamic()); +} + +std::unique_ptr<ErrorInfo> ErrorInfo::GetErrorInfo(ErrCode nId) +{ + if(nId.IsDynamic()) + return ImplDynamicErrorInfo::GetDynamicErrorInfo(nId); + else + return std::make_unique<ErrorInfo>(nId); +} + +ErrorInfo::~ErrorInfo() +{ +} + +DynamicErrorInfo::DynamicErrorInfo(ErrCode nArgUserId, DialogMask nMask) +: ErrorInfo(nArgUserId), + pImpl(new ImplDynamicErrorInfo(nMask)) +{ + pImpl->RegisterError(this); +} + +DynamicErrorInfo::~DynamicErrorInfo() +{ + ImplDynamicErrorInfo::UnRegisterError(this); +} + +DynamicErrorInfo::operator ErrCode() const +{ + return pImpl->nErrId; +} + +DialogMask DynamicErrorInfo::GetDialogMask() const +{ + return pImpl->nMask; +} + +StringErrorInfo::StringErrorInfo( + ErrCode nArgUserId, const OUString& aStringP, DialogMask nMask) +: DynamicErrorInfo(nArgUserId, nMask), aString(aStringP) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/event.cxx b/vcl/source/window/event.cxx new file mode 100644 index 000000000..e11d032b9 --- /dev/null +++ b/vcl/source/window/event.cxx @@ -0,0 +1,673 @@ +/* -*- 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/event.hxx> +#include <vcl/window.hxx> +#include <vcl/dockwin.hxx> +#include <vcl/layout.hxx> +#include <sal/log.hxx> + +#include <window.h> +#include <svdata.hxx> +#include <salframe.hxx> +#include <config_features.h> +#include <comphelper/scopeguard.hxx> + +#include "impldockingwrapper.hxx" + +namespace vcl { + +void Window::DataChanged( const DataChangedEvent& ) +{ +} + +void Window::NotifyAllChildren( DataChangedEvent& rDCEvt ) +{ + CompatDataChanged( rDCEvt ); + + vcl::Window* pChild = mpWindowImpl->mpFirstChild; + while ( pChild ) + { + pChild->NotifyAllChildren( rDCEvt ); + pChild = pChild->mpWindowImpl->mpNext; + } +} + +bool Window::PreNotify( NotifyEvent& rNEvt ) +{ + bool bDone = false; + if ( mpWindowImpl->mpParent && !ImplIsOverlapWindow() ) + bDone = mpWindowImpl->mpParent->CompatPreNotify( rNEvt ); + + if ( !bDone ) + { + if( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS ) + { + bool bCompoundFocusChanged = false; + if ( mpWindowImpl->mbCompoundControl && !mpWindowImpl->mbCompoundControlHasFocus && HasChildPathFocus() ) + { + mpWindowImpl->mbCompoundControlHasFocus = true; + bCompoundFocusChanged = true; + } + + if ( bCompoundFocusChanged || ( rNEvt.GetWindow() == this ) ) + CallEventListeners( VclEventId::WindowGetFocus ); + } + else if( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS ) + { + bool bCompoundFocusChanged = false; + if ( mpWindowImpl->mbCompoundControl && mpWindowImpl->mbCompoundControlHasFocus && !HasChildPathFocus() ) + { + mpWindowImpl->mbCompoundControlHasFocus = false ; + bCompoundFocusChanged = true; + } + + if ( bCompoundFocusChanged || ( rNEvt.GetWindow() == this ) ) + CallEventListeners( VclEventId::WindowLoseFocus ); + } + + // #82968# mouse and key events will be notified after processing ( in ImplNotifyKeyMouseCommandEventListeners() )! + // see also ImplHandleMouseEvent(), ImplHandleKey() + + } + + return bDone; +} + +namespace +{ + bool parentNotDialogControl(Window* pWindow) + { + vcl::Window* pParent = getNonLayoutParent(pWindow); + if (!pParent) + return true; + return ((pParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) != WB_DIALOGCONTROL); + } +} + +bool Window::EventNotify( NotifyEvent& rNEvt ) +{ + bool bRet = false; + + if (isDisposed()) + return false; + + // check for docking window + // but do nothing if window is docked and locked + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if ((GetStyle() & WB_DOCKABLE) && + pWrapper && ( pWrapper->IsFloatingMode() || !pWrapper->IsLocked() )) + { + const bool bDockingSupportCrippled = !StyleSettings::GetDockingFloatsSupported(); + + if ( rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONDOWN ) + { + const MouseEvent* pMEvt = rNEvt.GetMouseEvent(); + bool bHit = pWrapper->GetDragArea().Contains( pMEvt->GetPosPixel() ); + if ( pMEvt->IsLeft() ) + { + if (!bDockingSupportCrippled && pMEvt->IsMod1() && (pMEvt->GetClicks() == 2)) + { + // ctrl double click toggles floating mode + pWrapper->SetFloatingMode( !pWrapper->IsFloatingMode() ); + return true; + } + else if ( pMEvt->GetClicks() == 1 && bHit) + { + // allow start docking during mouse move + pWrapper->ImplEnableStartDocking(); + return true; + } + } + } + else if ( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE ) + { + const MouseEvent* pMEvt = rNEvt.GetMouseEvent(); + bool bHit = pWrapper->GetDragArea().Contains( pMEvt->GetPosPixel() ); + if ( pMEvt->IsLeft() ) + { + // check if a single click initiated this sequence ( ImplStartDockingEnabled() ) + // check if window is docked and + if( pWrapper->ImplStartDockingEnabled() && !pWrapper->IsFloatingMode() && + !pWrapper->IsDocking() && bHit ) + { + Point aPos = pMEvt->GetPosPixel(); + vcl::Window* pWindow = rNEvt.GetWindow(); + if ( pWindow != this ) + { + aPos = pWindow->OutputToScreenPixel( aPos ); + aPos = ScreenToOutputPixel( aPos ); + } + pWrapper->ImplStartDocking( aPos ); + } + return true; + } + } + else if( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) + { + const vcl::KeyCode& rKey = rNEvt.GetKeyEvent()->GetKeyCode(); + if (rKey.GetCode() == KEY_F10 && rKey.GetModifier() && + rKey.IsShift() && rKey.IsMod1() && !bDockingSupportCrippled) + { + pWrapper->SetFloatingMode( !pWrapper->IsFloatingMode() ); + /* At this point the floating toolbar frame does not have the + * input focus since these frames don't get the focus per default + * To enable keyboard handling of this toolbar set the input focus + * to the frame. This needs to be done with ToTop since GrabFocus + * would not notice any change since "this" already has the focus. + */ + if( pWrapper->IsFloatingMode() ) + ToTop( ToTopFlags::GrabFocusOnly ); + return true; + } + } + } + + // manage the dialogs + if ( (GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL ) + { + // if the parent also has dialog control activated, the parent takes over control + if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) || (rNEvt.GetType() == MouseNotifyEvent::KEYUP) ) + { + // ScGridWindow has WB_DIALOGCONTROL set, so pressing tab in ScCheckListMenuControl won't + // get processed here by the toplevel DockingWindow of ScCheckListMenuControl by + // just checking if parentNotDialogControl is true + bool bTopLevelFloatingWindow = (pWrapper && pWrapper->IsFloatingMode()); + if (ImplIsOverlapWindow() || parentNotDialogControl(this) || bTopLevelFloatingWindow) + { + bRet = ImplDlgCtrl( *rNEvt.GetKeyEvent(), rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ); + } + } + else if ( (rNEvt.GetType() == MouseNotifyEvent::GETFOCUS) || (rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS) ) + { + ImplDlgCtrlFocusChanged( rNEvt.GetWindow(), rNEvt.GetType() == MouseNotifyEvent::GETFOCUS ); + if ( (rNEvt.GetWindow() == this) && (rNEvt.GetType() == MouseNotifyEvent::GETFOCUS) && + !(GetStyle() & WB_TABSTOP) && !(mpWindowImpl->mnDlgCtrlFlags & DialogControlFlags::WantFocus) ) + { + vcl::Window* pFirstChild = ImplGetDlgWindow( 0, GetDlgWindowType::First ); + if ( pFirstChild ) + pFirstChild->ImplControlFocus(); + } + } + } + + if ( !bRet ) + { + if ( mpWindowImpl->mpParent && !ImplIsOverlapWindow() ) + bRet = mpWindowImpl->mpParent->CompatNotify( rNEvt ); + } + + return bRet; +} + +void Window::CallEventListeners( VclEventId nEvent, void* pData ) +{ + VclWindowEvent aEvent( this, nEvent, pData ); + + VclPtr<vcl::Window> xWindow = this; + + Application::ImplCallEventListeners( aEvent ); + + // if we have ObjectDying, then the bIsDisposed flag has already been set, + // but we still need to let listeners know. + const bool bIgnoreDisposed = nEvent == VclEventId::ObjectDying; + + if ( !bIgnoreDisposed && xWindow->isDisposed() ) + return; + + // If maEventListeners is empty, the XVCLWindow has not yet been initialized. + // Calling GetComponentInterface will do that. + if (mpWindowImpl->maEventListeners.empty() && pData) + xWindow->GetComponentInterface(); + + if (!mpWindowImpl->maEventListeners.empty()) + { + // Copy the list, because this can be destroyed when calling a Link... + std::vector<Link<VclWindowEvent&,void>> aCopy( mpWindowImpl->maEventListeners ); + // we use an iterating counter/flag and a set of deleted Link's to avoid O(n^2) behaviour + mpWindowImpl->mnEventListenersIteratingCount++; + auto& rWindowImpl = *mpWindowImpl; + comphelper::ScopeGuard aGuard( + [&rWindowImpl, &xWindow, &bIgnoreDisposed]() + { + if (bIgnoreDisposed || !xWindow->isDisposed()) + { + rWindowImpl.mnEventListenersIteratingCount--; + if (rWindowImpl.mnEventListenersIteratingCount == 0) + rWindowImpl.maEventListenersDeleted.clear(); + } + } + ); + for ( const Link<VclWindowEvent&,void>& rLink : aCopy ) + { + if (!bIgnoreDisposed && xWindow->isDisposed()) break; + // check this hasn't been removed in some re-enterancy scenario fdo#47368 + if( rWindowImpl.maEventListenersDeleted.find(rLink) == rWindowImpl.maEventListenersDeleted.end() ) + rLink.Call( aEvent ); + } + } + + while ( xWindow ) + { + + if ( !bIgnoreDisposed && xWindow->isDisposed() ) + return; + + auto& rWindowImpl = *xWindow->mpWindowImpl; + if (!rWindowImpl.maChildEventListeners.empty()) + { + // Copy the list, because this can be destroyed when calling a Link... + std::vector<Link<VclWindowEvent&,void>> aCopy( rWindowImpl.maChildEventListeners ); + // we use an iterating counter/flag and a set of deleted Link's to avoid O(n^2) behaviour + rWindowImpl.mnChildEventListenersIteratingCount++; + comphelper::ScopeGuard aGuard( + [&rWindowImpl, &xWindow, &bIgnoreDisposed]() + { + if (bIgnoreDisposed || !xWindow->isDisposed()) + { + rWindowImpl.mnChildEventListenersIteratingCount--; + if (rWindowImpl.mnChildEventListenersIteratingCount == 0) + rWindowImpl.maChildEventListenersDeleted.clear(); + } + } + ); + for ( const Link<VclWindowEvent&,void>& rLink : aCopy ) + { + if (!bIgnoreDisposed && xWindow->isDisposed()) + return; + // Check this hasn't been removed in some re-enterancy scenario fdo#47368. + if( rWindowImpl.maChildEventListenersDeleted.find(rLink) == rWindowImpl.maChildEventListenersDeleted.end() ) + rLink.Call( aEvent ); + } + } + + if ( !bIgnoreDisposed && xWindow->isDisposed() ) + return; + + xWindow = xWindow->GetParent(); + } +} + +void Window::AddEventListener( const Link<VclWindowEvent&,void>& rEventListener ) +{ + mpWindowImpl->maEventListeners.push_back( rEventListener ); +} + +void Window::RemoveEventListener( const Link<VclWindowEvent&,void>& rEventListener ) +{ + if (mpWindowImpl) + { + auto& rListeners = mpWindowImpl->maEventListeners; + rListeners.erase( std::remove(rListeners.begin(), rListeners.end(), rEventListener ), rListeners.end() ); + if (mpWindowImpl->mnEventListenersIteratingCount) + mpWindowImpl->maEventListenersDeleted.insert(rEventListener); + } +} + +void Window::AddChildEventListener( const Link<VclWindowEvent&,void>& rEventListener ) +{ + mpWindowImpl->maChildEventListeners.push_back( rEventListener ); +} + +void Window::RemoveChildEventListener( const Link<VclWindowEvent&,void>& rEventListener ) +{ + if (mpWindowImpl) + { + auto& rListeners = mpWindowImpl->maChildEventListeners; + rListeners.erase( std::remove(rListeners.begin(), rListeners.end(), rEventListener ), rListeners.end() ); + if (mpWindowImpl->mnChildEventListenersIteratingCount) + mpWindowImpl->maChildEventListenersDeleted.insert(rEventListener); + } +} + +ImplSVEvent * Window::PostUserEvent( const Link<void*,void>& rLink, void* pCaller, bool bReferenceLink ) +{ + std::unique_ptr<ImplSVEvent> pSVEvent(new ImplSVEvent); + pSVEvent->mpData = pCaller; + pSVEvent->maLink = rLink; + pSVEvent->mpWindow = this; + pSVEvent->mbCall = true; + if (bReferenceLink) + { + pSVEvent->mpInstanceRef = static_cast<vcl::Window *>(rLink.GetInstance()); + } + + auto pTmpEvent = pSVEvent.get(); + if (!mpWindowImpl->mpFrame->PostEvent( std::move(pSVEvent) )) + return nullptr; + return pTmpEvent; +} + +void Window::RemoveUserEvent( ImplSVEvent * nUserEvent ) +{ + SAL_WARN_IF( nUserEvent->mpWindow.get() != this, "vcl", + "Window::RemoveUserEvent(): Event doesn't send to this window or is already removed" ); + SAL_WARN_IF( !nUserEvent->mbCall, "vcl", + "Window::RemoveUserEvent(): Event is already removed" ); + + if ( nUserEvent->mpWindow ) + { + nUserEvent->mpWindow = nullptr; + } + + nUserEvent->mbCall = false; +} + + +static MouseEvent ImplTranslateMouseEvent( const MouseEvent& rE, vcl::Window const * pSource, vcl::Window const * pDest ) +{ + // the mouse event occurred in a different window, we need to translate the coordinates of + // the mouse cursor within that (source) window to the coordinates the mouse cursor would + // be in the destination window + Point aPos = pSource->OutputToScreenPixel( rE.GetPosPixel() ); + return MouseEvent( pDest->ScreenToOutputPixel( aPos ), rE.GetClicks(), rE.GetMode(), rE.GetButtons(), rE.GetModifier() ); +} + +void Window::ImplNotifyKeyMouseCommandEventListeners( NotifyEvent& rNEvt ) +{ + if( rNEvt.GetType() == MouseNotifyEvent::COMMAND ) + { + const CommandEvent* pCEvt = rNEvt.GetCommandEvent(); + if ( pCEvt->GetCommand() != CommandEventId::ContextMenu ) + // non context menu events are not to be notified up the chain + // so we return immediately + return; + + if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) ) + { + // not interested: The event listeners are already called in ::Command, + // and calling them here a second time doesn't make sense + if ( rNEvt.GetWindow() != this ) + { + CommandEvent aCommandEvent; + + if ( !pCEvt->IsMouseEvent() ) + { + aCommandEvent = *pCEvt; + } + else + { + // the mouse event occurred in a different window, we need to translate the coordinates of + // the mouse cursor within that window to the coordinates the mouse cursor would be in the + // current window + vcl::Window* pSource = rNEvt.GetWindow(); + Point aPos = pSource->OutputToScreenPixel( pCEvt->GetMousePosPixel() ); + aCommandEvent = CommandEvent( ScreenToOutputPixel( aPos ), pCEvt->GetCommand(), pCEvt->IsMouseEvent(), pCEvt->GetEventData() ); + } + + CallEventListeners( VclEventId::WindowCommand, &aCommandEvent ); + } + } + } + + // #82968# notify event listeners for mouse and key events separately and + // not in PreNotify ( as for focus listeners ) + // this allows for processing those events internally first and pass it to + // the toolkit later + + VclPtr<vcl::Window> xWindow = this; + + if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE ) + { + if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) ) + { + if ( rNEvt.GetWindow() == this ) + CallEventListeners( VclEventId::WindowMouseMove, const_cast<MouseEvent *>(rNEvt.GetMouseEvent()) ); + else + { + MouseEvent aMouseEvent = ImplTranslateMouseEvent( *rNEvt.GetMouseEvent(), rNEvt.GetWindow(), this ); + CallEventListeners( VclEventId::WindowMouseMove, &aMouseEvent ); + } + } + } + else if( rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONUP ) + { + if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) ) + { + if ( rNEvt.GetWindow() == this ) + CallEventListeners( VclEventId::WindowMouseButtonUp, const_cast<MouseEvent *>(rNEvt.GetMouseEvent()) ); + else + { + MouseEvent aMouseEvent = ImplTranslateMouseEvent( *rNEvt.GetMouseEvent(), rNEvt.GetWindow(), this ); + CallEventListeners( VclEventId::WindowMouseButtonUp, &aMouseEvent ); + } + } + } + else if( rNEvt.GetType() == MouseNotifyEvent::MOUSEBUTTONDOWN ) + { + if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) ) + { + if ( rNEvt.GetWindow() == this ) + CallEventListeners( VclEventId::WindowMouseButtonDown, const_cast<MouseEvent *>(rNEvt.GetMouseEvent()) ); + else + { + MouseEvent aMouseEvent = ImplTranslateMouseEvent( *rNEvt.GetMouseEvent(), rNEvt.GetWindow(), this ); + CallEventListeners( VclEventId::WindowMouseButtonDown, &aMouseEvent ); + } + } + } + else if( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) + { + if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) ) + CallEventListeners( VclEventId::WindowKeyInput, const_cast<KeyEvent *>(rNEvt.GetKeyEvent()) ); + } + else if( rNEvt.GetType() == MouseNotifyEvent::KEYUP ) + { + if ( mpWindowImpl->mbCompoundControl || ( rNEvt.GetWindow() == this ) ) + CallEventListeners( VclEventId::WindowKeyUp, const_cast<KeyEvent *>(rNEvt.GetKeyEvent()) ); + } + + if ( xWindow->isDisposed() ) + return; + + // #106721# check if we're part of a compound control and notify + vcl::Window *pParent = ImplGetParent(); + while( pParent ) + { + if( pParent->IsCompoundControl() ) + { + pParent->ImplNotifyKeyMouseCommandEventListeners( rNEvt ); + break; + } + pParent = pParent->ImplGetParent(); + } +} + +void Window::ImplCallInitShow() +{ + mpWindowImpl->mbReallyShown = true; + mpWindowImpl->mbInInitShow = true; + CompatStateChanged( StateChangedType::InitShow ); + mpWindowImpl->mbInInitShow = false; + + vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap; + while ( pWindow ) + { + if ( pWindow->mpWindowImpl->mbVisible ) + pWindow->ImplCallInitShow(); + pWindow = pWindow->mpWindowImpl->mpNext; + } + + pWindow = mpWindowImpl->mpFirstChild; + while ( pWindow ) + { + if ( pWindow->mpWindowImpl->mbVisible ) + pWindow->ImplCallInitShow(); + pWindow = pWindow->mpWindowImpl->mpNext; + } +} + + +void Window::ImplCallResize() +{ + mpWindowImpl->mbCallResize = false; + + // Normally we avoid blanking on re-size unless people might notice: + if( GetBackground().IsGradient() ) + Invalidate(); + + Resize(); + + // #88419# Most classes don't call the base class in Resize() and Move(), + // => Call ImpleResize/Move instead of Resize/Move directly... + CallEventListeners( VclEventId::WindowResize ); +} + +void Window::ImplCallMove() +{ + mpWindowImpl->mbCallMove = false; + + if( mpWindowImpl->mbFrame ) + { + // update frame position + SalFrame *pParentFrame = nullptr; + vcl::Window *pParent = ImplGetParent(); + while( pParent ) + { + if( pParent->mpWindowImpl && + pParent->mpWindowImpl->mpFrame != mpWindowImpl->mpFrame ) + { + pParentFrame = pParent->mpWindowImpl->mpFrame; + break; + } + pParent = pParent->GetParent(); + } + + SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry(); + mpWindowImpl->maPos = Point( g.nX, g.nY ); + if( pParentFrame ) + { + g = pParentFrame->GetGeometry(); + mpWindowImpl->maPos -= Point( g.nX, g.nY ); + } + // the client window and all its subclients have the same position as the borderframe + // this is important for floating toolbars where the borderwindow is a floating window + // which has another borderwindow (ie the system floating window) + vcl::Window *pClientWin = mpWindowImpl->mpClientWindow; + while( pClientWin ) + { + pClientWin->mpWindowImpl->maPos = mpWindowImpl->maPos; + pClientWin = pClientWin->mpWindowImpl->mpClientWindow; + } + } + + Move(); + + CallEventListeners( VclEventId::WindowMove ); +} + +void Window::ImplCallFocusChangeActivate( vcl::Window* pNewOverlapWindow, + vcl::Window* pOldOverlapWindow ) +{ + ImplSVData* pSVData = ImplGetSVData(); + vcl::Window* pNewRealWindow; + vcl::Window* pOldRealWindow; + bool bCallActivate = true; + bool bCallDeactivate = true; + + if (!pOldOverlapWindow) + { + return; + } + + pOldRealWindow = pOldOverlapWindow->ImplGetWindow(); + if (!pNewOverlapWindow) + { + return; + } + + pNewRealWindow = pNewOverlapWindow->ImplGetWindow(); + if ( (pOldRealWindow->GetType() != WindowType::FLOATINGWINDOW) || + pOldRealWindow->GetActivateMode() != ActivateModeFlags::NONE ) + { + if ( (pNewRealWindow->GetType() == WindowType::FLOATINGWINDOW) && + pNewRealWindow->GetActivateMode() == ActivateModeFlags::NONE) + { + pSVData->mpWinData->mpLastDeacWin = pOldOverlapWindow; + bCallDeactivate = false; + } + } + else if ( (pNewRealWindow->GetType() != WindowType::FLOATINGWINDOW) || + pNewRealWindow->GetActivateMode() != ActivateModeFlags::NONE ) + { + if (pSVData->mpWinData->mpLastDeacWin) + { + if (pSVData->mpWinData->mpLastDeacWin.get() == pNewOverlapWindow) + bCallActivate = false; + else + { + vcl::Window* pLastRealWindow = pSVData->mpWinData->mpLastDeacWin->ImplGetWindow(); + pSVData->mpWinData->mpLastDeacWin->mpWindowImpl->mbActive = false; + pSVData->mpWinData->mpLastDeacWin->Deactivate(); + if (pLastRealWindow != pSVData->mpWinData->mpLastDeacWin.get()) + { + pLastRealWindow->mpWindowImpl->mbActive = true; + pLastRealWindow->Activate(); + } + } + pSVData->mpWinData->mpLastDeacWin = nullptr; + } + } + + if ( bCallDeactivate ) + { + if( pOldOverlapWindow->mpWindowImpl->mbActive ) + { + pOldOverlapWindow->mpWindowImpl->mbActive = false; + pOldOverlapWindow->Deactivate(); + } + if ( pOldRealWindow != pOldOverlapWindow ) + { + if( pOldRealWindow->mpWindowImpl->mbActive ) + { + pOldRealWindow->mpWindowImpl->mbActive = false; + pOldRealWindow->Deactivate(); + } + } + } + if ( !bCallActivate || pNewOverlapWindow->mpWindowImpl->mbActive ) + return; + + pNewOverlapWindow->mpWindowImpl->mbActive = true; + pNewOverlapWindow->Activate(); + + if ( pNewRealWindow != pNewOverlapWindow ) + { + if( ! pNewRealWindow->mpWindowImpl->mbActive ) + { + pNewRealWindow->mpWindowImpl->mbActive = true; + pNewRealWindow->Activate(); + } + } +} + +} /* namespace vcl */ + + +NotifyEvent::NotifyEvent( MouseNotifyEvent nEventType, vcl::Window* pWindow, + const void* pEvent ) +{ + mpWindow = pWindow; + mpData = const_cast<void*>(pEvent); + mnEventType = nEventType; +} + +NotifyEvent::~NotifyEvent() = default; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/floatwin.cxx b/vcl/source/window/floatwin.cxx new file mode 100644 index 000000000..094a3ef18 --- /dev/null +++ b/vcl/source/window/floatwin.cxx @@ -0,0 +1,973 @@ +/* -*- 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 <svdata.hxx> +#include <brdwin.hxx> +#include <window.h> +#include <salframe.hxx> +#include <helpwin.hxx> + +#include <comphelper/lok.hxx> +#include <sal/log.hxx> +#include <vcl/layout.hxx> +#include <vcl/svapp.hxx> +#include <vcl/wrkwin.hxx> +#include <vcl/event.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/toolkit/floatwin.hxx> +#include <vcl/settings.hxx> +#include <vcl/IDialogRenderable.hxx> + +class FloatingWindow::ImplData +{ +public: + ImplData(); + + VclPtr<ToolBox> mpBox; + tools::Rectangle maItemEdgeClipRect; // used to clip the common edge between a toolbar item and the border of this window + Point maPos; // position of the floating window wrt. parent + Point maLOKTwipsPos; ///< absolute position of the floating window in the document - in twips (for toplevel floating windows). +}; + +FloatingWindow::ImplData::ImplData() +{ + mpBox = nullptr; +} + +tools::Rectangle& FloatingWindow::ImplGetItemEdgeClipRect() +{ + return mpImplData->maItemEdgeClipRect; +} + +void FloatingWindow::ImplInitFloating( vcl::Window* pParent, WinBits nStyle ) +{ + mpImplData.reset(new ImplData); + + mpWindowImpl->mbFloatWin = true; + mbInCleanUp = false; + mbGrabFocus = false; + + SAL_WARN_IF(!pParent, "vcl", "FloatWindow::FloatingWindow(): - pParent == NULL!"); + + if (!pParent) + pParent = ImplGetSVData()->maFrameData.mpAppWin; + + SAL_WARN_IF(!pParent, "vcl", "FloatWindow::FloatingWindow(): - pParent == NULL and no AppWindow exists"); + + // no Border, then we don't need a border window + if (!nStyle) + { + mpWindowImpl->mbOverlapWin = true; + nStyle |= WB_DIALOGCONTROL; + ImplInit(pParent, nStyle, nullptr); + } + else + { + if (!(nStyle & WB_NODIALOGCONTROL)) + nStyle |= WB_DIALOGCONTROL; + + if (nStyle & (WB_MOVEABLE | WB_SIZEABLE | WB_CLOSEABLE | WB_STANDALONE) + && !(nStyle & WB_OWNERDRAWDECORATION)) + { + WinBits nFloatWinStyle = nStyle; + // #99154# floaters are not closeable by default anymore, eg fullscreen floater + // nFloatWinStyle |= WB_CLOSEABLE; + mpWindowImpl->mbFrame = true; + mpWindowImpl->mbOverlapWin = true; + ImplInit(pParent, nFloatWinStyle & ~WB_BORDER, nullptr); + } + else + { + VclPtr<ImplBorderWindow> pBorderWin; + BorderWindowStyle nBorderStyle = BorderWindowStyle::Float; + + if (nStyle & WB_OWNERDRAWDECORATION) + nBorderStyle |= BorderWindowStyle::Frame; + else + nBorderStyle |= BorderWindowStyle::Overlap; + + if ((nStyle & WB_SYSTEMWINDOW) && !(nStyle & (WB_MOVEABLE | WB_SIZEABLE))) + { + nBorderStyle |= BorderWindowStyle::Frame; + nStyle |= WB_CLOSEABLE; // make undecorated floaters closeable + } + pBorderWin = VclPtr<ImplBorderWindow>::Create(pParent, nStyle, nBorderStyle); + ImplInit(pBorderWin, nStyle & ~WB_BORDER, nullptr); + pBorderWin->mpWindowImpl->mpClientWindow = this; + pBorderWin->GetBorder(mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, + mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder); + pBorderWin->SetDisplayActive(true); + mpWindowImpl->mpBorderWindow = pBorderWin; + mpWindowImpl->mpRealParent = pParent; + } + } + SetActivateMode( ActivateModeFlags::NONE ); + + mpNextFloat = nullptr; + mpFirstPopupModeWin = nullptr; + mnPostId = nullptr; + mnTitle = (nStyle & (WB_MOVEABLE | WB_POPUP)) ? FloatWinTitleType::Normal : FloatWinTitleType::NONE; + mnOldTitle = mnTitle; + mnPopupModeFlags = FloatWinPopupFlags::NONE; + mbInPopupMode = false; + mbPopupMode = false; + mbPopupModeCanceled = false; + mbPopupModeTearOff = false; + mbMouseDown = false; + + ImplInitSettings(); +} + +void FloatingWindow::ImplInitSettings() +{ + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + + Color aColor; + if (IsControlBackground()) + aColor = GetControlBackground(); + else if (Window::GetStyle() & WB_3DLOOK) + aColor = rStyleSettings.GetFaceColor(); + else + aColor = rStyleSettings.GetWindowColor(); + SetBackground(aColor); +} + +FloatingWindow::FloatingWindow(vcl::Window* pParent, WinBits nStyle) : + SystemWindow(WindowType::FLOATINGWINDOW, "vcl::FloatingWindow maLayoutIdle") +{ + ImplInitFloating(pParent, nStyle); +} + +FloatingWindow::FloatingWindow(vcl::Window* pParent, const OString& rID, const OUString& rUIXMLDescription, const css::uno::Reference<css::frame::XFrame> &rFrame) + : SystemWindow(WindowType::FLOATINGWINDOW, "vcl::FloatingWindow maLayoutIdle") + , mpNextFloat(nullptr) + , mpFirstPopupModeWin(nullptr) + , mnPostId(nullptr) + , mnPopupModeFlags(FloatWinPopupFlags::NONE) + , mnTitle(FloatWinTitleType::Unknown) + , mnOldTitle(FloatWinTitleType::Unknown) + , mbInPopupMode(false) + , mbPopupMode(false) + , mbPopupModeCanceled(false) + , mbPopupModeTearOff(false) + , mbMouseDown(false) + , mbGrabFocus(false) + , mbInCleanUp(false) +{ + loadUI(pParent, rID, rUIXMLDescription, rFrame); +} + +//Find the real parent stashed in mpDialogParent. +void FloatingWindow::doDeferredInit(WinBits nBits) +{ + vcl::Window *pParent = mpDialogParent; + mpDialogParent = nullptr; + ImplInitFloating(pParent, nBits); + mbIsDeferredInit = false; +} + +void FloatingWindow::ApplySettings(vcl::RenderContext& rRenderContext) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + Color aColor; + if (Window::GetStyle() & WB_3DLOOK) + aColor = rStyleSettings.GetFaceColor(); + else + aColor = rStyleSettings.GetWindowColor(); + + ApplyControlBackground(rRenderContext, aColor); +} + +FloatingWindow::~FloatingWindow() +{ + disposeOnce(); + assert (!mnPostId); +} + +void FloatingWindow::dispose() +{ + ReleaseLOKNotifier(); + + if (mpImplData) + { + if( mbPopupModeCanceled ) + // indicates that ESC key was pressed + // will be handled in Window::ImplGrabFocus() + SetDialogControlFlags( GetDialogControlFlags() | DialogControlFlags::FloatWinPopupModeEndCancel ); + + if ( IsInPopupMode() ) + EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll | FloatWinPopupEndFlags::DontCallHdl ); + + if ( mnPostId ) + Application::RemoveUserEvent( mnPostId ); + mnPostId = nullptr; + } + + mpImplData.reset(); + + mpNextFloat.clear(); + mpFirstPopupModeWin.clear(); + mxPrevFocusWin.clear(); + SystemWindow::dispose(); +} + +Point FloatingWindow::ImplCalcPos(vcl::Window* pWindow, + const tools::Rectangle& rRect, FloatWinPopupFlags nFlags, + sal_uInt16& rArrangeIndex, Point* pLOKTwipsPos) +{ + // get window position + Point aPos; + Size aSize = ::isLayoutEnabled(pWindow) ? pWindow->get_preferred_size() : pWindow->GetSizePixel(); + tools::Rectangle aScreenRect = pWindow->ImplGetFrameWindow()->GetDesktopRectPixel(); + FloatingWindow *pFloatingWindow = dynamic_cast<FloatingWindow*>( pWindow ); + + // convert... + vcl::Window* pW = pWindow; + if ( pW->mpWindowImpl->mpRealParent ) + pW = pW->mpWindowImpl->mpRealParent; + + tools::Rectangle normRect( rRect ); // rRect is already relative to top-level window + normRect.SetPos( pW->ScreenToOutputPixel( normRect.TopLeft() ) ); + + bool bRTL = AllSettings::GetLayoutRTL(); + + tools::Rectangle devRect( pW->OutputToAbsoluteScreenPixel( normRect.TopLeft() ), + pW->OutputToAbsoluteScreenPixel( normRect.BottomRight() ) ); + + tools::Rectangle devRectRTL( devRect ); + if( bRTL ) + // create a rect that can be compared to desktop coordinates + devRectRTL = pW->ImplOutputToUnmirroredAbsoluteScreenPixel( normRect ); + if( Application::GetScreenCount() > 1 && Application::IsUnifiedDisplay() ) + aScreenRect = Application::GetScreenPosSizePixel( + Application::GetBestScreen( bRTL ? devRectRTL : devRect ) ); + + FloatWinPopupFlags nArrangeAry[5]; + sal_uInt16 nArrangeAttempts = 5; + Point e1,e2; // the common edge between the item rect and the floating window + + if ( nFlags & FloatWinPopupFlags::Left ) + { + nArrangeAry[0] = FloatWinPopupFlags::Left; + nArrangeAry[1] = FloatWinPopupFlags::Right; + nArrangeAry[2] = FloatWinPopupFlags::Up; + nArrangeAry[3] = FloatWinPopupFlags::Down; + nArrangeAry[4] = FloatWinPopupFlags::Left; + } + else if ( nFlags & FloatWinPopupFlags::Right ) + { + nArrangeAry[0] = FloatWinPopupFlags::Right; + nArrangeAry[1] = FloatWinPopupFlags::Left; + nArrangeAry[2] = FloatWinPopupFlags::Up; + nArrangeAry[3] = FloatWinPopupFlags::Down; + nArrangeAry[4] = FloatWinPopupFlags::Right; + } + else if ( nFlags & FloatWinPopupFlags::Up ) + { + nArrangeAry[0] = FloatWinPopupFlags::Up; + nArrangeAry[1] = FloatWinPopupFlags::Down; + if (nFlags & FloatWinPopupFlags::NoHorzPlacement) + { + nArrangeAry[2] = FloatWinPopupFlags::Up; + nArrangeAttempts = 3; + } + else + { + nArrangeAry[2] = FloatWinPopupFlags::Right; + nArrangeAry[3] = FloatWinPopupFlags::Left; + nArrangeAry[4] = FloatWinPopupFlags::Up; + } + } + else + { + nArrangeAry[0] = FloatWinPopupFlags::Down; + nArrangeAry[1] = FloatWinPopupFlags::Up; + if (nFlags & FloatWinPopupFlags::NoHorzPlacement) + { + nArrangeAry[2] = FloatWinPopupFlags::Down; + nArrangeAttempts = 3; + } + else + { + nArrangeAry[2] = FloatWinPopupFlags::Right; + nArrangeAry[3] = FloatWinPopupFlags::Left; + nArrangeAry[4] = FloatWinPopupFlags::Down; + } + } + + sal_uInt16 nArrangeIndex = 0; + const bool bLOKActive = comphelper::LibreOfficeKit::isActive(); + + for ( ; nArrangeIndex < nArrangeAttempts; nArrangeIndex++ ) + { + bool bBreak = true; + switch ( nArrangeAry[nArrangeIndex] ) + { + + case FloatWinPopupFlags::Left: + aPos.setX( devRect.Left()-aSize.Width()+1 ); + aPos.setY( devRect.Top() ); + aPos.AdjustY( -(pWindow->mpWindowImpl->mnTopBorder) ); + if( bRTL ) + { + if( (devRectRTL.Right()+aSize.Width()) > aScreenRect.Right() ) + bBreak = false; + } + else + { + if ( aPos.X() < aScreenRect.Left() ) + bBreak = false; + } + if (bBreak || bLOKActive) + { + e1 = devRect.TopLeft(); + e2 = devRect.BottomLeft(); + // set non-zero width + e2.AdjustX( 1 ); + // don't clip corners + e1.AdjustY( 1 ); + e2.AdjustY( -1 ); + } + break; + case FloatWinPopupFlags::Right: + aPos = devRect.TopRight(); + aPos.AdjustY( -(pWindow->mpWindowImpl->mnTopBorder) ); + if( bRTL ) + { + if( (devRectRTL.Left() - aSize.Width()) < aScreenRect.Left() ) + bBreak = false; + } + else + { + if ( aPos.X()+aSize.Width() > aScreenRect.Right() ) + bBreak = false; + } + if (bBreak || bLOKActive) + { + e1 = devRect.TopRight(); + e2 = devRect.BottomRight(); + // set non-zero width + e2.AdjustX( 1 ); + // don't clip corners + e1.AdjustY( 1 ); + e2.AdjustY( -1 ); + } + break; + case FloatWinPopupFlags::Up: + aPos.setX( devRect.Left() ); + aPos.setY( devRect.Top()-aSize.Height()+1 ); + if ( aPos.Y() < aScreenRect.Top() ) + bBreak = false; + if (bBreak || bLOKActive) + { + e1 = devRect.TopLeft(); + e2 = devRect.TopRight(); + // set non-zero height + e2.AdjustY( 1 ); + // don't clip corners + e1.AdjustX( 1 ); + e2.AdjustX( -1 ); + } + break; + case FloatWinPopupFlags::Down: + aPos = devRect.BottomLeft(); + if ( aPos.Y()+aSize.Height() > aScreenRect.Bottom() ) + bBreak = false; + if (bBreak || bLOKActive) + { + e1 = devRect.BottomLeft(); + e2 = devRect.BottomRight(); + // set non-zero height + e2.AdjustY( 1 ); + // don't clip corners + e1.AdjustX( 1 ); + e2.AdjustX( -1 ); + } + break; + default: break; + } + + // no further adjustment for LibreOfficeKit + if (bLOKActive) + break; + + // adjust if necessary + if (bBreak) + { + if ( (nArrangeAry[nArrangeIndex] == FloatWinPopupFlags::Left) || + (nArrangeAry[nArrangeIndex] == FloatWinPopupFlags::Right) ) + { + if ( aPos.Y()+aSize.Height() > aScreenRect.Bottom() ) + { + aPos.setY( devRect.Bottom()-aSize.Height()+1 ); + if ( aPos.Y() < aScreenRect.Top() ) + aPos.setY( aScreenRect.Top() ); + } + } + else + { + if( bRTL ) + { + if( devRectRTL.Right()-aSize.Width()+1 < aScreenRect.Left() ) + aPos.AdjustX( -(aScreenRect.Left() - devRectRTL.Right() + aSize.Width() - 1) ); + } + else if ( aPos.X()+aSize.Width() > aScreenRect.Right() ) + { + aPos.setX( devRect.Right()-aSize.Width()+1 ); + if ( aPos.X() < aScreenRect.Left() ) + aPos.setX( aScreenRect.Left() ); + } + } + } + + if ( bBreak ) + break; + } + if (nArrangeIndex >= nArrangeAttempts) + nArrangeIndex = nArrangeAttempts - 1; + + rArrangeIndex = nArrangeIndex; + + aPos = pW->AbsoluteScreenToOutputPixel( aPos ); + + // store a cliprect that can be used to clip the common edge of the itemrect and the floating window + if( pFloatingWindow && pFloatingWindow->mpImplData->mpBox ) + { + pFloatingWindow->mpImplData->maItemEdgeClipRect = + tools::Rectangle( e1, e2 ); + } + + if (bLOKActive && pLOKTwipsPos) + { + if (pW->IsMapModeEnabled() || pW->GetMapMode().GetMapUnit() == MapUnit::MapPixel) + { + // if we use pW->LogicToLogic(aPos, pW->GetMapMode(), MapMode(MapUnit::MapTwip)), + // for pixel conversions when map mode is not enabled, we get + // a 20 twips per pixel conversion since LogicToLogic uses + // a fixed 72 dpi value, instead of a correctly computed output + // device dpi or at least the most commonly used 96 dpi value; + // and anyway the following is what we already do in + // ScGridWindow::LogicInvalidate when map mode is not enabled. + + *pLOKTwipsPos = pW->PixelToLogic(aPos, MapMode(MapUnit::MapTwip)); + } + else + { + *pLOKTwipsPos = OutputDevice::LogicToLogic(aPos, pW->GetMapMode(), MapMode(MapUnit::MapTwip)); + } + } + + // caller expects coordinates relative to top-level win + return pW->OutputToScreenPixel( aPos ); +} + +Point FloatingWindow::ImplConvertToAbsPos(vcl::Window* pReference, const Point& rPos) +{ + Point aAbsolute( rPos ); + + const OutputDevice *pWindowOutDev = pReference->GetOutDev(); + + // compare coordinates in absolute screen coordinates + if( pWindowOutDev->HasMirroredGraphics() ) + { + if(!pReference->IsRTLEnabled() ) + pWindowOutDev->ReMirror( aAbsolute ); + + tools::Rectangle aRect( pReference->ScreenToOutputPixel(aAbsolute), Size(1,1) ) ; + aRect = pReference->ImplOutputToUnmirroredAbsoluteScreenPixel( aRect ); + aAbsolute = aRect.TopLeft(); + } + else + aAbsolute = pReference->OutputToAbsoluteScreenPixel( + pReference->ScreenToOutputPixel(rPos) ); + + return aAbsolute; +} + +tools::Rectangle FloatingWindow::ImplConvertToAbsPos(vcl::Window* pReference, const tools::Rectangle& rRect) +{ + tools::Rectangle aFloatRect = rRect; + + const OutputDevice *pParentWinOutDev = pReference->GetOutDev(); + + // compare coordinates in absolute screen coordinates + // Keep in sync with FloatingWindow::ImplFloatHitTest, e.g. fdo#33509 + if( pParentWinOutDev->HasMirroredGraphics() && !comphelper::LibreOfficeKit::isActive() ) + { + if(!pReference->IsRTLEnabled() ) + pParentWinOutDev->ReMirror(aFloatRect); + + aFloatRect.SetPos(pReference->ScreenToOutputPixel(aFloatRect.TopLeft())); + aFloatRect = pReference->ImplOutputToUnmirroredAbsoluteScreenPixel(aFloatRect); + } + else + aFloatRect.SetPos(pReference->OutputToAbsoluteScreenPixel(pReference->ScreenToOutputPixel(rRect.TopLeft()))); + + return aFloatRect; +} + +tools::Rectangle FloatingWindow::ImplConvertToRelPos(vcl::Window* pReference, const tools::Rectangle& rRect) +{ + tools::Rectangle aFloatRect = rRect; + + const OutputDevice *pParentWinOutDev = pReference->GetOutDev(); + + // compare coordinates in absolute screen coordinates + // Keep in sync with FloatingWindow::ImplFloatHitTest, e.g. fdo#33509 + if( pParentWinOutDev->HasMirroredGraphics() ) + { + aFloatRect = pReference->ImplUnmirroredAbsoluteScreenToOutputPixel(aFloatRect); + aFloatRect.SetPos(pReference->OutputToScreenPixel(aFloatRect.TopLeft())); + + if(!pReference->IsRTLEnabled() ) + pParentWinOutDev->ReMirror(aFloatRect); + } + else + aFloatRect.SetPos(pReference->OutputToScreenPixel(pReference->AbsoluteScreenToOutputPixel(rRect.TopLeft()))); + + return aFloatRect; +} + +FloatingWindow* FloatingWindow::ImplFloatHitTest( vcl::Window* pReference, const Point& rPos, bool& rbHitTestInsideRect ) +{ + FloatingWindow* pWin = this; + rbHitTestInsideRect = false; + + Point aAbsolute(FloatingWindow::ImplConvertToAbsPos(pReference, rPos)); + + do + { + // compute the floating window's size in absolute screen coordinates + + // use the border window to have the exact position + vcl::Window *pBorderWin = pWin->GetWindow( GetWindowType::Border ); + if (!pBorderWin) + break; + + // the top-left corner in output coordinates ie (0,0) + tools::Rectangle devRect( pBorderWin->ImplOutputToUnmirroredAbsoluteScreenPixel( tools::Rectangle( Point(), pBorderWin->GetSizePixel()) ) ) ; + if ( devRect.Contains( aAbsolute ) ) + { + // inside the window + return pWin; + } + + // test, if mouse is in rectangle, (this is typically the rect of the active + // toolbox item or similar) + // note: maFloatRect is set in FloatingWindow::StartPopupMode() and + // is already in absolute device coordinates + if ( pWin->maFloatRect.Contains( aAbsolute ) ) + { + rbHitTestInsideRect = true; + return pWin; + } + + pWin = pWin->mpNextFloat; + } + while ( pWin ); + + return nullptr; +} + +FloatingWindow* FloatingWindow::ImplFindLastLevelFloat() +{ + FloatingWindow* pWin = this; + FloatingWindow* pLastFoundWin = pWin; + + do + { + if ( pWin->GetPopupModeFlags() & FloatWinPopupFlags::NewLevel ) + pLastFoundWin = pWin; + + pWin = pWin->mpNextFloat; + } + while ( pWin ); + + return pLastFoundWin; +} + +bool FloatingWindow::ImplIsFloatPopupModeWindow( const vcl::Window* pWindow ) +{ + FloatingWindow* pWin = this; + + do + { + if ( pWin->mpFirstPopupModeWin == pWindow ) + return true; + + pWin = pWin->mpNextFloat; + } + while ( pWin ); + + return false; +} + +IMPL_LINK_NOARG(FloatingWindow, ImplEndPopupModeHdl, void*, void) +{ + VclPtr<FloatingWindow> pThis(this); + mnPostId = nullptr; + mnPopupModeFlags = FloatWinPopupFlags::NONE; + mbPopupMode = false; + PopupModeEnd(); +} + +bool FloatingWindow::EventNotify( NotifyEvent& rNEvt ) +{ + // call Base Class first for tab control + bool bRet = SystemWindow::EventNotify( rNEvt ); + if ( !bRet ) + { + if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) + { + const KeyEvent* pKEvt = rNEvt.GetKeyEvent(); + vcl::KeyCode aKeyCode = pKEvt->GetKeyCode(); + sal_uInt16 nKeyCode = aKeyCode.GetCode(); + + if ( (nKeyCode == KEY_ESCAPE) && (GetStyle() & WB_CLOSEABLE) ) + { + Close(); + return true; + } + } + } + + return bRet; +} + +void FloatingWindow::PixelInvalidate(const tools::Rectangle* /*pRectangle*/) +{ + if (VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier()) + { + const tools::Rectangle aRect(Point(0,0), Size(GetSizePixel().Width()+1, GetSizePixel().Height()+1)); + std::vector<vcl::LOKPayloadItem> aPayload + { + std::make_pair(OString("rectangle"), aRect.toString()) + }; + const vcl::ILibreOfficeKitNotifier* pNotifier = pParent->GetLOKNotifier(); + pNotifier->notifyWindow(GetLOKWindowId(), "invalidate", aPayload); + } +} + +void FloatingWindow::StateChanged( StateChangedType nType ) +{ + if (nType == StateChangedType::InitShow) + { + DoInitialLayout(); + } + + SystemWindow::StateChanged( nType ); + + VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier(); + if (pParent) + { + if (nType == StateChangedType::InitShow) + { + std::vector<vcl::LOKPayloadItem> aItems; + if (pParent == this) + { + // we are a toplevel window, let's so far pretend to be a + // dialog - but maybe we'll need a separate type for this + // later + if (mbInPopupMode) + aItems.emplace_back("type", "dropdown"); + else + aItems.emplace_back("type", "dialog"); + aItems.emplace_back("position", mpImplData->maLOKTwipsPos.toString()); // twips + } + else + { + SetLOKNotifier(pParent->GetLOKNotifier()); + if (dynamic_cast<HelpTextWindow*>(this)) + aItems.emplace_back("type", "tooltip"); + else + aItems.emplace_back("type", "child"); + + aItems.emplace_back("parentId", OString::number(pParent->GetLOKWindowId())); + if (mbInPopupMode) + aItems.emplace_back("position", mpImplData->maPos.toString()); // pixels + else // mpImplData->maPos is not set + aItems.emplace_back("position", GetPosPixel().toString()); + + } + aItems.emplace_back("size", GetSizePixel().toString()); + GetLOKNotifier()->notifyWindow(GetLOKWindowId(), "created", aItems); + } + else if (!IsVisible() && nType == StateChangedType::Visible) + { + if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier()) + { + pNotifier->notifyWindow(GetLOKWindowId(), "close"); + ReleaseLOKNotifier(); + } + } + } + + if ( nType == StateChangedType::ControlBackground ) + { + ImplInitSettings(); + Invalidate(); + } +} + +void FloatingWindow::DataChanged( const DataChangedEvent& rDCEvt ) +{ + SystemWindow::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + ImplInitSettings(); + Invalidate(); + } +} + +void FloatingWindow::ImplCallPopupModeEnd() +{ + // PopupMode is finished + mbInPopupMode = false; + + // call Handler asynchronously. + if ( mpImplData && !mnPostId ) + mnPostId = Application::PostUserEvent(LINK(this, FloatingWindow, ImplEndPopupModeHdl)); +} + +void FloatingWindow::PopupModeEnd() +{ + maPopupModeEndHdl.Call( this ); +} + +void FloatingWindow::SetTitleType( FloatWinTitleType nTitle ) +{ + if ( (mnTitle == nTitle) || !mpWindowImpl->mpBorderWindow ) + return; + + mnTitle = nTitle; + Size aOutSize = GetOutputSizePixel(); + BorderWindowTitleType nTitleStyle; + if ( nTitle == FloatWinTitleType::Normal ) + nTitleStyle = BorderWindowTitleType::Small; + else if ( nTitle == FloatWinTitleType::TearOff ) + nTitleStyle = BorderWindowTitleType::Tearoff; + else if ( nTitle == FloatWinTitleType::Popup ) + nTitleStyle = BorderWindowTitleType::Popup; + else // nTitle == FloatWinTitleType::NONE + nTitleStyle = BorderWindowTitleType::NONE; + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetTitleType( nTitleStyle, aOutSize ); + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder ); +} + +void FloatingWindow::StartPopupMode( const tools::Rectangle& rRect, FloatWinPopupFlags nFlags ) +{ + // remove title + mnOldTitle = mnTitle; + if ( ( mpWindowImpl->mnStyle & WB_POPUP ) && !GetText().isEmpty() ) + SetTitleType( FloatWinTitleType::Popup ); + else if ( nFlags & FloatWinPopupFlags::AllowTearOff ) + SetTitleType( FloatWinTitleType::TearOff ); + else + SetTitleType( FloatWinTitleType::NONE ); + + // avoid close on focus change for decorated floating windows only + if( mpWindowImpl->mbFrame && (GetStyle() & WB_MOVEABLE) ) + nFlags |= FloatWinPopupFlags::NoAppFocusClose; + + // compute window position according to flags and arrangement + sal_uInt16 nArrangeIndex; + DoInitialLayout(); + mpImplData->maPos = ImplCalcPos(this, rRect, nFlags, nArrangeIndex, &mpImplData->maLOKTwipsPos); + SetPosPixel( mpImplData->maPos ); + ImplGetFrame()->PositionByToolkit(rRect, nFlags); + + // set data and display window + // convert maFloatRect to absolute device coordinates + // so they can be compared across different frames + // !!! rRect is expected to be in screen coordinates of the parent frame window !!! + maFloatRect = FloatingWindow::ImplConvertToAbsPos(GetParent(), rRect); + + maFloatRect.AdjustLeft( -2 ); + maFloatRect.AdjustTop( -2 ); + maFloatRect.AdjustRight(2 ); + maFloatRect.AdjustBottom(2 ); + mnPopupModeFlags = nFlags; + mbInPopupMode = true; + mbPopupMode = true; + mbPopupModeCanceled = false; + mbPopupModeTearOff = false; + mbMouseDown = false; + + // add FloatingWindow to list of windows that are in popup mode + ImplSVData* pSVData = ImplGetSVData(); + mpNextFloat = pSVData->mpWinData->mpFirstFloat; + pSVData->mpWinData->mpFirstFloat = this; + bool bGrabFocus(nFlags & FloatWinPopupFlags::GrabFocus); + if (bGrabFocus) + { + // force key input even without focus (useful for menus) + mbGrabFocus = true; + mxPrevFocusWin = Window::SaveFocus(); + mpWindowImpl->mpFrameData->mbHasFocus = true; + } + Show( true, ShowFlags::NoActivate ); + if (bGrabFocus) + GrabFocus(); +} + +void FloatingWindow::StartPopupMode( ToolBox* pBox, FloatWinPopupFlags nFlags ) +{ + mpImplData->mpBox = pBox; + + // get selected button + ToolBoxItemId nItemId = pBox->GetDownItemId(); + + if ( nItemId ) + pBox->ImplFloatControl( true, this ); + + // retrieve some data from the ToolBox + tools::Rectangle aRect = nItemId ? pBox->GetItemRect( nItemId ) : pBox->GetOverflowRect(); + + // convert to parent's screen coordinates + mpImplData->maPos = GetParent()->OutputToScreenPixel( GetParent()->AbsoluteScreenToOutputPixel( pBox->OutputToAbsoluteScreenPixel( aRect.TopLeft() ) ) ); + aRect.SetPos( mpImplData->maPos ); + + nFlags |= + FloatWinPopupFlags::AllMouseButtonClose | + FloatWinPopupFlags::NoMouseUpClose; + + // set Flags for positioning + if ( !(nFlags & (FloatWinPopupFlags::Down | FloatWinPopupFlags::Up | + FloatWinPopupFlags::Left | FloatWinPopupFlags::Right)) ) + { + if ( pBox->IsHorizontal() ) + nFlags |= FloatWinPopupFlags::Down; + else + nFlags |= FloatWinPopupFlags::Right; + } + + // start FloatingMode + StartPopupMode( aRect, nFlags ); +} + +void FloatingWindow::ImplEndPopupMode( FloatWinPopupEndFlags nFlags, const VclPtr<vcl::Window>& xFocusId ) +{ + if ( !mbInPopupMode ) + return; + + ImplSVData* pSVData = ImplGetSVData(); + + mbInCleanUp = true; // prevent killing this window due to focus change while working with it + + if (!(nFlags & FloatWinPopupEndFlags::NoCloseChildren)) + { + // stop the PopupMode also for all PopupMode windows created after us + std::vector<VclPtr<FloatingWindow>> aCancelFloats; + // stop the PopupMode also for all following PopupMode windows + for (auto pFloat = pSVData->mpWinData->mpFirstFloat; + pFloat != nullptr && pFloat != this; + pFloat = pFloat->mpNextFloat) + aCancelFloats.push_back(pFloat); + for (auto & it : aCancelFloats) + it->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::NoCloseChildren); + } + + // delete window from the list + pSVData->mpWinData->mpFirstFloat = mpNextFloat; + mpNextFloat = nullptr; + + FloatWinPopupFlags nPopupModeFlags = mnPopupModeFlags; + mbPopupModeTearOff = nFlags & FloatWinPopupEndFlags::TearOff && + nPopupModeFlags & FloatWinPopupFlags::AllowTearOff; + + // hide window again if it was not deleted + if (!mbPopupModeTearOff) + Show( false, ShowFlags::NoFocusChange ); + + if (HasChildPathFocus() && xFocusId != nullptr) + { + // restore focus to previous focus window if we still have the focus + Window::EndSaveFocus(xFocusId); + } + else if ( pSVData->mpWinData->mpFocusWin && pSVData->mpWinData->mpFirstFloat && + ImplIsWindowOrChild( pSVData->mpWinData->mpFocusWin ) ) + { + // maybe pass focus on to a suitable FloatingWindow + pSVData->mpWinData->mpFirstFloat->GrabFocus(); + } + + mbPopupModeCanceled = bool(nFlags & FloatWinPopupEndFlags::Cancel); + + // redo title + SetTitleType( mnOldTitle ); + + // set ToolBox again to normal + if (mpImplData && mpImplData->mpBox) + { + mpImplData->mpBox->ImplFloatControl( false, this ); + // if the parent ToolBox is in popup mode, it should be closed too. + if ( GetDockingManager()->IsInPopupMode( mpImplData->mpBox ) ) + nFlags |= FloatWinPopupEndFlags::CloseAll; + + mpImplData->mpBox = nullptr; + } + + // call PopupModeEnd-Handler depending on parameter + if ( !(nFlags & FloatWinPopupEndFlags::DontCallHdl) ) + ImplCallPopupModeEnd(); + + // close all other windows depending on parameter + if ( nFlags & FloatWinPopupEndFlags::CloseAll ) + { + if ( !(nPopupModeFlags & FloatWinPopupFlags::NewLevel) ) + { + if (pSVData->mpWinData->mpFirstFloat) + { + FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat(); + pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll ); + } + } + } + + mbInCleanUp = false; +} + +void FloatingWindow::EndPopupMode( FloatWinPopupEndFlags nFlags ) +{ + ImplEndPopupMode(nFlags, mxPrevFocusWin); +} + +void FloatingWindow::AddPopupModeWindow(vcl::Window* pWindow) +{ + // !!! up-to-now only 1 window and not yet a list + mpFirstPopupModeWin = pWindow; +} + +bool SystemWindow::UpdatePositionData() +{ + auto pWin = ImplGetParent(); + if (pWin) + { + // Simulate Move, so the relative position of the floating window will be recalculated + pWin->ImplCallMove(); + return true; + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/globalization.cxx b/vcl/source/window/globalization.cxx new file mode 100644 index 000000000..952182978 --- /dev/null +++ b/vcl/source/window/globalization.cxx @@ -0,0 +1,44 @@ +/* -*- 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/window.hxx> +#include <vcl/outdev.hxx> +#include <windowdev.hxx> +#include <window.h> + +namespace vcl { + +void WindowOutputDevice::EnableRTL ( bool bEnable ) +{ + if (mbEnableRTL != bEnable) + mxOwnerWindow->ImplEnableRTL(bEnable); +} + +void Window::ImplEnableRTL( bool bEnable ) +{ + if (mpWindowImpl->mxOutDev->mbEnableRTL != bEnable) + { + CompatStateChanged( StateChangedType::Mirroring ); + mpWindowImpl->mxOutDev->OutputDevice::EnableRTL(bEnable); + } +} + +} /* namespace vcl */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/impldockingwrapper.hxx b/vcl/source/window/impldockingwrapper.hxx new file mode 100644 index 000000000..671510e3c --- /dev/null +++ b/vcl/source/window/impldockingwrapper.hxx @@ -0,0 +1,131 @@ +/* -*- 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/dockwin.hxx> +#include <memory> +#include <vector> + +/** ImplDockingWindowWrapper + * + * ImplDockingWindowWrapper obsoletes the DockingWindow class. + * It is better because it can make a "normal window" dockable. + * All DockingWindows should be converted the new class. + */ + +class ImplDockingWindowWrapper final +{ + friend class ::vcl::Window; + friend class DockingManager; + friend class DockingWindow; + +private: + + // the original 'Docking'window + VclPtr<vcl::Window> mpDockingWindow; + + // the original DockingWindow members + VclPtr<FloatingWindow> mpFloatWin; + VclPtr<vcl::Window> mpOldBorderWin; + VclPtr<vcl::Window> mpParent; + Link<FloatingWindow*,void> maPopupModeEndHdl; + Point maFloatPos; + Point maDockPos; + Point maMouseOff; + Size maMinOutSize; + Size maMaxOutSize; + tools::Rectangle maDragArea; + tools::Long mnTrackX; + tools::Long mnTrackY; + tools::Long mnTrackWidth; + tools::Long mnTrackHeight; + sal_Int32 mnDockLeft; + sal_Int32 mnDockTop; + sal_Int32 mnDockRight; + sal_Int32 mnDockBottom; + WinBits mnFloatBits; + bool mbDockCanceled:1, + mbDocking:1, + mbLastFloatMode:1, + mbDockBtn:1, + mbHideBtn:1, + mbStartDockingEnabled:1, + mbLocked:1; + + DECL_LINK( PopupModeEnd, FloatingWindow*, void ); + void ImplEnableStartDocking() { mbStartDockingEnabled = true; } + bool ImplStartDockingEnabled() const { return mbStartDockingEnabled; } + void ImplPreparePopupMode(); + +public: + ImplDockingWindowWrapper( const vcl::Window *pWindow ); + ~ImplDockingWindowWrapper(); + + vcl::Window* GetWindow() { return mpDockingWindow; } + void ImplStartDocking( const Point& rPos ); + + // those methods actually call the corresponding handlers + void StartDocking( const Point& rPos, tools::Rectangle const & rRect ); + bool Docking( const Point& rPos, tools::Rectangle& rRect ); + void EndDocking( const tools::Rectangle& rRect, bool bFloatMode ); + bool PrepareToggleFloatingMode(); + void ToggleFloatingMode(); + + void SetDragArea( const tools::Rectangle& rRect ); + const tools::Rectangle& GetDragArea() const { return maDragArea;} + + void Lock(); + void Unlock(); + bool IsLocked() const { return mbLocked;} + + void StartPopupMode( const tools::Rectangle& rRect, FloatWinPopupFlags nPopupModeFlags ); + void StartPopupMode( ToolBox* pParentToolBox, FloatWinPopupFlags nPopupModeFlags ); + bool IsInPopupMode() const; + + void SetPopupModeEndHdl( const Link<FloatingWindow*,void>& rLink ) { maPopupModeEndHdl = rLink; } + + void TitleButtonClick( TitleButton nButton ); + void Resizing( Size& rSize ); + void Tracking( const TrackingEvent& rTEvt ); + + void ShowMenuTitleButton( bool bVisible ); + + void SetMinOutputSizePixel( const Size& rSize ); + + void SetMaxOutputSizePixel( const Size& rSize ); + + bool IsDocking() const { return mbDocking; } + bool IsDockingCanceled() const { return mbDockCanceled; } + + void SetFloatingMode( bool bFloatMode ); + bool IsFloatingMode() const; + SystemWindow* GetFloatingWindow() const; + + void SetFloatStyle( WinBits nWinStyle ); + WinBits GetFloatStyle() const { return mnFloatBits;} + + void setPosSizePixel( tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + PosSizeFlags nFlags ); + Point GetPosPixel() const; + Size GetSizePixel() const; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/introwin.cxx b/vcl/source/window/introwin.cxx new file mode 100644 index 000000000..3ac75eab0 --- /dev/null +++ b/vcl/source/window/introwin.cxx @@ -0,0 +1,49 @@ +/* -*- 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/wrkwin.hxx> +#include <vcl/introwin.hxx> + +#include <svdata.hxx> + +void IntroWindow::ImplInitIntroWindowData() +{ + ImplSVData* pSVData = ImplGetSVData(); + pSVData->mpIntroWindow = this; +} + +IntroWindow::IntroWindow() + : WorkWindow(WindowType::INTROWINDOW) +{ + ImplInitIntroWindowData(); + WorkWindow::ImplInit(nullptr, WB_INTROWIN); +} + +IntroWindow::~IntroWindow() { disposeOnce(); } + +void IntroWindow::dispose() +{ + ImplSVData* pSVData = ImplGetSVData(); + if (pSVData->mpIntroWindow.get() == this) + pSVData->mpIntroWindow = nullptr; + + WorkWindow::dispose(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/keycod.cxx b/vcl/source/window/keycod.cxx new file mode 100644 index 000000000..cc3fca580 --- /dev/null +++ b/vcl/source/window/keycod.cxx @@ -0,0 +1,95 @@ +/* -*- 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 <accel.hxx> +#include <salframe.hxx> +#include <svdata.hxx> + +#include <vcl/window.hxx> +#include <vcl/keycod.hxx> + +const sal_uInt16 aImplKeyFuncTab[(static_cast<int>(KeyFuncType::DELETE)+1)*4] = +{ + 0, 0, 0, 0, // KeyFuncType::DONTKNOW + KEY_X | KEY_MOD1, KEY_DELETE | KEY_SHIFT, KEY_CUT, 0, // KeyFuncType::CUT + KEY_C | KEY_MOD1, KEY_INSERT | KEY_MOD1, KEY_COPY, 0, // KeyFuncType::COPY + KEY_V | KEY_MOD1, KEY_INSERT | KEY_SHIFT, KEY_PASTE, 0, // KeyFuncType::PASTE + KEY_Z | KEY_MOD1, KEY_BACKSPACE | KEY_MOD2, KEY_UNDO, 0, // KeyFuncType::UNDO + KEY_Y | KEY_MOD1, KEY_UNDO | KEY_SHIFT, 0, 0, // KeyFuncType::REDO + KEY_DELETE, 0, 0, 0 // KeyFuncType::DELETE +}; + +bool ImplGetKeyCode( KeyFuncType eFunc, sal_uInt16& rCode1, sal_uInt16& rCode2, sal_uInt16& rCode3, sal_uInt16& rCode4 ) +{ + size_t nIndex = static_cast<size_t>(eFunc); + nIndex *= 4; + + assert(nIndex + 3 < SAL_N_ELEMENTS(aImplKeyFuncTab) && "bad key code index"); + if (nIndex + 3 >= SAL_N_ELEMENTS(aImplKeyFuncTab)) + { + rCode1 = rCode2 = rCode3 = rCode4 = 0; + return false; + } + + rCode1 = aImplKeyFuncTab[nIndex]; + rCode2 = aImplKeyFuncTab[nIndex+1]; + rCode3 = aImplKeyFuncTab[nIndex+2]; + rCode4 = aImplKeyFuncTab[nIndex+3]; + return true; +} + +vcl::KeyCode::KeyCode( KeyFuncType eFunction ) +{ + sal_uInt16 nDummy; + ImplGetKeyCode( eFunction, nKeyCodeAndModifiers, nDummy, nDummy, nDummy ); + eFunc = eFunction; +} + +OUString vcl::KeyCode::GetName() const +{ + vcl::Window* pWindow = ImplGetDefaultWindow(); + return pWindow ? pWindow->ImplGetFrame()->GetKeyName( GetFullCode() ) : ""; +} + +KeyFuncType vcl::KeyCode::GetFunction() const +{ + if ( eFunc != KeyFuncType::DONTKNOW ) + return eFunc; + + sal_uInt16 nCompCode = GetModifier() | GetCode(); + if ( nCompCode ) + { + for ( sal_uInt16 i = sal_uInt16(KeyFuncType::CUT); i <= sal_uInt16(KeyFuncType::DELETE); i++ ) + { + sal_uInt16 nKeyCode1; + sal_uInt16 nKeyCode2; + sal_uInt16 nKeyCode3; + sal_uInt16 nKeyCode4; + ImplGetKeyCode( static_cast<KeyFuncType>(i), nKeyCode1, nKeyCode2, nKeyCode3, nKeyCode4 ); + if ( (nCompCode == nKeyCode1) || (nCompCode == nKeyCode2) || (nCompCode == nKeyCode3) || (nCompCode == nKeyCode4) ) + return static_cast<KeyFuncType>(i); + } + } + + return KeyFuncType::DONTKNOW; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/keyevent.cxx b/vcl/source/window/keyevent.cxx new file mode 100644 index 000000000..eca00d411 --- /dev/null +++ b/vcl/source/window/keyevent.cxx @@ -0,0 +1,67 @@ +/* -*- 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/event.hxx> + +KeyEvent KeyEvent::LogicalTextDirectionality (TextDirectionality eMode) const +{ + KeyEvent aClone(*this); + + sal_uInt16 nCode = maKeyCode.GetCode(); + sal_uInt16 nMod = maKeyCode.GetModifier(); + + switch (eMode) + { + case TextDirectionality::RightToLeft_TopToBottom: + switch (nCode) + { + case KEY_LEFT: aClone.maKeyCode = vcl::KeyCode(KEY_RIGHT, nMod); break; + case KEY_RIGHT: aClone.maKeyCode = vcl::KeyCode(KEY_LEFT, nMod); break; + } + break; + + case TextDirectionality::TopToBottom_RightToLeft: + switch (nCode) + { + case KEY_DOWN: aClone.maKeyCode = vcl::KeyCode(KEY_RIGHT, nMod); break; + case KEY_UP: aClone.maKeyCode = vcl::KeyCode(KEY_LEFT, nMod); break; + case KEY_LEFT: aClone.maKeyCode = vcl::KeyCode(KEY_DOWN, nMod); break; + case KEY_RIGHT: aClone.maKeyCode = vcl::KeyCode(KEY_UP, nMod); break; + } + break; + + case TextDirectionality::BottomToTop_LeftToRight: + switch (nCode) + { + case KEY_DOWN: aClone.maKeyCode = vcl::KeyCode(KEY_LEFT, nMod); break; + case KEY_UP: aClone.maKeyCode = vcl::KeyCode(KEY_RIGHT, nMod); break; + case KEY_LEFT: aClone.maKeyCode = vcl::KeyCode(KEY_UP, nMod); break; + case KEY_RIGHT: aClone.maKeyCode = vcl::KeyCode(KEY_DOWN, nMod); break; + } + break; + + case TextDirectionality::LeftToRight_TopToBottom: + /* do nothing */ + break; + } + + return aClone; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/layout.cxx b/vcl/source/window/layout.cxx new file mode 100644 index 000000000..946e2cce2 --- /dev/null +++ b/vcl/source/window/layout.cxx @@ -0,0 +1,3028 @@ +/* -*- 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 <config_features.h> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <comphelper/base64.hxx> +#include <comphelper/lok.hxx> +#include <o3tl/enumarray.hxx> +#include <o3tl/enumrange.hxx> +#include <o3tl/string_view.hxx> +#include <tools/stream.hxx> +#include <vcl/builder.hxx> +#include <vcl/toolkit/button.hxx> +#include <vcl/cvtgrf.hxx> +#include <vcl/decoview.hxx> +#include <vcl/help.hxx> +#include <vcl/toolkit/dialog.hxx> +#include <vcl/layout.hxx> +#include <vcl/scrbar.hxx> +#include <vcl/stdtext.hxx> +#include <vcl/split.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> +#include <bitmaps.hlst> +#include <messagedialog.hxx> +#include <svdata.hxx> +#include <window.h> +#include <boost/multi_array.hpp> +#include <vcl/toolkit/vclmedit.hxx> +#include <vcl/uitest/uiobject.hxx> +#include <sal/log.hxx> +#include <tools/json_writer.hxx> + +VclContainer::VclContainer(vcl::Window *pParent, WinBits nStyle) + : Window(WindowType::CONTAINER) + , m_bLayoutDirty(true) +{ + ImplInit(pParent, nStyle, nullptr); + EnableChildTransparentMode(); + SetPaintTransparent(true); + SetBackground(); +} + +sal_uInt16 VclContainer::getDefaultAccessibleRole() const +{ + return css::accessibility::AccessibleRole::PANEL; +} + +Size VclContainer::GetOptimalSize() const +{ + return calculateRequisition(); +} + +void VclContainer::setLayoutPosSize(vcl::Window &rWindow, const Point &rPos, const Size &rSize) +{ + sal_Int32 nBorderWidth = rWindow.get_border_width(); + sal_Int32 nLeft = rWindow.get_margin_start() + nBorderWidth; + sal_Int32 nTop = rWindow.get_margin_top() + nBorderWidth; + sal_Int32 nRight = rWindow.get_margin_end() + nBorderWidth; + sal_Int32 nBottom = rWindow.get_margin_bottom() + nBorderWidth; + Point aPos(rPos.X() + nLeft, rPos.Y() + nTop); + Size aSize(rSize.Width() - nLeft - nRight, rSize.Height() - nTop - nBottom); + rWindow.SetPosSizePixel(aPos, aSize); +} + +void VclContainer::setLayoutAllocation(vcl::Window &rChild, const Point &rAllocPos, const Size &rChildAlloc) +{ + VclAlign eHalign = rChild.get_halign(); + VclAlign eValign = rChild.get_valign(); + + //typical case + if (eHalign == VclAlign::Fill && eValign == VclAlign::Fill) + { + setLayoutPosSize(rChild, rAllocPos, rChildAlloc); + return; + } + + Point aChildPos(rAllocPos); + Size aChildSize(rChildAlloc); + Size aChildPreferredSize(getLayoutRequisition(rChild)); + + switch (eHalign) + { + case VclAlign::Fill: + break; + case VclAlign::Start: + if (aChildPreferredSize.Width() < rChildAlloc.Width()) + aChildSize.setWidth( aChildPreferredSize.Width() ); + break; + case VclAlign::End: + if (aChildPreferredSize.Width() < rChildAlloc.Width()) + aChildSize.setWidth( aChildPreferredSize.Width() ); + aChildPos.AdjustX(rChildAlloc.Width() ); + aChildPos.AdjustX( -(aChildSize.Width()) ); + break; + case VclAlign::Center: + if (aChildPreferredSize.Width() < aChildSize.Width()) + aChildSize.setWidth( aChildPreferredSize.Width() ); + aChildPos.AdjustX((rChildAlloc.Width() - aChildSize.Width()) / 2 ); + break; + } + + switch (eValign) + { + case VclAlign::Fill: + break; + case VclAlign::Start: + if (aChildPreferredSize.Height() < rChildAlloc.Height()) + aChildSize.setHeight( aChildPreferredSize.Height() ); + break; + case VclAlign::End: + if (aChildPreferredSize.Height() < rChildAlloc.Height()) + aChildSize.setHeight( aChildPreferredSize.Height() ); + aChildPos.AdjustY(rChildAlloc.Height() ); + aChildPos.AdjustY( -(aChildSize.Height()) ); + break; + case VclAlign::Center: + if (aChildPreferredSize.Height() < aChildSize.Height()) + aChildSize.setHeight( aChildPreferredSize.Height() ); + aChildPos.AdjustY((rChildAlloc.Height() - aChildSize.Height()) / 2 ); + break; + } + + setLayoutPosSize(rChild, aChildPos, aChildSize); +} + +namespace +{ + Size subtractBorder(const vcl::Window &rWindow, const Size& rSize) + { + sal_Int32 nBorderWidth = rWindow.get_border_width(); + sal_Int32 nLeft = rWindow.get_margin_start() + nBorderWidth; + sal_Int32 nTop = rWindow.get_margin_top() + nBorderWidth; + sal_Int32 nRight = rWindow.get_margin_end() + nBorderWidth; + sal_Int32 nBottom = rWindow.get_margin_bottom() + nBorderWidth; + Size aSize(rSize); + return Size(aSize.Width() + nLeft + nRight, aSize.Height() + nTop + nBottom); + } +} + +Size VclContainer::getLayoutRequisition(const vcl::Window &rWindow) +{ + return subtractBorder(rWindow, rWindow.get_preferred_size()); +} + +void VclContainer::SetPosSizePixel(const Point& rAllocPos, const Size& rAllocation) +{ + bool bSizeChanged = rAllocation != GetOutputSizePixel(); + Window::SetPosSizePixel(rAllocPos, rAllocation); + if (m_bLayoutDirty || bSizeChanged) + { + m_bLayoutDirty = false; + setAllocation(rAllocation); + } +} + +void VclContainer::SetPosPixel(const Point& rAllocPos) +{ + Point aAllocPos = rAllocPos; + sal_Int32 nBorderWidth = get_border_width(); + aAllocPos.AdjustX(nBorderWidth + get_margin_start() ); + aAllocPos.AdjustY(nBorderWidth + get_margin_top() ); + + if (aAllocPos != GetPosPixel()) + Window::SetPosPixel(aAllocPos); +} + +void VclContainer::SetSizePixel(const Size& rAllocation) +{ + Size aAllocation = rAllocation; + sal_Int32 nBorderWidth = get_border_width(); + aAllocation.AdjustWidth( -(nBorderWidth*2 + get_margin_start() + get_margin_end()) ); + aAllocation.AdjustHeight( -(nBorderWidth*2 + get_margin_top() + get_margin_bottom()) ); + bool bSizeChanged = aAllocation != GetSizePixel(); + if (bSizeChanged) + Window::SetSizePixel(aAllocation); + if (m_bLayoutDirty || bSizeChanged) + { + m_bLayoutDirty = false; + setAllocation(aAllocation); + } +} + +void VclContainer::queue_resize(StateChangedType eReason) +{ + m_bLayoutDirty = true; + Window::queue_resize(eReason); +} + +// support for screenshot context menu +void VclContainer::Command(const CommandEvent& rCEvt) +{ + if (CommandEventId::ContextMenu == rCEvt.GetCommand()) + { + auto pParent = GetParent(); + if (pParent) + { + CommandEvent aCEvt(rCEvt.GetMousePosPixel() + GetPosPixel(), rCEvt.GetCommand(), rCEvt.IsMouseEvent(), rCEvt.GetEventData()); + pParent->Command(aCEvt); + return; + } + } + + // call parent (do not consume) + Window::Command(rCEvt); +} + +void VclBox::accumulateMaxes(const Size &rChildSize, Size &rSize) const +{ + tools::Long nSecondaryChildDimension = getSecondaryDimension(rChildSize); + tools::Long nSecondaryBoxDimension = getSecondaryDimension(rSize); + setSecondaryDimension(rSize, std::max(nSecondaryChildDimension, nSecondaryBoxDimension)); + + tools::Long nPrimaryChildDimension = getPrimaryDimension(rChildSize); + tools::Long nPrimaryBoxDimension = getPrimaryDimension(rSize); + if (m_bHomogeneous) + setPrimaryDimension(rSize, std::max(nPrimaryBoxDimension, nPrimaryChildDimension)); + else + setPrimaryDimension(rSize, nPrimaryBoxDimension + nPrimaryChildDimension); +} + +Size VclBox::calculateRequisition() const +{ + sal_uInt16 nVisibleChildren = 0; + + Size aSize; + for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (!pChild->IsVisible()) + continue; + ++nVisibleChildren; + Size aChildSize = getLayoutRequisition(*pChild); + + tools::Long nPrimaryDimension = getPrimaryDimension(aChildSize); + nPrimaryDimension += pChild->get_padding() * 2; + setPrimaryDimension(aChildSize, nPrimaryDimension); + + accumulateMaxes(aChildSize, aSize); + } + + return finalizeMaxes(aSize, nVisibleChildren); +} + +void VclBox::setAllocation(const Size &rAllocation) +{ + sal_uInt16 nVisibleChildren = 0, nExpandChildren = 0; + for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (!pChild->IsVisible()) + continue; + ++nVisibleChildren; + bool bExpand = getPrimaryDimensionChildExpand(*pChild); + if (bExpand) + ++nExpandChildren; + } + + if (!nVisibleChildren) + return; + + tools::Long nAllocPrimaryDimension = getPrimaryDimension(rAllocation); + + tools::Long nHomogeneousDimension = 0, nExtraSpace = 0; + if (m_bHomogeneous) + { + nHomogeneousDimension = (nAllocPrimaryDimension - + (nVisibleChildren - 1) * m_nSpacing) / nVisibleChildren; + } + else if (nExpandChildren) + { + Size aRequisition = calculateRequisition(); + nExtraSpace = (getPrimaryDimension(rAllocation) - getPrimaryDimension(aRequisition)) / nExpandChildren; +// In mobile, the screen size is small and extraSpace can become negative +// Though the dialogs are rendered in javascript for LOK Android some widgets like weld::DrawingArea +// is sent as bitmap but it is rendered from only the visible part +// when it gets negative, it shrinks instead of expands and it becomes invisible + + if (nExtraSpace < 0) + { + SAL_WARN("vcl.layout", "nExtraSpace went negative for VclBox: " << GetHelpId()); + if (comphelper::LibreOfficeKit::isActive()) + { + nExtraSpace = 0; + } + } + } + + //Split into those we pack from the start onwards, and those we pack from the end backwards + o3tl::enumarray<VclPackType,std::vector<vcl::Window*>> aWindows; + for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (!pChild->IsVisible()) + continue; + + VclPackType ePacking = pChild->get_pack_type(); + aWindows[ePacking].push_back(pChild); + } + + //See VclBuilder::sortIntoBestTabTraversalOrder for why they are in visual + //order under the parent which requires us to reverse them here to + //pack from the end back + std::reverse(aWindows[VclPackType::End].begin(),aWindows[VclPackType::End].end()); + + for (VclPackType ePackType : o3tl::enumrange<VclPackType>()) + { + Point aPos(0, 0); + if (ePackType == VclPackType::End) + { + tools::Long nPrimaryCoordinate = getPrimaryCoordinate(aPos); + setPrimaryCoordinate(aPos, nPrimaryCoordinate + nAllocPrimaryDimension); + } + + for (auto const& window : aWindows[ePackType]) + { + vcl::Window *pChild = window; + + tools::Long nPadding = pChild->get_padding(); + + Size aBoxSize; + if (m_bHomogeneous) + setPrimaryDimension(aBoxSize, nHomogeneousDimension); + else + { + aBoxSize = getLayoutRequisition(*pChild); + tools::Long nPrimaryDimension = getPrimaryDimension(aBoxSize); + nPrimaryDimension += nPadding * 2; + if (getPrimaryDimensionChildExpand(*pChild)) + nPrimaryDimension += nExtraSpace; + setPrimaryDimension(aBoxSize, nPrimaryDimension); + } + setSecondaryDimension(aBoxSize, getSecondaryDimension(rAllocation)); + + Point aChildPos(aPos); + Size aChildSize(aBoxSize); + tools::Long nPrimaryCoordinate = getPrimaryCoordinate(aPos); + + bool bFill = pChild->get_fill(); + if (bFill) + { + setPrimaryDimension(aChildSize, std::max(static_cast<tools::Long>(1), + std::min(getPrimaryDimension(rAllocation), getPrimaryDimension(aBoxSize) - nPadding * 2))); + + setPrimaryCoordinate(aChildPos, nPrimaryCoordinate + nPadding); + } + else + { + setPrimaryDimension(aChildSize, + getPrimaryDimension(getLayoutRequisition(*pChild))); + + setPrimaryCoordinate(aChildPos, nPrimaryCoordinate + + (getPrimaryDimension(aBoxSize) - getPrimaryDimension(aChildSize)) / 2); + } + + tools::Long nDiff = getPrimaryDimension(aBoxSize) + m_nSpacing; + if (ePackType == VclPackType::Start) + setPrimaryCoordinate(aPos, nPrimaryCoordinate + nDiff); + else + { + setPrimaryCoordinate(aPos, nPrimaryCoordinate - nDiff); + setPrimaryCoordinate(aChildPos, getPrimaryCoordinate(aChildPos) - + getPrimaryDimension(aBoxSize)); + } + + setLayoutAllocation(*pChild, aChildPos, aChildSize); + } + } +} + +bool VclBox::set_property(const OString &rKey, const OUString &rValue) +{ + if (rKey == "spacing") + set_spacing(rValue.toInt32()); + else if (rKey == "homogeneous") + set_homogeneous(toBool(rValue)); + else + return VclContainer::set_property(rKey, rValue); + return true; +} + +void VclBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + VclContainer::DumpAsPropertyTree(rJsonWriter); + rJsonWriter.put("vertical", m_bVerticalContainer); +} + +sal_uInt16 VclBox::getDefaultAccessibleRole() const +{ +#if defined(_WIN32) + //fdo#74284 call Boxes Panels, keep then as "Filler" under + //at least Linux seeing as that's what Gtk does for GtkBoxes + return css::accessibility::AccessibleRole::PANEL; +#else + return css::accessibility::AccessibleRole::FILLER; +#endif +} + +#define DEFAULT_CHILD_MIN_WIDTH 85 +#define DEFAULT_CHILD_MIN_HEIGHT 27 + +Size VclBox::finalizeMaxes(const Size &rSize, sal_uInt16 nVisibleChildren) const +{ + Size aRet; + + if (nVisibleChildren) + { + tools::Long nPrimaryDimension = getPrimaryDimension(rSize); + if (m_bHomogeneous) + nPrimaryDimension *= nVisibleChildren; + setPrimaryDimension(aRet, nPrimaryDimension + m_nSpacing * (nVisibleChildren-1)); + setSecondaryDimension(aRet, getSecondaryDimension(rSize)); + } + + return aRet; +} + +Size VclButtonBox::addReqGroups(const VclButtonBox::Requisition &rReq) const +{ + Size aRet; + + tools::Long nMainGroupDimension = getPrimaryDimension(rReq.m_aMainGroupSize); + tools::Long nSubGroupDimension = getPrimaryDimension(rReq.m_aSubGroupSize); + + setPrimaryDimension(aRet, nMainGroupDimension + nSubGroupDimension); + + setSecondaryDimension(aRet, + std::max(getSecondaryDimension(rReq.m_aMainGroupSize), + getSecondaryDimension(rReq.m_aSubGroupSize))); + + return aRet; +} + +static tools::Long getMaxNonOutlier(const std::vector<tools::Long> &rG, tools::Long nAvgDimension) +{ + tools::Long nMaxDimensionNonOutlier = 0; + for (auto const& nPrimaryChildDimension : rG) + { + if (nPrimaryChildDimension < nAvgDimension * 1.5) + { + nMaxDimensionNonOutlier = std::max(nPrimaryChildDimension, + nMaxDimensionNonOutlier); + } + } + return nMaxDimensionNonOutlier; +} + +static std::vector<tools::Long> setButtonSizes(const std::vector<tools::Long> &rG, + const std::vector<bool> &rNonHomogeneous, + tools::Long nAvgDimension, tools::Long nMaxNonOutlier, tools::Long nMinWidth) +{ + std::vector<tools::Long> aVec; + //set everything < 1.5 times the average to the same width, leave the + //outliers un-touched + std::vector<bool>::const_iterator aJ = rNonHomogeneous.begin(); + auto nNonOutlierWidth = std::max(nMaxNonOutlier, nMinWidth); + for (auto const& nPrimaryChildDimension : rG) + { + bool bNonHomogeneous = *aJ; + if (!bNonHomogeneous && nPrimaryChildDimension < nAvgDimension * 1.5) + { + aVec.push_back(nNonOutlierWidth); + } + else + { + aVec.push_back(std::max(nPrimaryChildDimension, nMinWidth)); + } + ++aJ; + } + return aVec; +} + +VclButtonBox::Requisition VclButtonBox::calculatePrimarySecondaryRequisitions() const +{ + Requisition aReq; + + Size aMainGroupSize(DEFAULT_CHILD_MIN_WIDTH, DEFAULT_CHILD_MIN_HEIGHT); //to-do, pull from theme + Size aSubGroupSize(DEFAULT_CHILD_MIN_WIDTH, DEFAULT_CHILD_MIN_HEIGHT); //to-do, pull from theme + + tools::Long nMinMainGroupPrimary = getPrimaryDimension(aMainGroupSize); + tools::Long nMinSubGroupPrimary = getPrimaryDimension(aSubGroupSize); + tools::Long nMainGroupSecondary = getSecondaryDimension(aMainGroupSize); + tools::Long nSubGroupSecondary = getSecondaryDimension(aSubGroupSize); + + bool bIgnoreSecondaryPacking = (m_eLayoutStyle == VclButtonBoxStyle::Spread || m_eLayoutStyle == VclButtonBoxStyle::Center); + + std::vector<tools::Long> aMainGroupSizes; + std::vector<bool> aMainGroupNonHomogeneous; + std::vector<tools::Long> aSubGroupSizes; + std::vector<bool> aSubGroupNonHomogeneous; + + for (const vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (!pChild->IsVisible()) + continue; + Size aChildSize = getLayoutRequisition(*pChild); + if (bIgnoreSecondaryPacking || !pChild->get_secondary()) + { + //set the max secondary dimension + nMainGroupSecondary = std::max(nMainGroupSecondary, getSecondaryDimension(aChildSize)); + //collect the primary dimensions + aMainGroupSizes.push_back(getPrimaryDimension(aChildSize)); + aMainGroupNonHomogeneous.push_back(pChild->get_non_homogeneous()); + } + else + { + nSubGroupSecondary = std::max(nSubGroupSecondary, getSecondaryDimension(aChildSize)); + aSubGroupSizes.push_back(getPrimaryDimension(aChildSize)); + aSubGroupNonHomogeneous.push_back(pChild->get_non_homogeneous()); + } + } + + if (m_bHomogeneous) + { + tools::Long nMaxMainDimension = aMainGroupSizes.empty() ? 0 : + *std::max_element(aMainGroupSizes.begin(), aMainGroupSizes.end()); + nMaxMainDimension = std::max(nMaxMainDimension, nMinMainGroupPrimary); + tools::Long nMaxSubDimension = aSubGroupSizes.empty() ? 0 : + *std::max_element(aSubGroupSizes.begin(), aSubGroupSizes.end()); + nMaxSubDimension = std::max(nMaxSubDimension, nMinSubGroupPrimary); + tools::Long nMaxDimension = std::max(nMaxMainDimension, nMaxSubDimension); + aReq.m_aMainGroupDimensions.resize(aMainGroupSizes.size(), nMaxDimension); + aReq.m_aSubGroupDimensions.resize(aSubGroupSizes.size(), nMaxDimension); + } + else + { + //Ideally set everything to the same size, but find outlier widgets + //that are way wider than the average and leave them + //at their natural size and set the remainder to share the + //max size of the remaining members of the buttonbox + tools::Long nAccDimension = std::accumulate(aMainGroupSizes.begin(), + aMainGroupSizes.end(), 0); + nAccDimension = std::accumulate(aSubGroupSizes.begin(), + aSubGroupSizes.end(), nAccDimension); + + size_t nTotalSize = aMainGroupSizes.size() + aSubGroupSizes.size(); + + tools::Long nAvgDimension = nTotalSize ? nAccDimension / nTotalSize : 0; + + tools::Long nMaxMainNonOutlier = getMaxNonOutlier(aMainGroupSizes, + nAvgDimension); + tools::Long nMaxSubNonOutlier = getMaxNonOutlier(aSubGroupSizes, + nAvgDimension); + tools::Long nMaxNonOutlier = std::max(nMaxMainNonOutlier, nMaxSubNonOutlier); + + aReq.m_aMainGroupDimensions = setButtonSizes(aMainGroupSizes, + aMainGroupNonHomogeneous, + nAvgDimension, nMaxNonOutlier, nMinMainGroupPrimary); + aReq.m_aSubGroupDimensions = setButtonSizes(aSubGroupSizes, + aSubGroupNonHomogeneous, + nAvgDimension, nMaxNonOutlier, nMinSubGroupPrimary); + } + + if (!aReq.m_aMainGroupDimensions.empty()) + { + setSecondaryDimension(aReq.m_aMainGroupSize, nMainGroupSecondary); + setPrimaryDimension(aReq.m_aMainGroupSize, + std::accumulate(aReq.m_aMainGroupDimensions.begin(), + aReq.m_aMainGroupDimensions.end(), 0)); + } + if (!aReq.m_aSubGroupDimensions.empty()) + { + setSecondaryDimension(aReq.m_aSubGroupSize, nSubGroupSecondary); + setPrimaryDimension(aReq.m_aSubGroupSize, + std::accumulate(aReq.m_aSubGroupDimensions.begin(), + aReq.m_aSubGroupDimensions.end(), 0)); + } + + return aReq; +} + +Size VclButtonBox::addSpacing(const Size &rSize, sal_uInt16 nVisibleChildren) const +{ + Size aRet; + + if (nVisibleChildren) + { + tools::Long nPrimaryDimension = getPrimaryDimension(rSize); + setPrimaryDimension(aRet, + nPrimaryDimension + m_nSpacing * (nVisibleChildren-1)); + setSecondaryDimension(aRet, getSecondaryDimension(rSize)); + } + + return aRet; +} + +Size VclButtonBox::calculateRequisition() const +{ + Requisition aReq(calculatePrimarySecondaryRequisitions()); + sal_uInt16 nVisibleChildren = aReq.m_aMainGroupDimensions.size() + + aReq.m_aSubGroupDimensions.size(); + return addSpacing(addReqGroups(aReq), nVisibleChildren); +} + +bool VclButtonBox::set_property(const OString &rKey, const OUString &rValue) +{ + if (rKey == "layout-style") + { + VclButtonBoxStyle eStyle = VclButtonBoxStyle::Default; + if (rValue == "spread") + eStyle = VclButtonBoxStyle::Spread; + else if (rValue == "edge") + eStyle = VclButtonBoxStyle::Edge; + else if (rValue == "start") + eStyle = VclButtonBoxStyle::Start; + else if (rValue == "end") + eStyle = VclButtonBoxStyle::End; + else if (rValue == "center") + eStyle = VclButtonBoxStyle::Center; + else + { + SAL_WARN("vcl.layout", "unknown layout style " << rValue); + } + m_eLayoutStyle = eStyle; + } + else + return VclBox::set_property(rKey, rValue); + return true; +} + +void VclButtonBox::setAllocation(const Size &rAllocation) +{ + Requisition aReq(calculatePrimarySecondaryRequisitions()); + + if (aReq.m_aMainGroupDimensions.empty() && aReq.m_aSubGroupDimensions.empty()) + return; + + tools::Long nAllocPrimaryDimension = getPrimaryDimension(rAllocation); + + Point aMainGroupPos, aOtherGroupPos; + int nSpacing = m_nSpacing; + + //To-Do, other layout styles + switch (m_eLayoutStyle) + { + case VclButtonBoxStyle::Start: + if (!aReq.m_aSubGroupDimensions.empty()) + { + tools::Long nOtherPrimaryDimension = getPrimaryDimension( + addSpacing(aReq.m_aSubGroupSize, aReq.m_aSubGroupDimensions.size())); + setPrimaryCoordinate(aOtherGroupPos, + nAllocPrimaryDimension - nOtherPrimaryDimension); + } + break; + case VclButtonBoxStyle::Spread: + if (!aReq.m_aMainGroupDimensions.empty()) + { + tools::Long nMainPrimaryDimension = getPrimaryDimension( + addSpacing(aReq.m_aMainGroupSize, aReq.m_aMainGroupDimensions.size())); + tools::Long nExtraSpace = nAllocPrimaryDimension - nMainPrimaryDimension; + nExtraSpace += (aReq.m_aMainGroupDimensions.size()-1) * nSpacing; + nSpacing = nExtraSpace/(aReq.m_aMainGroupDimensions.size()+1); + setPrimaryCoordinate(aMainGroupPos, nSpacing); + } + break; + case VclButtonBoxStyle::Center: + if (!aReq.m_aMainGroupDimensions.empty()) + { + tools::Long nMainPrimaryDimension = getPrimaryDimension( + addSpacing(aReq.m_aMainGroupSize, aReq.m_aMainGroupDimensions.size())); + tools::Long nExtraSpace = nAllocPrimaryDimension - nMainPrimaryDimension; + setPrimaryCoordinate(aMainGroupPos, nExtraSpace/2); + } + break; + default: + SAL_WARN("vcl.layout", "todo unimplemented layout style"); + [[fallthrough]]; + case VclButtonBoxStyle::Default: + case VclButtonBoxStyle::End: + if (!aReq.m_aMainGroupDimensions.empty()) + { + tools::Long nMainPrimaryDimension = getPrimaryDimension( + addSpacing(aReq.m_aMainGroupSize, aReq.m_aMainGroupDimensions.size())); + setPrimaryCoordinate(aMainGroupPos, + nAllocPrimaryDimension - nMainPrimaryDimension); + } + break; + } + + Size aChildSize; + setSecondaryDimension(aChildSize, getSecondaryDimension(rAllocation)); + + std::vector<tools::Long>::const_iterator aPrimaryI = aReq.m_aMainGroupDimensions.begin(); + std::vector<tools::Long>::const_iterator aSecondaryI = aReq.m_aSubGroupDimensions.begin(); + bool bIgnoreSecondaryPacking = (m_eLayoutStyle == VclButtonBoxStyle::Spread || m_eLayoutStyle == VclButtonBoxStyle::Center); + for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (!pChild->IsVisible()) + continue; + + if (bIgnoreSecondaryPacking || !pChild->get_secondary()) + { + tools::Long nMainGroupPrimaryDimension = *aPrimaryI++; + setPrimaryDimension(aChildSize, nMainGroupPrimaryDimension); + setLayoutAllocation(*pChild, aMainGroupPos, aChildSize); + tools::Long nPrimaryCoordinate = getPrimaryCoordinate(aMainGroupPos); + setPrimaryCoordinate(aMainGroupPos, nPrimaryCoordinate + nMainGroupPrimaryDimension + nSpacing); + } + else + { + tools::Long nSubGroupPrimaryDimension = *aSecondaryI++; + setPrimaryDimension(aChildSize, nSubGroupPrimaryDimension); + setLayoutAllocation(*pChild, aOtherGroupPos, aChildSize); + tools::Long nPrimaryCoordinate = getPrimaryCoordinate(aOtherGroupPos); + setPrimaryCoordinate(aOtherGroupPos, nPrimaryCoordinate + nSubGroupPrimaryDimension + nSpacing); + } + } +} + +void VclButtonBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + VclBox::DumpAsPropertyTree(rJsonWriter); + rJsonWriter.put("type", "buttonbox"); + + switch(m_eLayoutStyle) + { + case VclButtonBoxStyle::Default: + rJsonWriter.put("layoutstyle", "default"); + break; + + case VclButtonBoxStyle::Spread: + rJsonWriter.put("layoutstyle", "spread"); + break; + + case VclButtonBoxStyle::Edge: + rJsonWriter.put("layoutstyle", "edge"); + break; + + case VclButtonBoxStyle::Center: + rJsonWriter.put("layoutstyle", "center"); + break; + + case VclButtonBoxStyle::Start: + rJsonWriter.put("layoutstyle", "start"); + break; + + case VclButtonBoxStyle::End: + rJsonWriter.put("layoutstyle", "end"); + break; + } +} + +namespace { + +struct ButtonOrder +{ + std::u16string_view m_aType; + int m_nPriority; +}; + +} + +static int getButtonPriority(std::u16string_view rType) +{ + static const size_t N_TYPES = 6; + static const ButtonOrder aDiscardCancelSave[N_TYPES] = + { + { u"discard", 0 }, + { u"cancel", 1 }, + { u"no", 2 }, + { u"save", 3 }, + { u"yes", 3 }, + { u"ok", 3 } + }; + + static const ButtonOrder aSaveDiscardCancel[N_TYPES] = + { + { u"save", 0 }, + { u"yes", 0 }, + { u"ok", 0 }, + { u"discard", 1 }, + { u"no", 1 }, + { u"cancel", 2 } + }; + + const ButtonOrder* pOrder = &aDiscardCancelSave[0]; + + const OUString &rEnv = Application::GetDesktopEnvironment(); + + if (rEnv.equalsIgnoreAsciiCase("windows") || + rEnv.equalsIgnoreAsciiCase("lxqt") || + rEnv.startsWithIgnoreAsciiCase("plasma")) + { + pOrder = &aSaveDiscardCancel[0]; + } + + for (size_t i = 0; i < N_TYPES; ++i, ++pOrder) + { + if (rType == pOrder->m_aType) + return pOrder->m_nPriority; + } + + return -1; +} + +namespace { + +class sortButtons +{ + bool m_bVerticalContainer; +public: + explicit sortButtons(bool bVerticalContainer) + : m_bVerticalContainer(bVerticalContainer) + { + } + bool operator()(const vcl::Window *pA, const vcl::Window *pB) const; +}; + +} + +bool sortButtons::operator()(const vcl::Window *pA, const vcl::Window *pB) const +{ + //sort into two groups of pack start and pack end + VclPackType ePackA = pA->get_pack_type(); + VclPackType ePackB = pB->get_pack_type(); + if (ePackA < ePackB) + return true; + if (ePackA > ePackB) + return false; + bool bPackA = pA->get_secondary(); + bool bPackB = pB->get_secondary(); + if (!m_bVerticalContainer) + { + //for horizontal boxes group secondaries before primaries + if (bPackA > bPackB) + return true; + if (bPackA < bPackB) + return false; + } + else + { + //for vertical boxes group secondaries after primaries + if (bPackA < bPackB) + return true; + if (bPackA > bPackB) + return false; + } + + //now order within groups according to platform rules + return getButtonPriority(pA->get_id()) < getButtonPriority(pB->get_id()); +} + +void sort_native_button_order(const VclBox& rContainer) +{ + std::vector<vcl::Window*> aChilds; + for (vcl::Window* pChild = rContainer.GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + aChilds.push_back(pChild); + } + + //sort child order within parent so that we match the platform + //button order + std::stable_sort(aChilds.begin(), aChilds.end(), sortButtons(rContainer.get_orientation())); + BuilderUtils::reorderWithinParent(aChilds, true); +} + +namespace { + +struct GridEntry +{ + VclPtr<vcl::Window> pChild; + sal_Int32 nSpanWidth; + sal_Int32 nSpanHeight; + int x; + int y; + GridEntry() + : pChild(nullptr) + , nSpanWidth(0) + , nSpanHeight(0) + , x(-1) + , y(-1) + { + } +}; + +} + +typedef boost::multi_array<GridEntry, 2> array_type; + +static array_type assembleGrid(const VclGrid &rGrid); +static bool isNullGrid(const array_type& A); +static void calcMaxs(const array_type &A, std::vector<VclGrid::Value> &rWidths, std::vector<VclGrid::Value> &rHeights); + +array_type assembleGrid(const VclGrid &rGrid) +{ + array_type A; + + for (vcl::Window* pChild = rGrid.GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + sal_Int32 nLeftAttach = std::max<sal_Int32>(pChild->get_grid_left_attach(), 0); + sal_Int32 nWidth = pChild->get_grid_width(); + sal_Int32 nMaxXPos = nLeftAttach+nWidth-1; + + sal_Int32 nTopAttach = std::max<sal_Int32>(pChild->get_grid_top_attach(), 0); + sal_Int32 nHeight = pChild->get_grid_height(); + sal_Int32 nMaxYPos = nTopAttach+nHeight-1; + + sal_Int32 nCurrentMaxXPos = A.shape()[0]-1; + sal_Int32 nCurrentMaxYPos = A.shape()[1]-1; + if (nMaxXPos > nCurrentMaxXPos || nMaxYPos > nCurrentMaxYPos) + { + nCurrentMaxXPos = std::max(nMaxXPos, nCurrentMaxXPos); + nCurrentMaxYPos = std::max(nMaxYPos, nCurrentMaxYPos); + A.resize(boost::extents[nCurrentMaxXPos+1][nCurrentMaxYPos+1]); + } + + GridEntry &rEntry = A[nLeftAttach][nTopAttach]; + rEntry.pChild = pChild; + rEntry.nSpanWidth = nWidth; + rEntry.nSpanHeight = nHeight; + rEntry.x = nLeftAttach; + rEntry.y = nTopAttach; + + for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX) + { + for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY) + { + GridEntry &rSpan = A[nLeftAttach+nSpanX][nTopAttach+nSpanY]; + rSpan.x = nLeftAttach; + rSpan.y = nTopAttach; + } + } + } + + //see if we have any empty rows/cols + sal_Int32 nMaxX = A.shape()[0]; + sal_Int32 nMaxY = A.shape()[1]; + + std::vector<bool> aNonEmptyCols(nMaxX); + std::vector<bool> aNonEmptyRows(nMaxY); + + for (sal_Int32 x = 0; x < nMaxX; ++x) + { + for (sal_Int32 y = 0; y < nMaxY; ++y) + { + const GridEntry &rEntry = A[x][y]; + const vcl::Window *pChild = rEntry.pChild; + if (pChild && pChild->IsVisible()) + { + aNonEmptyCols[x] = true; + if (rGrid.get_column_homogeneous()) + { + for (sal_Int32 nSpanX = 1; nSpanX < rEntry.nSpanWidth; ++nSpanX) + aNonEmptyCols[x+nSpanX] = true; + } + aNonEmptyRows[y] = true; + if (rGrid.get_row_homogeneous()) + { + for (sal_Int32 nSpanY = 1; nSpanY < rEntry.nSpanHeight; ++nSpanY) + aNonEmptyRows[y+nSpanY] = true; + } + } + } + } + + if (!rGrid.get_column_homogeneous()) + { + //reduce the spans of elements that span empty columns + for (sal_Int32 x = 0; x < nMaxX; ++x) + { + std::set<GridEntry*> candidates; + for (sal_Int32 y = 0; y < nMaxY; ++y) + { + if (aNonEmptyCols[x]) + continue; + GridEntry &rSpan = A[x][y]; + //cell x/y is spanned by the widget at cell rSpan.x/rSpan.y, + //just points back to itself if there's no cell spanning + if ((rSpan.x == -1) || (rSpan.y == -1)) + { + //there is no entry for this cell, i.e. this is a cell + //with no widget in it, or spanned by any other widget + continue; + } + GridEntry &rEntry = A[rSpan.x][rSpan.y]; + candidates.insert(&rEntry); + } + for (auto const& candidate : candidates) + { + GridEntry *pEntry = candidate; + --pEntry->nSpanWidth; + } + } + } + + if (!rGrid.get_row_homogeneous()) + { + //reduce the spans of elements that span empty rows + for (sal_Int32 y = 0; y < nMaxY; ++y) + { + std::set<GridEntry*> candidates; + for (sal_Int32 x = 0; x < nMaxX; ++x) + { + if (aNonEmptyRows[y]) + continue; + GridEntry &rSpan = A[x][y]; + //cell x/y is spanned by the widget at cell rSpan.x/rSpan.y, + //just points back to itself if there's no cell spanning + if ((rSpan.x == -1) || (rSpan.y == -1)) + { + //there is no entry for this cell, i.e. this is a cell + //with no widget in it, or spanned by any other widget + continue; + } + GridEntry &rEntry = A[rSpan.x][rSpan.y]; + candidates.insert(&rEntry); + } + for (auto const& candidate : candidates) + { + GridEntry *pEntry = candidate; + --pEntry->nSpanHeight; + } + } + } + + sal_Int32 nNonEmptyCols = std::count(aNonEmptyCols.begin(), aNonEmptyCols.end(), true); + sal_Int32 nNonEmptyRows = std::count(aNonEmptyRows.begin(), aNonEmptyRows.end(), true); + + //make new grid without empty rows and columns + array_type B(boost::extents[nNonEmptyCols][nNonEmptyRows]); + for (sal_Int32 x = 0, x2 = 0; x < nMaxX; ++x) + { + if (!aNonEmptyCols[x]) + continue; + for (sal_Int32 y = 0, y2 = 0; y < nMaxY; ++y) + { + if (!aNonEmptyRows[y]) + continue; + GridEntry &rEntry = A[x][y]; + B[x2][y2++] = rEntry; + } + ++x2; + } + + return B; +} + +static bool isNullGrid(const array_type &A) +{ + sal_Int32 nMaxX = A.shape()[0]; + sal_Int32 nMaxY = A.shape()[1]; + + return !nMaxX || !nMaxY; +} + +static void calcMaxs(const array_type &A, std::vector<VclGrid::Value> &rWidths, std::vector<VclGrid::Value> &rHeights) +{ + sal_Int32 nMaxX = A.shape()[0]; + sal_Int32 nMaxY = A.shape()[1]; + + rWidths.resize(nMaxX); + rHeights.resize(nMaxY); + + //first use the non spanning entries to set default width/heights + for (sal_Int32 x = 0; x < nMaxX; ++x) + { + for (sal_Int32 y = 0; y < nMaxY; ++y) + { + const GridEntry &rEntry = A[x][y]; + const vcl::Window *pChild = rEntry.pChild; + if (!pChild || !pChild->IsVisible()) + continue; + + sal_Int32 nWidth = rEntry.nSpanWidth; + sal_Int32 nHeight = rEntry.nSpanHeight; + + for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX) + rWidths[x+nSpanX].m_bExpand |= pChild->get_hexpand(); + + for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY) + rHeights[y+nSpanY].m_bExpand |= pChild->get_vexpand(); + + if (nWidth == 1 || nHeight == 1) + { + Size aChildSize = VclContainer::getLayoutRequisition(*pChild); + if (nWidth == 1) + rWidths[x].m_nValue = std::max(rWidths[x].m_nValue, aChildSize.Width()); + if (nHeight == 1) + rHeights[y].m_nValue = std::max(rHeights[y].m_nValue, aChildSize.Height()); + } + } + } + + //now use the spanning entries and split any extra sizes across expanding rows/cols + //where possible + for (sal_Int32 x = 0; x < nMaxX; ++x) + { + for (sal_Int32 y = 0; y < nMaxY; ++y) + { + const GridEntry &rEntry = A[x][y]; + const vcl::Window *pChild = rEntry.pChild; + if (!pChild || !pChild->IsVisible()) + continue; + + sal_Int32 nWidth = rEntry.nSpanWidth; + sal_Int32 nHeight = rEntry.nSpanHeight; + + if (nWidth == 1 && nHeight == 1) + continue; + + Size aChildSize = VclContainer::getLayoutRequisition(*pChild); + + if (nWidth > 1) + { + sal_Int32 nExistingWidth = 0; + for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX) + nExistingWidth += rWidths[x+nSpanX].m_nValue; + + sal_Int32 nExtraWidth = aChildSize.Width() - nExistingWidth; + + if (nExtraWidth > 0) + { + bool bForceExpandAll = false; + sal_Int32 nExpandables = 0; + for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX) + if (rWidths[x+nSpanX].m_bExpand) + ++nExpandables; + if (nExpandables == 0) + { + nExpandables = nWidth; + bForceExpandAll = true; + } + + for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX) + { + if (rWidths[x+nSpanX].m_bExpand || bForceExpandAll) + rWidths[x+nSpanX].m_nValue += nExtraWidth/nExpandables; + } + } + } + + if (nHeight > 1) + { + sal_Int32 nExistingHeight = 0; + for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY) + nExistingHeight += rHeights[y+nSpanY].m_nValue; + + sal_Int32 nExtraHeight = aChildSize.Height() - nExistingHeight; + + if (nExtraHeight > 0) + { + bool bForceExpandAll = false; + sal_Int32 nExpandables = 0; + for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY) + if (rHeights[y+nSpanY].m_bExpand) + ++nExpandables; + if (nExpandables == 0) + { + nExpandables = nHeight; + bForceExpandAll = true; + } + + for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY) + { + if (rHeights[y+nSpanY].m_bExpand || bForceExpandAll) + rHeights[y+nSpanY].m_nValue += nExtraHeight/nExpandables; + } + } + } + } + } +} + +static bool compareValues(const VclGrid::Value &i, const VclGrid::Value &j) +{ + return i.m_nValue < j.m_nValue; +} + +static VclGrid::Value accumulateValues(const VclGrid::Value &i, const VclGrid::Value &j) +{ + VclGrid::Value aRet; + aRet.m_nValue = i.m_nValue + j.m_nValue; + aRet.m_bExpand = i.m_bExpand || j.m_bExpand; + return aRet; +} + +Size VclGrid::calculateRequisition() const +{ + return calculateRequisitionForSpacings(get_row_spacing(), get_column_spacing()); +} + +Size VclGrid::calculateRequisitionForSpacings(sal_Int32 nRowSpacing, sal_Int32 nColSpacing) const +{ + array_type A = assembleGrid(*this); + + if (isNullGrid(A)) + return Size(); + + std::vector<Value> aWidths; + std::vector<Value> aHeights; + calcMaxs(A, aWidths, aHeights); + + tools::Long nTotalWidth = 0; + if (get_column_homogeneous()) + { + nTotalWidth = std::max_element(aWidths.begin(), aWidths.end(), compareValues)->m_nValue; + nTotalWidth *= aWidths.size(); + } + else + { + nTotalWidth = std::accumulate(aWidths.begin(), aWidths.end(), Value(), accumulateValues).m_nValue; + } + + nTotalWidth += nColSpacing * (aWidths.size()-1); + + tools::Long nTotalHeight = 0; + if (get_row_homogeneous()) + { + nTotalHeight = std::max_element(aHeights.begin(), aHeights.end(), compareValues)->m_nValue; + nTotalHeight *= aHeights.size(); + } + else + { + nTotalHeight = std::accumulate(aHeights.begin(), aHeights.end(), Value(), accumulateValues).m_nValue; + } + + nTotalHeight += nRowSpacing * (aHeights.size()-1); + + return Size(nTotalWidth, nTotalHeight); +} + +void VclGrid::setAllocation(const Size& rAllocation) +{ + array_type A = assembleGrid(*this); + + if (isNullGrid(A)) + return; + + sal_Int32 nMaxX = A.shape()[0]; + sal_Int32 nMaxY = A.shape()[1]; + + Size aRequisition; + std::vector<Value> aWidths(nMaxX); + std::vector<Value> aHeights(nMaxY); + if (!get_column_homogeneous() || !get_row_homogeneous()) + { + aRequisition = calculateRequisition(); + calcMaxs(A, aWidths, aHeights); + } + + sal_Int32 nColSpacing(get_column_spacing()); + sal_Int32 nRowSpacing(get_row_spacing()); + + tools::Long nAvailableWidth = rAllocation.Width(); + if (nMaxX) + nAvailableWidth -= nColSpacing * (nMaxX - 1); + if (get_column_homogeneous()) + { + for (sal_Int32 x = 0; x < nMaxX; ++x) + aWidths[x].m_nValue = nAvailableWidth/nMaxX; + } + else if (rAllocation.Width() != aRequisition.Width()) + { + sal_Int32 nExpandables = 0; + for (sal_Int32 x = 0; x < nMaxX; ++x) + if (aWidths[x].m_bExpand) + ++nExpandables; + tools::Long nExtraWidthForExpanders = nExpandables ? (rAllocation.Width() - aRequisition.Width()) / nExpandables : 0; + + //We don't fit and there is no volunteer to be shrunk + if (!nExpandables && rAllocation.Width() < aRequisition.Width()) + { + //first reduce spacing + while (nColSpacing) + { + nColSpacing /= 2; + aRequisition = calculateRequisitionForSpacings(nRowSpacing, nColSpacing); + if (aRequisition.Width() <= rAllocation.Width()) + break; + } + + //share out the remaining pain to everyone + tools::Long nExtraWidth = (rAllocation.Width() - aRequisition.Width()) / nMaxX; + + for (sal_Int32 x = 0; x < nMaxX; ++x) + aWidths[x].m_nValue += nExtraWidth; + } + + if (nExtraWidthForExpanders) + { + for (sal_Int32 x = 0; x < nMaxX; ++x) + if (aWidths[x].m_bExpand) + aWidths[x].m_nValue += nExtraWidthForExpanders; + } + } + + tools::Long nAvailableHeight = rAllocation.Height(); + if (nMaxY) + nAvailableHeight -= nRowSpacing * (nMaxY - 1); + if (get_row_homogeneous()) + { + for (sal_Int32 y = 0; y < nMaxY; ++y) + aHeights[y].m_nValue = nAvailableHeight/nMaxY; + } + else if (rAllocation.Height() != aRequisition.Height()) + { + sal_Int32 nExpandables = 0; + for (sal_Int32 y = 0; y < nMaxY; ++y) + if (aHeights[y].m_bExpand) + ++nExpandables; + tools::Long nExtraHeightForExpanders = nExpandables ? (rAllocation.Height() - aRequisition.Height()) / nExpandables : 0; + + //We don't fit and there is no volunteer to be shrunk + if (!nExpandables && rAllocation.Height() < aRequisition.Height()) + { + //first reduce spacing + while (nRowSpacing) + { + nRowSpacing /= 2; + aRequisition = calculateRequisitionForSpacings(nRowSpacing, nColSpacing); + if (aRequisition.Height() <= rAllocation.Height()) + break; + } + + //share out the remaining pain to everyone + tools::Long nExtraHeight = (rAllocation.Height() - aRequisition.Height()) / nMaxY; + + for (sal_Int32 y = 0; y < nMaxY; ++y) + aHeights[y].m_nValue += nExtraHeight; + } + + if (nExtraHeightForExpanders) + { + for (sal_Int32 y = 0; y < nMaxY; ++y) + if (aHeights[y].m_bExpand) + aHeights[y].m_nValue += nExtraHeightForExpanders; + } + } + + Point aAllocPos(0, 0); + for (sal_Int32 x = 0; x < nMaxX; ++x) + { + for (sal_Int32 y = 0; y < nMaxY; ++y) + { + GridEntry &rEntry = A[x][y]; + vcl::Window *pChild = rEntry.pChild; + if (pChild) + { + Size aChildAlloc(0, 0); + + sal_Int32 nWidth = rEntry.nSpanWidth; + for (sal_Int32 nSpanX = 0; nSpanX < nWidth; ++nSpanX) + aChildAlloc.AdjustWidth(aWidths[x+nSpanX].m_nValue ); + aChildAlloc.AdjustWidth(nColSpacing*(nWidth-1) ); + + sal_Int32 nHeight = rEntry.nSpanHeight; + for (sal_Int32 nSpanY = 0; nSpanY < nHeight; ++nSpanY) + aChildAlloc.AdjustHeight(aHeights[y+nSpanY].m_nValue ); + aChildAlloc.AdjustHeight(nRowSpacing*(nHeight-1) ); + + setLayoutAllocation(*pChild, aAllocPos, aChildAlloc); + } + aAllocPos.AdjustY(aHeights[y].m_nValue + nRowSpacing ); + } + aAllocPos.AdjustX(aWidths[x].m_nValue + nColSpacing ); + aAllocPos.setY( 0 ); + } +} + +void VclGrid::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + VclContainer::DumpAsPropertyTree(rJsonWriter); + rJsonWriter.put("type", "grid"); +} + +bool toBool(std::u16string_view rValue) +{ + return (!rValue.empty() && (rValue[0] == 't' || rValue[0] == 'T' || rValue[0] == '1')); +} + +bool VclGrid::set_property(const OString &rKey, const OUString &rValue) +{ + if (rKey == "row-spacing") + set_row_spacing(rValue.toInt32()); + else if (rKey == "column-spacing") + set_column_spacing(rValue.toInt32()); + else if (rKey == "row-homogeneous") + m_bRowHomogeneous = toBool(rValue); + else if (rKey == "column-homogeneous") + m_bColumnHomogeneous = toBool(rValue); + else if (rKey == "n-rows") + /*nothing to do*/; + else + return VclContainer::set_property(rKey, rValue); + return true; +} + +const vcl::Window *VclBin::get_child() const +{ + const WindowImpl* pWindowImpl = ImplGetWindowImpl(); + + return pWindowImpl->mpFirstChild; +} + +vcl::Window *VclBin::get_child() +{ + return const_cast<vcl::Window*>(const_cast<const VclBin*>(this)->get_child()); +} + +Size VclBin::calculateRequisition() const +{ + const vcl::Window *pChild = get_child(); + if (pChild && pChild->IsVisible()) + return getLayoutRequisition(*pChild); + return Size(0, 0); +} + +void VclBin::setAllocation(const Size &rAllocation) +{ + vcl::Window *pChild = get_child(); + if (pChild && pChild->IsVisible()) + setLayoutAllocation(*pChild, Point(0, 0), rAllocation); +} + +VclFrame::~VclFrame() +{ + disposeOnce(); +} + +void VclFrame::dispose() +{ + m_pLabel.clear(); + VclBin::dispose(); +} + +//To-Do, hook a DecorationView into VclFrame ? + +Size VclFrame::calculateRequisition() const +{ + Size aRet(0, 0); + + const vcl::Window *pChild = get_child(); + const vcl::Window *pLabel = get_label_widget(); + + if (pChild && pChild->IsVisible()) + aRet = getLayoutRequisition(*pChild); + + if (pLabel && pLabel->IsVisible()) + { + Size aLabelSize = getLayoutRequisition(*pLabel); + aRet.AdjustHeight(aLabelSize.Height() ); + aRet.setWidth( std::max(aLabelSize.Width(), aRet.Width()) ); + } + + return aRet; +} + +void VclFrame::setAllocation(const Size &rAllocation) +{ + //SetBackground( Color(0xFF, 0x00, 0xFF) ); + + Size aAllocation(rAllocation); + Point aChildPos; + + vcl::Window *pChild = get_child(); + vcl::Window *pLabel = get_label_widget(); + + if (pLabel && pLabel->IsVisible()) + { + Size aLabelSize = getLayoutRequisition(*pLabel); + aLabelSize.setHeight( std::min(aLabelSize.Height(), aAllocation.Height()) ); + aLabelSize.setWidth( std::min(aLabelSize.Width(), aAllocation.Width()) ); + setLayoutAllocation(*pLabel, aChildPos, aLabelSize); + aAllocation.AdjustHeight( -(aLabelSize.Height()) ); + aChildPos.AdjustY(aLabelSize.Height() ); + } + + if (pChild && pChild->IsVisible()) + setLayoutAllocation(*pChild, aChildPos, aAllocation); +} + +IMPL_LINK(VclFrame, WindowEventListener, VclWindowEvent&, rEvent, void) +{ + if (rEvent.GetId() == VclEventId::ObjectDying) + designate_label(nullptr); +} + +void VclFrame::designate_label(vcl::Window *pWindow) +{ + assert(!pWindow || pWindow->GetParent() == this); + if (m_pLabel) + m_pLabel->RemoveEventListener(LINK(this, VclFrame, WindowEventListener)); + m_pLabel = pWindow; + if (m_pLabel) + m_pLabel->AddEventListener(LINK(this, VclFrame, WindowEventListener)); +} + +const vcl::Window *VclFrame::get_label_widget() const +{ + if (m_pLabel) + return m_pLabel; + assert(GetChildCount() <= 2); + //The label widget is normally the first (of two) children + const WindowImpl* pWindowImpl = ImplGetWindowImpl(); + if (pWindowImpl->mpFirstChild == pWindowImpl->mpLastChild) //no label exists + return nullptr; + return pWindowImpl->mpFirstChild; +} + +vcl::Window *VclFrame::get_label_widget() +{ + return const_cast<vcl::Window*>(const_cast<const VclFrame*>(this)->get_label_widget()); +} + +const vcl::Window *VclFrame::get_child() const +{ + //The child widget is the normally the last (of two) children + const WindowImpl* pWindowImpl = ImplGetWindowImpl(); + assert(GetChildCount() == 2 || pWindowImpl->mbInDispose); + if (!m_pLabel) + return pWindowImpl->mpLastChild; + if (pWindowImpl->mpFirstChild == pWindowImpl->mpLastChild) //only label exists + return nullptr; + return pWindowImpl->mpLastChild; +} + +vcl::Window *VclFrame::get_child() +{ + return const_cast<vcl::Window*>(const_cast<const VclFrame*>(this)->get_child()); +} + +void VclFrame::set_label(const OUString &rLabel) +{ + vcl::Window *pLabel = get_label_widget(); + assert(pLabel); + pLabel->SetText(rLabel); +} + +OUString VclFrame::get_label() const +{ + const vcl::Window *pLabel = get_label_widget(); + assert(pLabel); + return pLabel->GetText(); +} + +OUString VclFrame::getDefaultAccessibleName() const +{ + const vcl::Window *pLabel = get_label_widget(); + if (pLabel) + return pLabel->GetAccessibleName(); + return VclBin::getDefaultAccessibleName(); +} + +void VclFrame::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + VclBin::DumpAsPropertyTree(rJsonWriter); + rJsonWriter.put("type", "frame"); +} + +class DisclosureButton final : public CheckBox +{ + virtual void ImplDrawCheckBoxState(vcl::RenderContext& rRenderContext) override + { + /* HACK: DisclosureButton is currently assuming, that the disclosure sign + will fit into the rectangle occupied by a normal checkbox on all themes. + If this does not hold true for some theme, ImplGetCheckImageSize + would have to be overridden for DisclosureButton; also GetNativeControlRegion + for ControlType::ListNode would have to be implemented and taken into account + */ + + tools::Rectangle aStateRect(GetStateRect()); + + ImplControlValue aControlValue(GetState() == TRISTATE_TRUE ? ButtonValue::On : ButtonValue::Off); + tools::Rectangle aCtrlRegion(aStateRect); + ControlState nState = ControlState::NONE; + + if (HasFocus()) + nState |= ControlState::FOCUSED; + if (GetButtonState() & DrawButtonFlags::Default) + nState |= ControlState::DEFAULT; + if (Window::IsEnabled()) + nState |= ControlState::ENABLED; + if (IsMouseOver() && GetMouseRect().Contains(GetPointerPosPixel())) + nState |= ControlState::ROLLOVER; + + if (rRenderContext.DrawNativeControl(ControlType::ListNode, ControlPart::Entire, aCtrlRegion, + nState, aControlValue, OUString())) + return; + + ImplSVCtrlData& rCtrlData(ImplGetSVData()->maCtrlData); + if (!rCtrlData.mpDisclosurePlus) + rCtrlData.mpDisclosurePlus.reset(new Image(StockImage::Yes, SV_DISCLOSURE_PLUS)); + if (!rCtrlData.mpDisclosureMinus) + rCtrlData.mpDisclosureMinus.reset(new Image(StockImage::Yes, SV_DISCLOSURE_MINUS)); + + Image* pImg + = IsChecked() ? rCtrlData.mpDisclosureMinus.get() : rCtrlData.mpDisclosurePlus.get(); + + DrawImageFlags nStyle = DrawImageFlags::NONE; + if (!IsEnabled()) + nStyle |= DrawImageFlags::Disable; + + Size aSize(aStateRect.GetSize()); + Size aImgSize(pImg->GetSizePixel()); + Point aOff((aSize.Width() - aImgSize.Width()) / 2, + (aSize.Height() - aImgSize.Height()) / 2); + aOff += aStateRect.TopLeft(); + rRenderContext.DrawImage(aOff, *pImg, nStyle); + } + +public: + explicit DisclosureButton(vcl::Window* pParent) + : CheckBox(pParent, 0) + { + } + + virtual void KeyInput( const KeyEvent& rKEvt ) override + { + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + + if( !aKeyCode.GetModifier() && + ( ( aKeyCode.GetCode() == KEY_ADD ) || + ( aKeyCode.GetCode() == KEY_SUBTRACT ) ) + ) + { + Check( aKeyCode.GetCode() == KEY_ADD ); + } + else + CheckBox::KeyInput( rKEvt ); + } +}; + +VclExpander::VclExpander(vcl::Window *pParent) + : VclBin(pParent) + , m_bResizeTopLevel(false) + , m_pDisclosureButton(VclPtr<DisclosureButton>::Create(this)) +{ + m_pDisclosureButton->SetToggleHdl(LINK(this, VclExpander, ClickHdl)); + m_pDisclosureButton->Show(); +} + +VclExpander::~VclExpander() +{ + disposeOnce(); +} + +bool VclExpander::get_expanded() const +{ + return m_pDisclosureButton->IsChecked(); +} + +void VclExpander::set_expanded(bool bExpanded) +{ + m_pDisclosureButton->Check(bExpanded); +} + +void VclExpander::set_label(const OUString& rLabel) +{ + m_pDisclosureButton->SetText(rLabel); +} + +OUString VclExpander::get_label() const +{ + return m_pDisclosureButton->GetText(); +} + +void VclExpander::dispose() +{ + m_pDisclosureButton.disposeAndClear(); + VclBin::dispose(); +} + +const vcl::Window *VclExpander::get_child() const +{ + const WindowImpl* pWindowImpl = ImplGetWindowImpl(); + + assert(pWindowImpl->mpFirstChild == m_pDisclosureButton); + + return pWindowImpl->mpFirstChild->GetWindow(GetWindowType::Next); +} + +vcl::Window *VclExpander::get_child() +{ + return const_cast<vcl::Window*>(const_cast<const VclExpander*>(this)->get_child()); +} + +Size VclExpander::calculateRequisition() const +{ + Size aRet(0, 0); + + WindowImpl* pWindowImpl = ImplGetWindowImpl(); + + const vcl::Window *pChild = get_child(); + const vcl::Window *pLabel = pChild != pWindowImpl->mpLastChild ? pWindowImpl->mpLastChild.get() : nullptr; + + if (pChild && pChild->IsVisible() && m_pDisclosureButton->IsChecked()) + aRet = getLayoutRequisition(*pChild); + + Size aExpanderSize = getLayoutRequisition(*m_pDisclosureButton); + + if (pLabel && pLabel->IsVisible()) + { + Size aLabelSize = getLayoutRequisition(*pLabel); + aExpanderSize.setHeight( std::max(aExpanderSize.Height(), aLabelSize.Height()) ); + aExpanderSize.AdjustWidth(aLabelSize.Width() ); + } + + aRet.AdjustHeight(aExpanderSize.Height() ); + aRet.setWidth( std::max(aExpanderSize.Width(), aRet.Width()) ); + + return aRet; +} + +void VclExpander::setAllocation(const Size &rAllocation) +{ + Size aAllocation(rAllocation); + Point aChildPos; + + WindowImpl* pWindowImpl = ImplGetWindowImpl(); + + //The label widget is the last (of two) children + vcl::Window *pChild = get_child(); + vcl::Window *pLabel = pChild != pWindowImpl->mpLastChild.get() ? pWindowImpl->mpLastChild.get() : nullptr; + + Size aButtonSize = getLayoutRequisition(*m_pDisclosureButton); + Size aLabelSize; + Size aExpanderSize = aButtonSize; + if (pLabel && pLabel->IsVisible()) + { + aLabelSize = getLayoutRequisition(*pLabel); + aExpanderSize.setHeight( std::max(aExpanderSize.Height(), aLabelSize.Height()) ); + aExpanderSize.AdjustWidth(aLabelSize.Width() ); + } + + aExpanderSize.setHeight( std::min(aExpanderSize.Height(), aAllocation.Height()) ); + aExpanderSize.setWidth( std::min(aExpanderSize.Width(), aAllocation.Width()) ); + + aButtonSize.setHeight( std::min(aButtonSize.Height(), aExpanderSize.Height()) ); + aButtonSize.setWidth( std::min(aButtonSize.Width(), aExpanderSize.Width()) ); + + tools::Long nExtraExpanderHeight = aExpanderSize.Height() - aButtonSize.Height(); + Point aButtonPos(aChildPos.X(), aChildPos.Y() + nExtraExpanderHeight/2); + setLayoutAllocation(*m_pDisclosureButton, aButtonPos, aButtonSize); + + if (pLabel && pLabel->IsVisible()) + { + aLabelSize.setHeight( std::min(aLabelSize.Height(), aExpanderSize.Height()) ); + aLabelSize.setWidth( std::min(aLabelSize.Width(), + aExpanderSize.Width() - aButtonSize.Width()) ); + + tools::Long nExtraLabelHeight = aExpanderSize.Height() - aLabelSize.Height(); + Point aLabelPos(aChildPos.X() + aButtonSize.Width(), aChildPos.Y() + nExtraLabelHeight/2); + setLayoutAllocation(*pLabel, aLabelPos, aLabelSize); + } + + aAllocation.AdjustHeight( -(aExpanderSize.Height()) ); + aChildPos.AdjustY(aExpanderSize.Height() ); + + if (pChild && pChild->IsVisible()) + { + if (!m_pDisclosureButton->IsChecked()) + aAllocation = Size(); + setLayoutAllocation(*pChild, aChildPos, aAllocation); + } +} + +bool VclExpander::set_property(const OString &rKey, const OUString &rValue) +{ + if (rKey == "expanded") + set_expanded(toBool(rValue)); + else if (rKey == "resize-toplevel") + m_bResizeTopLevel = toBool(rValue); + else + return VclBin::set_property(rKey, rValue); + return true; +} + +void VclExpander::StateChanged(StateChangedType nType) +{ + VclBin::StateChanged( nType ); + + if (nType == StateChangedType::InitShow) + { + vcl::Window *pChild = get_child(); + if (pChild) + pChild->Show(m_pDisclosureButton->IsChecked()); + } +} + +const vcl::Window *VclExpander::get_label_widget() const +{ + return m_pDisclosureButton; +} + +vcl::Window *VclExpander::get_label_widget() +{ + return const_cast<vcl::Window*>(const_cast<const VclExpander*>(this)->get_label_widget()); +} + +void VclExpander::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + VclContainer::DumpAsPropertyTree(rJsonWriter); + rJsonWriter.put("type", "expander"); +} + +FactoryFunction VclExpander::GetUITestFactory() const +{ + return ExpanderUIObject::create; +} + +IMPL_LINK( VclExpander, ClickHdl, CheckBox&, rBtn, void ) +{ + vcl::Window *pChild = get_child(); + if (pChild) + { + pChild->Show(rBtn.IsChecked()); + queue_resize(); + Dialog* pResizeDialog = m_bResizeTopLevel ? GetParentDialog() : nullptr; + if (pResizeDialog) + pResizeDialog->setOptimalLayoutSize(true); + } + maExpandedHdl.Call(*this); +} + +VclScrolledWindow::VclScrolledWindow(vcl::Window *pParent) + : VclBin(pParent, WB_HIDE | WB_CLIPCHILDREN | WB_AUTOHSCROLL | WB_AUTOVSCROLL | WB_TABSTOP) + , m_bUserManagedScrolling(false) + , m_eDrawFrameStyle(DrawFrameStyle::NONE) + , m_eDrawFrameFlags(DrawFrameFlags::WindowBorder) + , m_pVScroll(VclPtr<ScrollBar>::Create(this, WB_HIDE | WB_VERT)) + , m_pHScroll(VclPtr<ScrollBar>::Create(this, WB_HIDE | WB_HORZ)) + , m_aScrollBarBox(VclPtr<ScrollBarBox>::Create(this, WB_HIDE)) +{ + SetType(WindowType::SCROLLWINDOW); + + AllSettings aAllSettings = GetSettings(); + StyleSettings aStyle = aAllSettings.GetStyleSettings(); + aStyle.SetMonoColor(aStyle.GetShadowColor()); + aAllSettings.SetStyleSettings(aStyle); + GetOutDev()->SetSettings(aAllSettings); + + Link<ScrollBar*,void> aLink( LINK( this, VclScrolledWindow, ScrollBarHdl ) ); + m_pVScroll->SetScrollHdl(aLink); + m_pHScroll->SetScrollHdl(aLink); + + m_nBorderWidth = CalcBorderWidth(); +} + +int VclScrolledWindow::CalcBorderWidth() const +{ + if (m_eDrawFrameStyle == DrawFrameStyle::NONE) + return 0; + const tools::Rectangle aRect(tools::Rectangle(Point(0, 0), Size(100, 100))); + DecorationView aDecoView(const_cast<OutputDevice*>(GetOutDev())); + // don't actually draw anything, just measure what size it would be and the diff is the desired border size to reserve + const tools::Rectangle aContentRect = aDecoView.DrawFrame(aRect, m_eDrawFrameStyle, m_eDrawFrameFlags | DrawFrameFlags::NoDraw); + const auto nBorderWidth = (aRect.GetWidth() - aContentRect.GetWidth()) / 2; + return std::max<int>(nBorderWidth, 1); +} + +void VclScrolledWindow::dispose() +{ + m_pVScroll.disposeAndClear(); + m_pHScroll.disposeAndClear(); + m_aScrollBarBox.disposeAndClear(); + VclBin::dispose(); +} + +IMPL_LINK_NOARG(VclScrolledWindow, ScrollBarHdl, ScrollBar*, void) +{ + vcl::Window *pChild = get_child(); + if (!pChild) + return; + + assert(dynamic_cast<VclViewport*>(pChild) && "scrolledwindow child should be a Viewport"); + + pChild = pChild->GetWindow(GetWindowType::FirstChild); + + if (!pChild) + return; + + Point aWinPos(-m_pHScroll->GetThumbPos(), -m_pVScroll->GetThumbPos()); + pChild->SetPosPixel(aWinPos); +} + +const vcl::Window *VclScrolledWindow::get_child() const +{ + const WindowImpl* pWindowImpl = ImplGetWindowImpl(); + assert(GetChildCount() == 4 || pWindowImpl->mbInDispose); + return pWindowImpl->mpLastChild; +} + +vcl::Window *VclScrolledWindow::get_child() +{ + return const_cast<vcl::Window*>(const_cast<const VclScrolledWindow*>(this)->get_child()); +} + +Size VclScrolledWindow::calculateRequisition() const +{ + Size aRet(0, 0); + + const vcl::Window *pChild = get_child(); + if (pChild && pChild->IsVisible()) + aRet = getLayoutRequisition(*pChild); + + if (GetStyle() & WB_VSCROLL) + aRet.AdjustWidth(getLayoutRequisition(*m_pVScroll).Width() ); + + if (GetStyle() & WB_HSCROLL) + aRet.AdjustHeight(getLayoutRequisition(*m_pHScroll).Height() ); + + aRet.AdjustHeight(2 * m_nBorderWidth); + aRet.AdjustWidth(2 * m_nBorderWidth); + + return aRet; +} + +void VclScrolledWindow::InitScrollBars(const Size &rRequest) +{ + const vcl::Window *pChild = get_child(); + if (!pChild || !pChild->IsVisible()) + return; + + Size aOutSize(getVisibleChildSize()); + + m_pVScroll->SetRangeMax(rRequest.Height()); + m_pVScroll->SetVisibleSize(aOutSize.Height()); + m_pVScroll->SetPageSize(16); + + m_pHScroll->SetRangeMax(rRequest.Width()); + m_pHScroll->SetVisibleSize(aOutSize.Width()); + m_pHScroll->SetPageSize(16); + + m_pVScroll->Scroll(); + m_pHScroll->Scroll(); +} + +void VclScrolledWindow::doSetAllocation(const Size &rAllocation, bool bRetryOnFailure) +{ + Size aChildReq; + + vcl::Window *pChild = get_child(); + if (pChild && pChild->IsVisible()) + aChildReq = getLayoutRequisition(*pChild); + + tools::Long nAvailHeight = rAllocation.Height() - 2 * m_nBorderWidth; + tools::Long nAvailWidth = rAllocation.Width() - 2 * m_nBorderWidth; + + // vert. ScrollBar + bool bShowVScroll; + if (GetStyle() & WB_AUTOVSCROLL) + bShowVScroll = nAvailHeight < aChildReq.Height(); + else + bShowVScroll = (GetStyle() & WB_VSCROLL) != 0; + + if (bShowVScroll) + nAvailWidth -= getLayoutRequisition(*m_pVScroll).Width(); + + // horz. ScrollBar + bool bShowHScroll; + if (GetStyle() & WB_AUTOHSCROLL) + { + bShowHScroll = nAvailWidth < aChildReq.Width(); + + if (bShowHScroll) + nAvailHeight -= getLayoutRequisition(*m_pHScroll).Height(); + + if (GetStyle() & WB_AUTOVSCROLL) + bShowVScroll = nAvailHeight < aChildReq.Height(); + } + else + bShowHScroll = (GetStyle() & WB_HSCROLL) != 0; + + if (m_pHScroll->IsVisible() != bShowHScroll) + m_pHScroll->Show(bShowHScroll); + if (m_pVScroll->IsVisible() != bShowVScroll) + m_pVScroll->Show(bShowVScroll); + + Size aInnerSize(rAllocation); + aInnerSize.AdjustWidth(-2 * m_nBorderWidth); + aInnerSize.AdjustHeight(-2 * m_nBorderWidth); + + bool bBothVisible = m_pVScroll->IsVisible() && m_pHScroll->IsVisible(); + auto nScrollBarWidth = getLayoutRequisition(*m_pVScroll).Width(); + auto nScrollBarHeight = getLayoutRequisition(*m_pHScroll).Height(); + + if (m_pVScroll->IsVisible()) + { + Point aScrollPos(rAllocation.Width() - nScrollBarWidth - m_nBorderWidth, m_nBorderWidth); + Size aScrollSize(nScrollBarWidth, rAllocation.Height() - 2 * m_nBorderWidth); + if (bBothVisible) + aScrollSize.AdjustHeight(-nScrollBarHeight); + setLayoutAllocation(*m_pVScroll, aScrollPos, aScrollSize); + aInnerSize.AdjustWidth( -nScrollBarWidth ); + } + + if (m_pHScroll->IsVisible()) + { + Point aScrollPos(m_nBorderWidth, rAllocation.Height() - nScrollBarHeight); + Size aScrollSize(rAllocation.Width() - 2 * m_nBorderWidth, nScrollBarHeight); + if (bBothVisible) + aScrollSize.AdjustWidth(-nScrollBarWidth); + setLayoutAllocation(*m_pHScroll, aScrollPos, aScrollSize); + aInnerSize.AdjustHeight( -nScrollBarHeight ); + } + + if (bBothVisible) + { + Point aBoxPos(aInnerSize.Width() + m_nBorderWidth, aInnerSize.Height() + m_nBorderWidth); + m_aScrollBarBox->SetPosSizePixel(aBoxPos, Size(nScrollBarWidth, nScrollBarHeight)); + m_aScrollBarBox->Show(); + } + else + { + m_aScrollBarBox->Hide(); + } + + if (pChild && pChild->IsVisible()) + { + assert(dynamic_cast<VclViewport*>(pChild) && "scrolledwindow child should be a Viewport"); + + WinBits nOldBits = (GetStyle() & (WB_AUTOVSCROLL | WB_VSCROLL | WB_AUTOHSCROLL | WB_HSCROLL)); + + setLayoutAllocation(*pChild, Point(m_nBorderWidth, m_nBorderWidth), aInnerSize); + + // tdf#128758 if the layout allocation triggered some callback that + // immediately invalidates the layout by adding scrollbars then + // normally this would simply retrigger layout and another toplevel + // attempt is made later. But the initial layout attempt blocks + // relayouts, so just make another single effort here. + WinBits nNewBits = (GetStyle() & (WB_AUTOVSCROLL | WB_VSCROLL | WB_AUTOHSCROLL | WB_HSCROLL)); + if (nOldBits != nNewBits && bRetryOnFailure) + { + doSetAllocation(rAllocation, false); + return; + } + } + + if (!m_bUserManagedScrolling) + InitScrollBars(aChildReq); +} + +void VclScrolledWindow::setAllocation(const Size &rAllocation) +{ + doSetAllocation(rAllocation, true); +} + +Size VclScrolledWindow::getVisibleChildSize() const +{ + Size aRet(GetSizePixel()); + if (m_pVScroll->IsVisible()) + aRet.AdjustWidth( -(m_pVScroll->GetSizePixel().Width()) ); + if (m_pHScroll->IsVisible()) + aRet.AdjustHeight( -(m_pHScroll->GetSizePixel().Height()) ); + aRet.AdjustHeight(-2 * m_nBorderWidth); + aRet.AdjustWidth(-2 * m_nBorderWidth); + return aRet; +} + +bool VclScrolledWindow::set_property(const OString &rKey, const OUString &rValue) +{ + if (rKey == "shadow-type" || rKey == "name") + { + if (rKey == "shadow-type") + { + // despite the style names, this looks like the best mapping + if (rValue == "in") + m_eDrawFrameStyle = DrawFrameStyle::Out; + else if (rValue == "out") + m_eDrawFrameStyle = DrawFrameStyle::In; + else if (rValue == "etched-in") + m_eDrawFrameStyle = DrawFrameStyle::DoubleOut; + else if (rValue == "etched-out") + m_eDrawFrameStyle = DrawFrameStyle::DoubleIn; + else if (rValue == "none") + m_eDrawFrameStyle = DrawFrameStyle::NONE; + } + else if (rKey == "name") + { + m_eDrawFrameFlags = DrawFrameFlags::WindowBorder; + if (rValue == "monoborder") + m_eDrawFrameFlags |= DrawFrameFlags::Mono; + } + + auto nBorderWidth = CalcBorderWidth(); + if (m_nBorderWidth != nBorderWidth) + { + m_nBorderWidth = nBorderWidth; + queue_resize(); + } + + return true; + } + + bool bRet = VclBin::set_property(rKey, rValue); + m_pVScroll->Show((GetStyle() & WB_VSCROLL) != 0); + m_pHScroll->Show((GetStyle() & WB_HSCROLL) != 0); + return bRet; +} + +bool VclScrolledWindow::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 ) ) + { + // tdf#140537 only handle scroll commands in the valid shown scrollbars + bDone = HandleScrollCommand(rCEvt, + m_pHScroll->IsVisible() ? m_pHScroll : nullptr, + m_pVScroll->IsVisible() ? m_pVScroll : nullptr); + } + } + } + + return bDone || VclBin::EventNotify( rNEvt ); +} + +void VclScrolledWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + VclBin::Paint(rRenderContext, rRect); + if (m_eDrawFrameStyle == DrawFrameStyle::NONE) + return; + const tools::Rectangle aRect(tools::Rectangle(Point(0,0), GetSizePixel())); + DecorationView aDecoView(&rRenderContext); + const tools::Rectangle aContentRect = aDecoView.DrawFrame(aRect, m_eDrawFrameStyle, m_eDrawFrameFlags); + const auto nBorderWidth = (aRect.GetWidth() - aContentRect.GetWidth()) / 2; + SAL_WARN_IF(nBorderWidth > m_nBorderWidth, "vcl.layout", "desired border at paint " << + nBorderWidth << " is larger than expected " << m_nBorderWidth); +} + +void VclViewport::setAllocation(const Size &rAllocation) +{ + vcl::Window *pChild = get_child(); + if (!(pChild && pChild->IsVisible())) + return; + + Size aReq(getLayoutRequisition(*pChild)); + aReq.setWidth( std::max(aReq.Width(), rAllocation.Width()) ); + aReq.setHeight( std::max(aReq.Height(), rAllocation.Height()) ); + Point aKeepPos(pChild->GetPosPixel()); + if (m_bInitialAllocation) + { + aKeepPos = Point(0, 0); + m_bInitialAllocation = false; + } + setLayoutAllocation(*pChild, aKeepPos, aReq); +} + +const vcl::Window *VclEventBox::get_child() const +{ + const WindowImpl* pWindowImpl = ImplGetWindowImpl(); + + assert(pWindowImpl->mpFirstChild.get() == m_aEventBoxHelper.get()); + + return pWindowImpl->mpFirstChild->GetWindow(GetWindowType::Next); +} + +vcl::Window *VclEventBox::get_child() +{ + return const_cast<vcl::Window*>(const_cast<const VclEventBox*>(this)->get_child()); +} + +void VclEventBox::setAllocation(const Size& rAllocation) +{ + Point aChildPos(0, 0); + for (vcl::Window *pChild = GetWindow(GetWindowType::FirstChild); pChild; pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (!pChild->IsVisible()) + continue; + setLayoutAllocation(*pChild, aChildPos, rAllocation); + } +} + +Size VclEventBox::calculateRequisition() const +{ + Size aRet(0, 0); + + for (const vcl::Window* pChild = get_child(); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (!pChild->IsVisible()) + continue; + Size aChildSize = getLayoutRequisition(*pChild); + aRet.setWidth( std::max(aRet.Width(), aChildSize.Width()) ); + aRet.setHeight( std::max(aRet.Height(), aChildSize.Height()) ); + } + + return aRet; +} + +void VclEventBox::Command(const CommandEvent&) +{ + //discard events by default to block them reaching children +} + +VclEventBox::~VclEventBox() +{ + disposeOnce(); +} + +void VclEventBox::dispose() +{ + m_aEventBoxHelper.disposeAndClear(); + VclBin::dispose(); +} + +void VclSizeGroup::trigger_queue_resize() +{ + //sufficient to trigger one widget to trigger all of them + if (!m_aWindows.empty()) + { + (*m_aWindows.begin())->queue_resize(); + } +} + +void VclSizeGroup::set_ignore_hidden(bool bIgnoreHidden) +{ + if (bIgnoreHidden != m_bIgnoreHidden) + { + m_bIgnoreHidden = bIgnoreHidden; + trigger_queue_resize(); + } +} + +void VclSizeGroup::set_mode(VclSizeGroupMode eMode) +{ + if (eMode != m_eMode) + { + m_eMode = eMode; + trigger_queue_resize(); + } + +} + +void VclSizeGroup::set_property(const OString &rKey, const OUString &rValue) +{ + if (rKey == "ignore-hidden") + set_ignore_hidden(toBool(rValue)); + else if (rKey == "mode") + { + VclSizeGroupMode eMode = VclSizeGroupMode::Horizontal; + if (rValue == "none") + eMode = VclSizeGroupMode::NONE; + else if (rValue == "horizontal") + eMode = VclSizeGroupMode::Horizontal; + else if (rValue == "vertical") + eMode = VclSizeGroupMode::Vertical; + else if (rValue == "both") + eMode = VclSizeGroupMode::Both; + else + { + SAL_WARN("vcl.layout", "unknown size group mode" << rValue); + } + set_mode(eMode); + } + else + { + SAL_INFO("vcl.layout", "unhandled property: " << rKey); + } +} + +void MessageDialog::create_message_area() +{ + setDeferredProperties(); + + if (m_pGrid) + return; + + VclContainer *pContainer = get_content_area(); + assert(pContainer); + + m_pGrid.set( VclPtr<VclGrid>::Create(pContainer) ); + m_pGrid->reorderWithinParent(0); + m_pGrid->set_column_spacing(12); + m_pMessageBox.set(VclPtr<VclVBox>::Create(m_pGrid)); + m_pMessageBox->set_grid_left_attach(1); + m_pMessageBox->set_grid_top_attach(0); + m_pMessageBox->set_spacing(GetTextHeight()); + + m_pImage = VclPtr<FixedImage>::Create(m_pGrid, WB_CENTER | WB_VCENTER | WB_3DLOOK); + switch (m_eMessageType) + { + case VclMessageType::Info: + m_pImage->SetImage(GetStandardInfoBoxImage()); + break; + case VclMessageType::Warning: + m_pImage->SetImage(GetStandardWarningBoxImage()); + break; + case VclMessageType::Question: + m_pImage->SetImage(GetStandardQueryBoxImage()); + break; + case VclMessageType::Error: + m_pImage->SetImage(GetStandardErrorBoxImage()); + break; + case VclMessageType::Other: + break; + } + m_pImage->set_grid_left_attach(0); + m_pImage->set_grid_top_attach(0); + m_pImage->set_valign(VclAlign::Start); + m_pImage->Show(m_eMessageType != VclMessageType::Other); + + WinBits nWinStyle = WB_CLIPCHILDREN | WB_LEFT | WB_VCENTER | WB_NOLABEL | WB_NOTABSTOP; + + bool bHasSecondaryText = !m_sSecondaryString.isEmpty(); + + m_pPrimaryMessage = VclPtr<VclMultiLineEdit>::Create(m_pMessageBox, nWinStyle); + m_pPrimaryMessage->SetPaintTransparent(true); + m_pPrimaryMessage->EnableCursor(false); + + m_pPrimaryMessage->set_hexpand(true); + m_pPrimaryMessage->SetText(m_sPrimaryString); + m_pPrimaryMessage->Show(!m_sPrimaryString.isEmpty()); + + m_pSecondaryMessage = VclPtr<VclMultiLineEdit>::Create(m_pMessageBox, nWinStyle); + m_pSecondaryMessage->SetPaintTransparent(true); + m_pSecondaryMessage->EnableCursor(false); + m_pSecondaryMessage->set_hexpand(true); + m_pSecondaryMessage->SetText(m_sSecondaryString); + m_pSecondaryMessage->Show(bHasSecondaryText); + + MessageDialog::SetMessagesWidths(this, m_pPrimaryMessage, bHasSecondaryText ? m_pSecondaryMessage.get() : nullptr); + + VclButtonBox *pButtonBox = get_action_area(); + assert(pButtonBox); + + VclPtr<PushButton> pBtn; + short nDefaultResponse = get_default_response(); + switch (m_eButtonsType) + { + case VclButtonsType::NONE: + break; + case VclButtonsType::Ok: + pBtn.set( VclPtr<OKButton>::Create(pButtonBox) ); + pBtn->SetStyle(pBtn->GetStyle() & WB_DEFBUTTON); + pBtn->Show(); + pBtn->set_id("ok"); + add_button(pBtn, RET_OK, true); + nDefaultResponse = RET_OK; + break; + case VclButtonsType::Close: + pBtn.set( VclPtr<CloseButton>::Create(pButtonBox) ); + pBtn->SetStyle(pBtn->GetStyle() & WB_DEFBUTTON); + pBtn->Show(); + pBtn->set_id("close"); + add_button(pBtn, RET_CLOSE, true); + nDefaultResponse = RET_CLOSE; + break; + case VclButtonsType::Cancel: + pBtn.set( VclPtr<CancelButton>::Create(pButtonBox) ); + pBtn->SetStyle(pBtn->GetStyle() & WB_DEFBUTTON); + pBtn->Show(); + pBtn->set_id("cancel"); + add_button(pBtn, RET_CANCEL, true); + nDefaultResponse = RET_CANCEL; + break; + case VclButtonsType::YesNo: + pBtn = VclPtr<PushButton>::Create(pButtonBox); + pBtn->SetText(GetStandardText(StandardButtonType::Yes)); + pBtn->Show(); + pBtn->set_id("yes"); + add_button(pBtn, RET_YES, true); + + pBtn.set( VclPtr<PushButton>::Create(pButtonBox) ); + pBtn->SetText(GetStandardText(StandardButtonType::No)); + pBtn->Show(); + pBtn->set_id("no"); + add_button(pBtn, RET_NO, true); + nDefaultResponse = RET_NO; + break; + case VclButtonsType::OkCancel: + pBtn.set( VclPtr<OKButton>::Create(pButtonBox) ); + pBtn->Show(); + pBtn->set_id("ok"); + add_button(pBtn, RET_OK, true); + + pBtn.set( VclPtr<CancelButton>::Create(pButtonBox) ); + pBtn->Show(); + pBtn->set_id("cancel"); + add_button(pBtn, RET_CANCEL, true); + nDefaultResponse = RET_CANCEL; + break; + } + set_default_response(nDefaultResponse); + sort_native_button_order(*pButtonBox); + m_pMessageBox->Show(); + m_pGrid->Show(); +} + +void MessageDialog::create_owned_areas() +{ +#if defined _WIN32 + set_border_width(3); +#else + set_border_width(12); +#endif + m_pOwnedContentArea.set(VclPtr<VclVBox>::Create(this, false, 24)); + set_content_area(m_pOwnedContentArea); + m_pOwnedContentArea->Show(); + m_pOwnedActionArea.set( VclPtr<VclHButtonBox>::Create(m_pOwnedContentArea) ); + set_action_area(m_pOwnedActionArea); + m_pOwnedActionArea->Show(); +} + +MessageDialog::MessageDialog(vcl::Window* pParent, WinBits nStyle) + : Dialog(pParent, nStyle) + , m_eButtonsType(VclButtonsType::NONE) + , m_eMessageType(VclMessageType::Info) + , m_pOwnedContentArea(nullptr) + , m_pOwnedActionArea(nullptr) + , m_pGrid(nullptr) + , m_pMessageBox(nullptr) + , m_pImage(nullptr) + , m_pPrimaryMessage(nullptr) + , m_pSecondaryMessage(nullptr) +{ + SetType(WindowType::MESSBOX); +} + +MessageDialog::MessageDialog(vcl::Window* pParent, + const OUString &rMessage, + VclMessageType eMessageType, + VclButtonsType eButtonsType) + : Dialog(pParent, WB_MOVEABLE | WB_3DLOOK | WB_CLOSEABLE) + , m_eButtonsType(eButtonsType) + , m_eMessageType(eMessageType) + , m_pGrid(nullptr) + , m_pMessageBox(nullptr) + , m_pImage(nullptr) + , m_pPrimaryMessage(nullptr) + , m_pSecondaryMessage(nullptr) + , m_sPrimaryString(rMessage) +{ + SetType(WindowType::MESSBOX); + create_owned_areas(); + create_message_area(); + + switch (m_eMessageType) + { + case VclMessageType::Info: + SetText(GetStandardInfoBoxText()); + break; + case VclMessageType::Warning: + SetText(GetStandardWarningBoxText()); + break; + case VclMessageType::Question: + SetText(GetStandardQueryBoxText()); + break; + case VclMessageType::Error: + SetText(GetStandardErrorBoxText()); + break; + case VclMessageType::Other: + SetText(Application::GetDisplayName()); + break; + } +} + +void MessageDialog::dispose() +{ + disposeOwnedButtons(); + m_pPrimaryMessage.disposeAndClear(); + m_pSecondaryMessage.disposeAndClear(); + m_pImage.disposeAndClear(); + m_pMessageBox.disposeAndClear(); + m_pGrid.disposeAndClear(); + m_pOwnedActionArea.disposeAndClear(); + m_pOwnedContentArea.disposeAndClear(); + Dialog::dispose(); +} + +MessageDialog::~MessageDialog() +{ + disposeOnce(); +} + +void MessageDialog::SetMessagesWidths(vcl::Window const *pParent, + VclMultiLineEdit *pPrimaryMessage, VclMultiLineEdit *pSecondaryMessage) +{ + if (pSecondaryMessage) + { + assert(pPrimaryMessage); + vcl::Font aFont = pParent->GetSettings().GetStyleSettings().GetLabelFont(); + aFont.SetFontSize(Size(0, aFont.GetFontSize().Height() * 1.2)); + aFont.SetWeight(WEIGHT_BOLD); + pPrimaryMessage->SetControlFont(aFont); + pPrimaryMessage->SetMaxTextWidth(pPrimaryMessage->approximate_char_width() * 44); + pSecondaryMessage->SetMaxTextWidth(pSecondaryMessage->approximate_char_width() * 60); + } + else + pPrimaryMessage->SetMaxTextWidth(pPrimaryMessage->approximate_char_width() * 60); +} + +OUString const & MessageDialog::get_primary_text() const +{ + const_cast<MessageDialog*>(this)->setDeferredProperties(); + + return m_sPrimaryString; +} + +OUString const & MessageDialog::get_secondary_text() const +{ + const_cast<MessageDialog*>(this)->setDeferredProperties(); + + return m_sSecondaryString; +} + +bool MessageDialog::set_property(const OString &rKey, const OUString &rValue) +{ + if (rKey == "text") + set_primary_text(rValue); + else if (rKey == "secondary-text") + set_secondary_text(rValue); + else if (rKey == "message-type") + { + VclMessageType eMode = VclMessageType::Info; + if (rValue == "info") + eMode = VclMessageType::Info; + else if (rValue == "warning") + eMode = VclMessageType::Warning; + else if (rValue == "question") + eMode = VclMessageType::Question; + else if (rValue == "error") + eMode = VclMessageType::Error; + else if (rValue == "other") + eMode = VclMessageType::Other; + else + { + SAL_WARN("vcl.layout", "unknown message type mode" << rValue); + } + m_eMessageType = eMode; + } + else if (rKey == "buttons") + { + VclButtonsType eMode = VclButtonsType::NONE; + if (rValue == "none") + eMode = VclButtonsType::NONE; + else if (rValue == "ok") + eMode = VclButtonsType::Ok; + else if (rValue == "cancel") + eMode = VclButtonsType::Cancel; + else if (rValue == "close") + eMode = VclButtonsType::Close; + else if (rValue == "yes-no") + eMode = VclButtonsType::YesNo; + else if (rValue == "ok-cancel") + eMode = VclButtonsType::OkCancel; + else + { + SAL_WARN("vcl.layout", "unknown buttons type mode" << rValue); + } + m_eButtonsType = eMode; + } + else + return Dialog::set_property(rKey, rValue); + return true; +} + +void MessageDialog::set_primary_text(const OUString &rPrimaryString) +{ + m_sPrimaryString = rPrimaryString; + if (m_pPrimaryMessage) + { + m_pPrimaryMessage->SetText(m_sPrimaryString); + m_pPrimaryMessage->Show(!m_sPrimaryString.isEmpty()); + MessageDialog::SetMessagesWidths(this, m_pPrimaryMessage, !m_sSecondaryString.isEmpty() ? m_pSecondaryMessage.get() : nullptr); + } +} + +void MessageDialog::set_secondary_text(const OUString &rSecondaryString) +{ + m_sSecondaryString = rSecondaryString; + if (m_pSecondaryMessage) + { + m_pSecondaryMessage->SetText("\n" + m_sSecondaryString); + m_pSecondaryMessage->Show(!m_sSecondaryString.isEmpty()); + MessageDialog::SetMessagesWidths(this, m_pPrimaryMessage, !m_sSecondaryString.isEmpty() ? m_pSecondaryMessage.get() : nullptr); + } +} + +void MessageDialog::StateChanged(StateChangedType nType) +{ + Dialog::StateChanged(nType); + if (nType == StateChangedType::InitShow) + { + // MessageBox should be at least as wide as to see the title + auto nTitleWidth = CalcTitleWidth(); + // Extra-Width for Close button + nTitleWidth += mpWindowImpl->mnTopBorder; + if (get_preferred_size().Width() < nTitleWidth) + { + set_width_request(nTitleWidth); + DoInitialLayout(); + } + } +} + +VclPaned::VclPaned(vcl::Window *pParent, bool bVertical) + : VclContainer(pParent, WB_HIDE | WB_CLIPCHILDREN) + , m_pSplitter(VclPtr<Splitter>::Create(this, bVertical ? WB_VSCROLL : WB_HSCROLL)) + , m_nPosition(-1) +{ + m_pSplitter->SetBackground(Wallpaper(Application::GetSettings().GetStyleSettings().GetFaceColor())); + m_pSplitter->Show(); +} + +void VclPaned::dispose() +{ + m_pSplitter.disposeAndClear(); + VclContainer::dispose(); +} + +VclVPaned::VclVPaned(vcl::Window *pParent) + : VclPaned(pParent, true) +{ + m_pSplitter->SetSplitHdl(LINK(this, VclVPaned, SplitHdl)); +} + +IMPL_LINK(VclVPaned, SplitHdl, Splitter*, pSplitter, void) +{ + tools::Long nSize = pSplitter->GetSplitPosPixel(); + Size aSplitterSize(m_pSplitter->GetSizePixel()); + Size aAllocation(GetSizePixel()); + arrange(aAllocation, nSize, aAllocation.Height() - nSize - aSplitterSize.Height()); +} + +void VclVPaned::arrange(const Size& rAllocation, tools::Long nFirstHeight, tools::Long nSecondHeight) +{ + Size aSplitterSize(rAllocation.Width(), getLayoutRequisition(*m_pSplitter).Height()); + Size aFirstChildSize(rAllocation.Width(), nFirstHeight); + Size aSecondChildSize(rAllocation.Width(), nSecondHeight); + int nElement = 0; + for (vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (!pChild->IsVisible()) + continue; + if (nElement == 0) + { + Point aSplitterPos(0, aFirstChildSize.Height()); + setLayoutAllocation(*m_pSplitter, aSplitterPos, aSplitterSize); + m_nPosition = aSplitterPos.Y() + aSplitterSize.Height() / 2; + } + else if (nElement == 1) + { + Point aChildPos(0, 0); + setLayoutAllocation(*pChild, aChildPos, aFirstChildSize); + } + else if (nElement == 2) + { + Point aChildPos(0, aFirstChildSize.Height() + aSplitterSize.Height()); + setLayoutAllocation(*pChild, aChildPos, aSecondChildSize); + } + ++nElement; + } +} + +void VclVPaned::set_position(tools::Long nPosition) +{ + VclPaned::set_position(nPosition); + + Size aAllocation(GetSizePixel()); + Size aSplitterSize(m_pSplitter->GetSizePixel()); + + nPosition -= aSplitterSize.Height() / 2; + + arrange(aAllocation, nPosition, aAllocation.Height() - nPosition - aSplitterSize.Height()); +} + +void VclVPaned::setAllocation(const Size& rAllocation) +{ + //supporting "shrink" could be done by adjusting the allowed drag rectangle + m_pSplitter->SetDragRectPixel(tools::Rectangle(Point(0, 0), rAllocation)); + Size aSplitterSize(rAllocation.Width(), getLayoutRequisition(*m_pSplitter).Height()); + const tools::Long nHeight = rAllocation.Height() - aSplitterSize.Height(); + + tools::Long nFirstHeight = 0; + tools::Long nSecondHeight = 0; + bool bFirstCanResize = true; + bool bSecondCanResize = true; + const bool bInitialAllocation = get_position() < 0; + int nElement = 0; + for (const vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (!pChild->IsVisible()) + continue; + if (nElement == 1) + { + if (bInitialAllocation) + nFirstHeight = getLayoutRequisition(*pChild).Height(); + else + nFirstHeight = pChild->GetSizePixel().Height() + pChild->get_margin_top() + pChild->get_margin_bottom(); + bFirstCanResize = pChild->get_expand(); + } + else if (nElement == 2) + { + if (bInitialAllocation) + nSecondHeight = getLayoutRequisition(*pChild).Height(); + else + nSecondHeight = pChild->GetSizePixel().Height() + pChild->get_margin_top() + pChild->get_margin_bottom(); + bSecondCanResize = pChild->get_expand(); + } + ++nElement; + } + tools::Long nHeightRequest = nFirstHeight + nSecondHeight; + tools::Long nHeightDiff = nHeight - nHeightRequest; + if (bFirstCanResize == bSecondCanResize) + nFirstHeight += nHeightDiff/2; + else if (bFirstCanResize) + nFirstHeight += nHeightDiff; + arrange(rAllocation, nFirstHeight, rAllocation.Height() - nFirstHeight - aSplitterSize.Height()); +} + +Size VclVPaned::calculateRequisition() const +{ + Size aRet(0, 0); + + for (const vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (!pChild->IsVisible()) + continue; + Size aChildSize = getLayoutRequisition(*pChild); + aRet.setWidth( std::max(aRet.Width(), aChildSize.Width()) ); + aRet.AdjustHeight(aChildSize.Height() ); + } + + return aRet; +} + +VclHPaned::VclHPaned(vcl::Window *pParent) + : VclPaned(pParent, false) +{ + m_pSplitter->SetSplitHdl(LINK(this, VclHPaned, SplitHdl)); +} + +IMPL_LINK(VclHPaned, SplitHdl, Splitter*, pSplitter, void) +{ + tools::Long nSize = pSplitter->GetSplitPosPixel(); + Size aSplitterSize(m_pSplitter->GetSizePixel()); + Size aAllocation(GetSizePixel()); + arrange(aAllocation, nSize, aAllocation.Width() - nSize - aSplitterSize.Width()); +} + +void VclHPaned::arrange(const Size& rAllocation, tools::Long nFirstWidth, tools::Long nSecondWidth) +{ + Size aSplitterSize(getLayoutRequisition(*m_pSplitter).Width(), rAllocation.Height()); + Size aFirstChildSize(nFirstWidth, rAllocation.Height()); + Size aSecondChildSize(nSecondWidth, rAllocation.Height()); + int nElement = 0; + for (vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (!pChild->IsVisible()) + continue; + if (nElement == 0) + { + Point aSplitterPos(aFirstChildSize.Width(), 0); + setLayoutAllocation(*m_pSplitter, aSplitterPos, aSplitterSize); + m_nPosition = aSplitterPos.X() + aSplitterSize.Width() / 2; + } + else if (nElement == 1) + { + Point aChildPos(0, 0); + setLayoutAllocation(*pChild, aChildPos, aFirstChildSize); + } + else if (nElement == 2) + { + Point aChildPos(aFirstChildSize.Width() + aSplitterSize.Width(), 0); + setLayoutAllocation(*pChild, aChildPos, aSecondChildSize); + } + ++nElement; + } +} + +void VclHPaned::set_position(tools::Long nPosition) +{ + VclPaned::set_position(nPosition); + + Size aAllocation(GetSizePixel()); + Size aSplitterSize(m_pSplitter->GetSizePixel()); + + nPosition -= aSplitterSize.Width() / 2; + + arrange(aAllocation, nPosition, aAllocation.Width() - nPosition - aSplitterSize.Width()); +} + +void VclHPaned::setAllocation(const Size& rAllocation) +{ + //supporting "shrink" could be done by adjusting the allowed drag rectangle + m_pSplitter->SetDragRectPixel(tools::Rectangle(Point(0, 0), rAllocation)); + Size aSplitterSize(getLayoutRequisition(*m_pSplitter).Width(), rAllocation.Height()); + const tools::Long nWidth = rAllocation.Width() - aSplitterSize.Width(); + + tools::Long nFirstWidth = 0; + tools::Long nSecondWidth = 0; + bool bFirstCanResize = true; + bool bSecondCanResize = true; + const bool bInitialAllocation = get_position() < 0; + int nElement = 0; + for (const vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (!pChild->IsVisible()) + continue; + if (nElement == 1) + { + if (bInitialAllocation) + nFirstWidth = getLayoutRequisition(*pChild).Width(); + else + nFirstWidth = pChild->GetSizePixel().Width() + pChild->get_margin_start() + pChild->get_margin_end(); + bFirstCanResize = pChild->get_expand(); + } + else if (nElement == 2) + { + if (bInitialAllocation) + nSecondWidth = getLayoutRequisition(*pChild).Width(); + else + nSecondWidth = pChild->GetSizePixel().Width() + pChild->get_margin_start() + pChild->get_margin_end(); + bSecondCanResize = pChild->get_expand(); + } + ++nElement; + } + tools::Long nWidthRequest = nFirstWidth + nSecondWidth; + tools::Long nWidthDiff = nWidth - nWidthRequest; + if (bFirstCanResize == bSecondCanResize) + nFirstWidth += nWidthDiff/2; + else if (bFirstCanResize) + nFirstWidth += nWidthDiff; + arrange(rAllocation, nFirstWidth, rAllocation.Width() - nFirstWidth - aSplitterSize.Width()); +} + +Size VclHPaned::calculateRequisition() const +{ + Size aRet(0, 0); + + for (const vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (!pChild->IsVisible()) + continue; + Size aChildSize = getLayoutRequisition(*pChild); + aRet.setHeight( std::max(aRet.Height(), aChildSize.Height()) ); + aRet.AdjustWidth(aChildSize.Width() ); + } + + return aRet; +} + +Size getLegacyBestSizeForChildren(const vcl::Window &rWindow) +{ + tools::Rectangle aBounds; + + for (const vcl::Window* pChild = rWindow.GetWindow(GetWindowType::FirstChild); pChild; + pChild = pChild->GetWindow(GetWindowType::Next)) + { + if (!pChild->IsVisible()) + continue; + + tools::Rectangle aChildBounds(pChild->GetPosPixel(), pChild->GetSizePixel()); + aBounds.Union(aChildBounds); + } + + if (aBounds.IsEmpty()) + return rWindow.GetSizePixel(); + + Size aRet(aBounds.GetSize()); + Point aTopLeft(aBounds.TopLeft()); + aRet.AdjustWidth(aTopLeft.X()*2 ); + aRet.AdjustHeight(aTopLeft.Y()*2 ); + + return aRet; +} + +vcl::Window* getNonLayoutParent(vcl::Window *pWindow) +{ + while (pWindow) + { + pWindow = pWindow->GetParent(); + if (!pWindow || !isContainerWindow(*pWindow)) + break; + } + return pWindow; +} + +bool isVisibleInLayout(const vcl::Window *pWindow) +{ + bool bVisible = true; + while (bVisible) + { + bVisible = pWindow->IsVisible(); + pWindow = pWindow->GetParent(); + if (!pWindow || !isContainerWindow(*pWindow)) + break; + } + return bVisible; +} + +bool isEnabledInLayout(const vcl::Window *pWindow) +{ + bool bEnabled = true; + while (bEnabled) + { + bEnabled = pWindow->IsEnabled(); + pWindow = pWindow->GetParent(); + if (!pWindow || !isContainerWindow(*pWindow)) + break; + } + return bEnabled; +} + +bool isLayoutEnabled(const vcl::Window *pWindow) +{ + //Child is a container => we're layout enabled + const vcl::Window *pChild = pWindow ? pWindow->GetWindow(GetWindowType::FirstChild) : nullptr; + return pChild && isContainerWindow(*pChild) && !pChild->GetWindow(GetWindowType::Next); +} + +void VclDrawingArea::RequestHelp(const HelpEvent& rHelpEvent) +{ + if (!(rHelpEvent.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON))) + return; + + Point aPos(ScreenToOutputPixel(rHelpEvent.GetMousePosPixel())); + tools::Rectangle aHelpArea(aPos.X(), aPos.Y()); + OUString sHelpTip = m_aQueryTooltipHdl.Call(aHelpArea); + if (sHelpTip.isEmpty()) + return; + Point aPt = OutputToScreenPixel(aHelpArea.TopLeft()); + aHelpArea.SetLeft(aPt.X()); + aHelpArea.SetTop(aPt.Y()); + aPt = OutputToScreenPixel(aHelpArea.BottomRight()); + aHelpArea.SetRight(aPt.X()); + aHelpArea.SetBottom(aPt.Y()); + // tdf#125369 recover newline support of tdf#101779 + QuickHelpFlags eHelpWinStyle = sHelpTip.indexOf('\n') != -1 ? QuickHelpFlags::TipStyleBalloon : QuickHelpFlags::NONE; + Help::ShowQuickHelp(this, aHelpArea, sHelpTip, eHelpWinStyle); +} + +void VclDrawingArea::StartDrag(sal_Int8, const Point&) +{ + if (m_aStartDragHdl.Call(this)) + return; + + rtl::Reference<TransferDataContainer> xContainer = m_xTransferHelper; + if (!m_xTransferHelper.is()) + return; + + xContainer->StartDrag(this, m_nDragAction); +} + +OUString VclDrawingArea::GetSurroundingText() const +{ + if (!m_aGetSurroundingHdl.IsSet()) + return Control::GetSurroundingText(); + OUString sSurroundingText; + m_aGetSurroundingHdl.Call(sSurroundingText); + return sSurroundingText; +} + +Selection VclDrawingArea::GetSurroundingTextSelection() const +{ + if (!m_aGetSurroundingHdl.IsSet()) + return Control::GetSurroundingTextSelection(); + OUString sSurroundingText; + int nCursor = m_aGetSurroundingHdl.Call(sSurroundingText); + return Selection(nCursor, nCursor); +} + +bool VclDrawingArea::DeleteSurroundingText(const Selection& rSelection) +{ + if (!m_aDeleteSurroundingHdl.IsSet()) + return Control::DeleteSurroundingText(rSelection); + return m_aDeleteSurroundingHdl.Call(rSelection); +} + +VclHPaned::~VclHPaned() +{ +} + +VclVPaned::~VclVPaned() +{ +} + +VclPaned::~VclPaned() +{ + disposeOnce(); +} + +VclScrolledWindow::~VclScrolledWindow() +{ + disposeOnce(); +} + +void VclDrawingArea::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + Control::DumpAsPropertyTree(rJsonWriter); + rJsonWriter.put("type", "drawingarea"); + + ScopedVclPtrInstance<VirtualDevice> pDevice; + pDevice->SetOutputSize( GetSizePixel() ); + tools::Rectangle aRect(Point(0,0), GetSizePixel()); + Paint(*pDevice, aRect); + BitmapEx aImage = pDevice->GetBitmapEx( Point(0,0), GetSizePixel() ); + SvMemoryStream aOStm(65535, 65535); + if(GraphicConverter::Export(aOStm, aImage, ConvertDataFormat::PNG) == ERRCODE_NONE) + { + css::uno::Sequence<sal_Int8> aSeq( static_cast<sal_Int8 const *>(aOStm.GetData()), aOStm.Tell()); + OUStringBuffer aBuffer("data:image/png;base64,"); + ::comphelper::Base64::encode(aBuffer, aSeq); + rJsonWriter.put("image", aBuffer.makeStringAndClear()); + } + rJsonWriter.put("text", GetQuickHelpText()); +} + +FactoryFunction VclDrawingArea::GetUITestFactory() const +{ + if (m_pFactoryFunction) + return m_pFactoryFunction; + return DrawingAreaUIObject::create; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/legacyaccessibility.cxx b/vcl/source/window/legacyaccessibility.cxx new file mode 100644 index 000000000..346e1fdc8 --- /dev/null +++ b/vcl/source/window/legacyaccessibility.cxx @@ -0,0 +1,241 @@ +/* -*- 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 <window.h> + +#include "dlgctrl.hxx" + +using namespace ::com::sun::star; + + +static vcl::Window* ImplGetLabelFor( vcl::Window* pFrameWindow, WindowType nMyType, vcl::Window* pLabel, sal_Unicode nAccel ) +{ + vcl::Window* pWindow = nullptr; + + if( nMyType == WindowType::FIXEDTEXT || + nMyType == WindowType::FIXEDLINE || + nMyType == WindowType::GROUPBOX ) + { + // #i100833# MT 2010/02: Group box and fixed lines can also label a fixed text. + // See tools/options/print for example. + bool bThisIsAGroupControl = (nMyType == WindowType::GROUPBOX) || (nMyType == WindowType::FIXEDLINE); + // get index, form start and form end + sal_uInt16 nIndex=0, nFormStart=0, nFormEnd=0; + ::ImplFindDlgCtrlWindow( pFrameWindow, + pLabel, + nIndex, + nFormStart, + nFormEnd ); + if( nAccel ) + { + // find the accelerated window + pWindow = ::ImplFindAccelWindow( pFrameWindow, + nIndex, + nAccel, + nFormStart, + nFormEnd, + false ); + } + else + { + // find the next control; if that is a fixed text + // fixed line or group box, then return NULL + while( nIndex < nFormEnd ) + { + nIndex++; + vcl::Window* pSWindow = ::ImplGetChildWindow( pFrameWindow, + nIndex, + nIndex, + false ); + if( pSWindow && isVisibleInLayout(pSWindow) && ! (pSWindow->GetStyle() & WB_NOLABEL) ) + { + WindowType nType = pSWindow->GetType(); + if( nType != WindowType::FIXEDTEXT && + nType != WindowType::FIXEDLINE && + nType != WindowType::GROUPBOX ) + { + pWindow = pSWindow; + } + else if( bThisIsAGroupControl && ( nType == WindowType::FIXEDTEXT ) ) + { + pWindow = pSWindow; + } + break; + } + } + } + } + + return pWindow; +} + +namespace vcl { + +Window* Window::getLegacyNonLayoutAccessibleRelationLabelFor() const +{ + Window* pFrameWindow = ImplGetFrameWindow(); + + WinBits nFrameStyle = pFrameWindow->GetStyle(); + if( ! ( nFrameStyle & WB_DIALOGCONTROL ) + || ( nFrameStyle & WB_NODIALOGCONTROL ) + ) + return nullptr; + + sal_Unicode nAccel = getAccel( GetText() ); + + Window* pWindow = ImplGetLabelFor( pFrameWindow, GetType(), const_cast<Window*>(this), nAccel ); + if( ! pWindow && mpWindowImpl->mpRealParent ) + pWindow = ImplGetLabelFor( mpWindowImpl->mpRealParent, GetType(), const_cast<Window*>(this), nAccel ); + return pWindow; +} + +static Window* ImplGetLabeledBy( Window* pFrameWindow, WindowType nMyType, Window* pLabeled ) +{ + Window* pWindow = nullptr; + if ( (nMyType != WindowType::GROUPBOX) && (nMyType != WindowType::FIXEDLINE) ) + { + // search for a control that labels this window + // a label is considered the last fixed text, fixed line or group box + // that comes before this control; with the exception of push buttons + // which are labeled only if the fixed text, fixed line or group box + // is directly before the control + + // get form start and form end and index of this control + sal_uInt16 nIndex, nFormStart, nFormEnd; + Window* pSWindow = ::ImplFindDlgCtrlWindow( pFrameWindow, + pLabeled, + nIndex, + nFormStart, + nFormEnd ); + if( pSWindow && nIndex != nFormStart ) + { + if( nMyType == WindowType::PUSHBUTTON || + nMyType == WindowType::HELPBUTTON || + nMyType == WindowType::OKBUTTON || + nMyType == WindowType::CANCELBUTTON ) + { + nFormStart = nIndex-1; + } + for( sal_uInt16 nSearchIndex = nIndex-1; nSearchIndex >= nFormStart; nSearchIndex-- ) + { + sal_uInt16 nFoundIndex = 0; + pSWindow = ::ImplGetChildWindow( pFrameWindow, + nSearchIndex, + nFoundIndex, + false ); + if( pSWindow && isVisibleInLayout(pSWindow) && !(pSWindow->GetStyle() & WB_NOLABEL) ) + { + WindowType nType = pSWindow->GetType(); + if ( nType == WindowType::FIXEDTEXT || + nType == WindowType::FIXEDLINE || + nType == WindowType::GROUPBOX ) + { + // a fixed text can't be labelled by a fixed text. + if ( ( nMyType != WindowType::FIXEDTEXT ) || ( nType != WindowType::FIXEDTEXT ) ) + pWindow = pSWindow; + break; + } + } + if( nFoundIndex > nSearchIndex || nSearchIndex == 0 ) + break; + } + } + } + return pWindow; +} + +Window* Window::getLegacyNonLayoutAccessibleRelationLabeledBy() const +{ + Window* pFrameWindow = ImplGetFrameWindow(); + + // #i62723#, #104191# checkboxes and radiobuttons are not supposed to have labels + if( GetType() == WindowType::CHECKBOX || GetType() == WindowType::RADIOBUTTON ) + return nullptr; + +// if( ! ( GetType() == WindowType::FIXEDTEXT || +// GetType() == WindowType::FIXEDLINE || +// GetType() == WindowType::GROUPBOX ) ) + // #i100833# MT 2010/02: Group box and fixed lines can also label a fixed text. + // See tools/options/print for example. + + Window* pWindow = ImplGetLabeledBy( pFrameWindow, GetType(), const_cast<Window*>(this) ); + if( ! pWindow && mpWindowImpl->mpRealParent ) + pWindow = ImplGetLabeledBy( mpWindowImpl->mpRealParent, GetType(), const_cast<Window*>(this) ); + + return pWindow; +} + +Window* Window::getLegacyNonLayoutAccessibleRelationMemberOf() const +{ + Window* pWindow = nullptr; + Window* pFrameWindow = GetParent(); + if ( !pFrameWindow ) + { + pFrameWindow = ImplGetFrameWindow(); + } + // if( ! ( GetType() == WindowType::FIXEDTEXT || + if( GetType() != WindowType::FIXEDLINE && GetType() != WindowType::GROUPBOX ) + { + // search for a control that makes member of this window + // it is considered the last fixed line or group box + // that comes before this control; with the exception of push buttons + // which are labeled only if the fixed line or group box + // is directly before the control + // get form start and form end and index of this control + sal_uInt16 nIndex, nFormStart, nFormEnd; + Window* pSWindow = ::ImplFindDlgCtrlWindow( pFrameWindow, + const_cast<Window*>(this), + nIndex, + nFormStart, + nFormEnd ); + if( pSWindow && nIndex != nFormStart ) + { + if( GetType() == WindowType::PUSHBUTTON || + GetType() == WindowType::HELPBUTTON || + GetType() == WindowType::OKBUTTON || + GetType() == WindowType::CANCELBUTTON ) + { + nFormStart = nIndex-1; + } + for( sal_uInt16 nSearchIndex = nIndex-1; nSearchIndex >= nFormStart; nSearchIndex-- ) + { + sal_uInt16 nFoundIndex = 0; + pSWindow = ::ImplGetChildWindow( pFrameWindow, + nSearchIndex, + nFoundIndex, + false ); + if( pSWindow && pSWindow->IsVisible() && + ( pSWindow->GetType() == WindowType::FIXEDLINE || + pSWindow->GetType() == WindowType::GROUPBOX ) ) + { + pWindow = pSWindow; + break; + } + if( nFoundIndex > nSearchIndex || nSearchIndex == 0 ) + break; + } + } + } + return pWindow; +} + +} /* namespace vcl */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/menu.cxx b/vcl/source/window/menu.cxx new file mode 100644 index 000000000..89bd56720 --- /dev/null +++ b/vcl/source/window/menu.cxx @@ -0,0 +1,3077 @@ +/* -*- 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/diagnose_ex.h> +#include <sal/log.hxx> + +#include <comphelper/lok.hxx> +#include <vcl/dialoghelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/mnemonic.hxx> +#include <vcl/image.hxx> +#include <vcl/event.hxx> +#include <vcl/help.hxx> +#include <vcl/toolkit/floatwin.hxx> +#include <vcl/decoview.hxx> +#include <vcl/menu.hxx> +#include <vcl/taskpanelist.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandinfoprovider.hxx> + +#include <salinst.hxx> +#include <svdata.hxx> +#include <strings.hrc> +#include <window.h> +#include <salmenu.hxx> +#include <salframe.hxx> + +#include "menubarwindow.hxx" +#include "menufloatingwindow.hxx" +#include "menuitemlist.hxx" + +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <vcl/toolkit/unowrap.hxx> +#include <rtl/ustrbuf.hxx> + +#include <configsettings.hxx> + +#include <map> +#include <string_view> +#include <vector> + +namespace vcl +{ + +struct MenuLayoutData : public ControlLayoutData +{ + std::vector< sal_uInt16 > m_aLineItemIds; + std::map< sal_uInt16, tools::Rectangle > m_aVisibleItemBoundRects; +}; + +} + +using namespace vcl; + +#define EXTRAITEMHEIGHT 4 +#define SPACE_AROUND_TITLE 4 + +static bool ImplAccelDisabled() +{ + // display of accelerator strings may be suppressed via configuration + static int nAccelDisabled = -1; + + if( nAccelDisabled == -1 ) + { + OUString aStr = + vcl::SettingsConfigItem::get()-> + getValue( "Menu", "SuppressAccelerators" ); + nAccelDisabled = aStr.equalsIgnoreAsciiCase("true") ? 1 : 0; + } + return nAccelDisabled == 1; +} + +static void ImplSetMenuItemData( MenuItemData* pData ) +{ + // convert data + if ( !pData->aImage ) + pData->eType = MenuItemType::STRING; + else if ( pData->aText.isEmpty() ) + pData->eType = MenuItemType::IMAGE; + else + pData->eType = MenuItemType::STRINGIMAGE; +} + +namespace { + +void ImplClosePopupToolBox( const VclPtr<vcl::Window>& pWin ) +{ + if ( pWin->GetType() == WindowType::TOOLBOX && ImplGetDockingManager()->IsInPopupMode( pWin ) ) + { + SystemWindow* pFloatingWindow = ImplGetDockingManager()->GetFloatingWindow(pWin); + if (pFloatingWindow) + static_cast<FloatingWindow*>(pFloatingWindow)->EndPopupMode( FloatWinPopupEndFlags::CloseAll ); + } +} + +// TODO: Move to common code with the same function in toolbox +// Draw the ">>" - more indicator at the coordinates +void lclDrawMoreIndicator(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + rRenderContext.Push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR); + rRenderContext.SetLineColor(); + + if (rRenderContext.GetSettings().GetStyleSettings().GetFaceColor().IsDark()) + rRenderContext.SetFillColor(COL_WHITE); + else + rRenderContext.SetFillColor(COL_BLACK); + float fScaleFactor = rRenderContext.GetDPIScaleFactor(); + + int linewidth = 1 * fScaleFactor; + int space = 4 * fScaleFactor; + + tools::Long width = 8 * fScaleFactor; + tools::Long height = 5 * fScaleFactor; + + //Keep odd b/c drawing code works better + if ( height % 2 == 0 ) + height--; + + tools::Long heightOrig = height; + + tools::Long x = rRect.Left() + (rRect.getWidth() - width)/2 + 1; + tools::Long y = rRect.Top() + (rRect.getHeight() - height)/2 + 1; + while( height >= 1) + { + rRenderContext.DrawRect( tools::Rectangle( x, y, x + linewidth, y ) ); + x += space; + rRenderContext.DrawRect( tools::Rectangle( x, y, x + linewidth, y ) ); + x -= space; + y++; + if( height <= heightOrig / 2 + 1) x--; + else x++; + height--; + } + rRenderContext.Pop(); +} + +} // end anonymous namespace + + +Menu::Menu() + : mpFirstDel(nullptr), + pItemList(new MenuItemList), + pStartedFrom(nullptr), + pWindow(nullptr), + nTitleHeight(0), + nEventId(nullptr), + mnHighlightedItemPos(ITEMPOS_INVALID), + nMenuFlags(MenuFlags::NONE), + nSelectedId(0), + nImgOrChkPos(0), + nTextPos(0), + bCanceled(false), + bInCallback(false), + bKilled(false) +{ +} + +Menu::~Menu() +{ + disposeOnce(); +} + +void Menu::dispose() +{ + ImplCallEventListeners( VclEventId::ObjectDying, ITEMPOS_INVALID ); + + // at the window free the reference to the accessible component + // and make sure the MenuFloatingWindow knows about our destruction + if ( pWindow ) + { + MenuFloatingWindow* pFloat = static_cast<MenuFloatingWindow*>(pWindow.get()); + if( pFloat->pMenu.get() == this ) + pFloat->pMenu.clear(); + pWindow->SetAccessible( css::uno::Reference< css::accessibility::XAccessible >() ); + } + + // dispose accessible components + if ( mxAccessible.is() ) + { + css::uno::Reference< css::lang::XComponent> xComponent( mxAccessible, css::uno::UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); + } + + if ( nEventId ) + Application::RemoveUserEvent( nEventId ); + + // Notify deletion of this menu + ImplMenuDelData* pDelData = mpFirstDel; + while ( pDelData ) + { + pDelData->mpMenu = nullptr; + pDelData = pDelData->mpNext; + } + + bKilled = true; + + // tdf#140225 when clearing pItemList, keep SalMenu in sync with + // their removal during menu teardown + for (size_t n = pItemList->size(); n;) + { + --n; + if (mpSalMenu) + mpSalMenu->RemoveItem(n); + pItemList->Remove(n); + } + + assert(!pItemList->size()); + + mpLayoutData.reset(); + + // Native-support: destroy SalMenu + mpSalMenu.reset(); + + pStartedFrom.clear(); + pWindow.clear(); + VclReferenceBase::dispose(); +} + +void Menu::CreateAutoMnemonics() +{ + MnemonicGenerator aMnemonicGenerator; + size_t n; + for ( n = 0; n < pItemList->size(); n++ ) + { + MenuItemData* pData = pItemList->GetDataFromPos( n ); + if ( ! (pData->nBits & MenuItemBits::NOSELECT ) ) + aMnemonicGenerator.RegisterMnemonic( pData->aText ); + } + for ( n = 0; n < pItemList->size(); n++ ) + { + MenuItemData* pData = pItemList->GetDataFromPos( n ); + if ( ! (pData->nBits & MenuItemBits::NOSELECT ) ) + pData->aText = aMnemonicGenerator.CreateMnemonic( pData->aText ); + } +} + +void Menu::Activate() +{ + bInCallback = true; + + ImplMenuDelData aDelData( this ); + + ImplCallEventListeners( VclEventId::MenuActivate, ITEMPOS_INVALID ); + + if( !aDelData.isDeleted() ) + { + if ( !aActivateHdl.Call( this ) ) + { + if( !aDelData.isDeleted() ) + { + Menu* pStartMenu = ImplGetStartMenu(); + if ( pStartMenu && ( pStartMenu != this ) ) + { + pStartMenu->bInCallback = true; + // MT 11/01: Call EventListener here? I don't know... + pStartMenu->aActivateHdl.Call( this ); + pStartMenu->bInCallback = false; + } + } + } + bInCallback = false; + } + + if (!aDelData.isDeleted() && !(nMenuFlags & MenuFlags::NoAutoMnemonics)) + CreateAutoMnemonics(); +} + +void Menu::Deactivate() +{ + for ( size_t n = pItemList->size(); n; ) + { + MenuItemData* pData = pItemList->GetDataFromPos( --n ); + if ( pData->bIsTemporary ) + { + if ( ImplGetSalMenu() ) + ImplGetSalMenu()->RemoveItem( n ); + + pItemList->Remove( n ); + } + } + + bInCallback = true; + + ImplMenuDelData aDelData( this ); + + Menu* pStartMenu = ImplGetStartMenu(); + ImplCallEventListeners( VclEventId::MenuDeactivate, ITEMPOS_INVALID ); + + if( !aDelData.isDeleted() ) + { + if ( !aDeactivateHdl.Call( this ) ) + { + if( !aDelData.isDeleted() ) + { + if ( pStartMenu && ( pStartMenu != this ) ) + { + pStartMenu->bInCallback = true; + pStartMenu->aDeactivateHdl.Call( this ); + pStartMenu->bInCallback = false; + } + } + } + } + + if( !aDelData.isDeleted() ) + { + bInCallback = false; + } +} + +void Menu::ImplSelect() +{ + MenuItemData* pData = GetItemList()->GetData( nSelectedId ); + if ( pData && (pData->nBits & MenuItemBits::AUTOCHECK) ) + { + bool bChecked = IsItemChecked( nSelectedId ); + if ( pData->nBits & MenuItemBits::RADIOCHECK ) + { + if ( !bChecked ) + CheckItem( nSelectedId ); + } + else + CheckItem( nSelectedId, !bChecked ); + } + + // call select + ImplSVData* pSVData = ImplGetSVData(); + pSVData->maAppData.mpActivePopupMenu = nullptr; // if new execute in select() + nEventId = Application::PostUserEvent( LINK( this, Menu, ImplCallSelect ) ); +} + +void Menu::Select() +{ + ImplMenuDelData aDelData( this ); + + ImplCallEventListeners( VclEventId::MenuSelect, GetItemPos( GetCurItemId() ) ); + if (aDelData.isDeleted()) + return; + if (aSelectHdl.Call(this)) + return; + if (aDelData.isDeleted()) + return; + Menu* pStartMenu = ImplGetStartMenu(); + if (!pStartMenu || (pStartMenu == this)) + return; + pStartMenu->nSelectedId = nSelectedId; + pStartMenu->sSelectedIdent = sSelectedIdent; + pStartMenu->aSelectHdl.Call( this ); +} + +#if defined(MACOSX) +void Menu::ImplSelectWithStart( Menu* pSMenu ) +{ + auto pOldStartedFrom = pStartedFrom; + pStartedFrom = pSMenu; + auto pOldStartedStarted = pOldStartedFrom ? pOldStartedFrom->pStartedFrom : VclPtr<Menu>(); + Select(); + if( pOldStartedFrom ) + pOldStartedFrom->pStartedFrom = pOldStartedStarted; + pStartedFrom = pOldStartedFrom; +} +#endif + +void Menu::ImplCallEventListeners( VclEventId nEvent, sal_uInt16 nPos ) +{ + ImplMenuDelData aDelData( this ); + + VclMenuEvent aEvent( this, nEvent, nPos ); + + // This is needed by atk accessibility bridge + if ( nEvent == VclEventId::MenuHighlight ) + { + Application::ImplCallEventListeners( aEvent ); + } + + if ( !aDelData.isDeleted() ) + { + // Copy the list, because this can be destroyed when calling a Link... + std::list<Link<VclMenuEvent&,void>> aCopy( maEventListeners ); + for ( const auto& rLink : aCopy ) + { + if( std::find(maEventListeners.begin(), maEventListeners.end(), rLink) != maEventListeners.end() ) + rLink.Call( aEvent ); + } + } +} + +void Menu::AddEventListener( const Link<VclMenuEvent&,void>& rEventListener ) +{ + maEventListeners.push_back( rEventListener ); +} + +void Menu::RemoveEventListener( const Link<VclMenuEvent&,void>& rEventListener ) +{ + maEventListeners.remove( rEventListener ); +} + +MenuItemData* Menu::NbcInsertItem(sal_uInt16 nId, MenuItemBits nBits, + const OUString& rStr, Menu* pMenu, + size_t nPos, const OString &rIdent) +{ + // put Item in MenuItemList + MenuItemData* pData = pItemList->Insert(nId, MenuItemType::STRING, + nBits, rStr, pMenu, nPos, rIdent); + + // update native menu + if (ImplGetSalMenu() && pData->pSalMenuItem) + ImplGetSalMenu()->InsertItem(pData->pSalMenuItem.get(), nPos); + + return pData; +} + +void Menu::InsertItem(sal_uInt16 nItemId, const OUString& rStr, MenuItemBits nItemBits, + const OString &rIdent, sal_uInt16 nPos) +{ + SAL_WARN_IF( !nItemId, "vcl", "Menu::InsertItem(): ItemId == 0" ); + SAL_WARN_IF( GetItemPos( nItemId ) != MENU_ITEM_NOTFOUND, "vcl", + "Menu::InsertItem(): ItemId already exists" ); + + // if Position > ItemCount, append + if ( nPos >= pItemList->size() ) + nPos = MENU_APPEND; + + // put Item in MenuItemList + NbcInsertItem(nItemId, nItemBits, rStr, this, nPos, rIdent); + + vcl::Window* pWin = ImplGetWindow(); + mpLayoutData.reset(); + if ( pWin ) + { + ImplCalcSize( pWin ); + if ( pWin->IsVisible() ) + pWin->Invalidate(); + } + ImplCallEventListeners( VclEventId::MenuInsertItem, nPos ); +} + +void Menu::InsertItem(sal_uInt16 nItemId, const Image& rImage, + MenuItemBits nItemBits, const OString &rIdent, sal_uInt16 nPos) +{ + InsertItem(nItemId, OUString(), nItemBits, rIdent, nPos); + SetItemImage( nItemId, rImage ); +} + +void Menu::InsertItem(sal_uInt16 nItemId, const OUString& rStr, + const Image& rImage, MenuItemBits nItemBits, + const OString &rIdent, sal_uInt16 nPos) +{ + InsertItem(nItemId, rStr, nItemBits, rIdent, nPos); + SetItemImage( nItemId, rImage ); +} + +void Menu::InsertSeparator(const OString &rIdent, sal_uInt16 nPos) +{ + // do nothing if it's a menu bar + if (IsMenuBar()) + return; + + // if position > ItemCount, append + if ( nPos >= pItemList->size() ) + nPos = MENU_APPEND; + + // put separator in item list + pItemList->InsertSeparator(rIdent, nPos); + + // update native menu + size_t itemPos = ( nPos != MENU_APPEND ) ? nPos : pItemList->size() - 1; + MenuItemData *pData = pItemList->GetDataFromPos( itemPos ); + if( ImplGetSalMenu() && pData && pData->pSalMenuItem ) + ImplGetSalMenu()->InsertItem( pData->pSalMenuItem.get(), nPos ); + + mpLayoutData.reset(); + + ImplCallEventListeners( VclEventId::MenuInsertItem, nPos ); +} + +void Menu::RemoveItem( sal_uInt16 nPos ) +{ + bool bRemove = false; + + if ( nPos < GetItemCount() ) + { + // update native menu + if( ImplGetSalMenu() ) + ImplGetSalMenu()->RemoveItem( nPos ); + + pItemList->Remove( nPos ); + bRemove = true; + } + + vcl::Window* pWin = ImplGetWindow(); + if ( pWin ) + { + ImplCalcSize( pWin ); + if ( pWin->IsVisible() ) + pWin->Invalidate(); + } + mpLayoutData.reset(); + + if ( bRemove ) + ImplCallEventListeners( VclEventId::MenuRemoveItem, nPos ); +} + +static void ImplCopyItem( Menu* pThis, const Menu& rMenu, sal_uInt16 nPos, sal_uInt16 nNewPos ) +{ + MenuItemType eType = rMenu.GetItemType( nPos ); + + if ( eType == MenuItemType::DONTKNOW ) + return; + + if ( eType == MenuItemType::SEPARATOR ) + pThis->InsertSeparator( OString(), nNewPos ); + else + { + sal_uInt16 nId = rMenu.GetItemId( nPos ); + + SAL_WARN_IF( pThis->GetItemPos( nId ) != MENU_ITEM_NOTFOUND, "vcl", + "Menu::CopyItem(): ItemId already exists" ); + + MenuItemData* pData = rMenu.GetItemList()->GetData( nId ); + + if (!pData) + return; + + if ( eType == MenuItemType::STRINGIMAGE ) + pThis->InsertItem( nId, pData->aText, pData->aImage, pData->nBits, pData->sIdent, nNewPos ); + else if ( eType == MenuItemType::STRING ) + pThis->InsertItem( nId, pData->aText, pData->nBits, pData->sIdent, nNewPos ); + else + pThis->InsertItem( nId, pData->aImage, pData->nBits, pData->sIdent, nNewPos ); + + if ( rMenu.IsItemChecked( nId ) ) + pThis->CheckItem( nId ); + if ( !rMenu.IsItemEnabled( nId ) ) + pThis->EnableItem( nId, false ); + pThis->SetHelpId( nId, pData->aHelpId ); + pThis->SetHelpText( nId, pData->aHelpText ); + pThis->SetAccelKey( nId, pData->aAccelKey ); + pThis->SetItemCommand( nId, pData->aCommandStr ); + pThis->SetHelpCommand( nId, pData->aHelpCommandStr ); + + PopupMenu* pSubMenu = rMenu.GetPopupMenu( nId ); + if ( pSubMenu ) + { + // create auto-copy + VclPtr<PopupMenu> pNewMenu = VclPtr<PopupMenu>::Create( *pSubMenu ); + pThis->SetPopupMenu( nId, pNewMenu ); + } + } +} + +void Menu::Clear() +{ + for ( sal_uInt16 i = GetItemCount(); i; i-- ) + RemoveItem( 0 ); +} + +sal_uInt16 Menu::GetItemCount() const +{ + return static_cast<sal_uInt16>(pItemList->size()); +} + +sal_uInt16 Menu::ImplGetVisibleItemCount() const +{ + sal_uInt16 nItems = 0; + for ( size_t n = pItemList->size(); n; ) + { + if ( ImplIsVisible( --n ) ) + nItems++; + } + return nItems; +} + +sal_uInt16 Menu::ImplGetFirstVisible() const +{ + for ( size_t n = 0; n < pItemList->size(); n++ ) + { + if ( ImplIsVisible( n ) ) + return n; + } + return ITEMPOS_INVALID; +} + +sal_uInt16 Menu::ImplGetPrevVisible( sal_uInt16 nPos ) const +{ + for ( size_t n = nPos; n; ) + { + if (ImplIsVisible(--n)) + return n; + } + return ITEMPOS_INVALID; +} + +sal_uInt16 Menu::ImplGetNextVisible( sal_uInt16 nPos ) const +{ + for ( size_t n = nPos+1; n < pItemList->size(); n++ ) + { + if ( ImplIsVisible( n ) ) + return n; + } + return ITEMPOS_INVALID; +} + +sal_uInt16 Menu::GetItemId(sal_uInt16 nPos) const +{ + MenuItemData* pData = pItemList->GetDataFromPos( nPos ); + + if ( pData ) + return pData->nId; + else + return 0; +} + +sal_uInt16 Menu::GetItemId(std::string_view rIdent) const +{ + for (size_t n = 0; n < pItemList->size(); ++n) + { + MenuItemData* pData = pItemList->GetDataFromPos(n); + if (pData && pData->sIdent == rIdent) + return pData->nId; + } + return MENU_ITEM_NOTFOUND; +} + +sal_uInt16 Menu::GetItemPos( sal_uInt16 nItemId ) const +{ + size_t nPos; + MenuItemData* pData = pItemList->GetData( nItemId, nPos ); + + if ( pData ) + return static_cast<sal_uInt16>(nPos); + else + return MENU_ITEM_NOTFOUND; +} + +MenuItemType Menu::GetItemType( sal_uInt16 nPos ) const +{ + MenuItemData* pData = pItemList->GetDataFromPos( nPos ); + + if ( pData ) + return pData->eType; + else + return MenuItemType::DONTKNOW; +} + +OString Menu::GetItemIdent(sal_uInt16 nId) const +{ + const MenuItemData* pData = pItemList->GetData(nId); + return pData ? pData->sIdent : OString(); +} + +void Menu::SetItemBits( sal_uInt16 nItemId, MenuItemBits nBits ) +{ + size_t nPos; + MenuItemData* pData = pItemList->GetData(nItemId, nPos); + + if (pData && (pData->nBits != nBits)) + { + pData->nBits = nBits; + + // update native menu + if (ImplGetSalMenu()) + ImplGetSalMenu()->SetItemBits(nPos, nBits); + } +} + +MenuItemBits Menu::GetItemBits( sal_uInt16 nItemId ) const +{ + MenuItemBits nBits = MenuItemBits::NONE; + MenuItemData* pData = pItemList->GetData( nItemId ); + if ( pData ) + nBits = pData->nBits; + return nBits; +} + +void Menu::SetUserValue(sal_uInt16 nItemId, void* nUserValue, MenuUserDataReleaseFunction aFunc) +{ + MenuItemData* pData = pItemList->GetData(nItemId); + if (pData) + { + if (pData->aUserValueReleaseFunc) + pData->aUserValueReleaseFunc(pData->nUserValue); + pData->aUserValueReleaseFunc = aFunc; + pData->nUserValue = nUserValue; + } +} + +void* Menu::GetUserValue( sal_uInt16 nItemId ) const +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + return pData ? pData->nUserValue : nullptr; +} + +void Menu::SetPopupMenu( sal_uInt16 nItemId, PopupMenu* pMenu ) +{ + size_t nPos; + MenuItemData* pData = pItemList->GetData( nItemId, nPos ); + + // Item does not exist -> return NULL + if ( !pData ) + return; + + // same menu, nothing to do + if ( static_cast<PopupMenu*>(pData->pSubMenu.get()) == pMenu ) + return; + + // remove old menu + auto oldSubMenu = pData->pSubMenu; + + // data exchange + pData->pSubMenu = pMenu; + + // #112023# Make sure pStartedFrom does not point to invalid (old) data + if ( pData->pSubMenu ) + pData->pSubMenu->pStartedFrom = nullptr; + + // set native submenu + if( ImplGetSalMenu() && pData->pSalMenuItem ) + { + if( pMenu ) + ImplGetSalMenu()->SetSubMenu( pData->pSalMenuItem.get(), pMenu->ImplGetSalMenu(), nPos ); + else + ImplGetSalMenu()->SetSubMenu( pData->pSalMenuItem.get(), nullptr, nPos ); + } + + oldSubMenu.disposeAndClear(); + + ImplCallEventListeners( VclEventId::MenuSubmenuChanged, nPos ); +} + +PopupMenu* Menu::GetPopupMenu( sal_uInt16 nItemId ) const +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if ( pData ) + return static_cast<PopupMenu*>(pData->pSubMenu.get()); + else + return nullptr; +} + +void Menu::SetAccelKey( sal_uInt16 nItemId, const KeyCode& rKeyCode ) +{ + size_t nPos; + MenuItemData* pData = pItemList->GetData( nItemId, nPos ); + + if ( !pData ) + return; + + if ( pData->aAccelKey == rKeyCode ) + return; + + pData->aAccelKey = rKeyCode; + + // update native menu + if( ImplGetSalMenu() && pData->pSalMenuItem ) + ImplGetSalMenu()->SetAccelerator( nPos, pData->pSalMenuItem.get(), rKeyCode, rKeyCode.GetName() ); +} + +KeyCode Menu::GetAccelKey( sal_uInt16 nItemId ) const +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if ( pData ) + return pData->aAccelKey; + else + return KeyCode(); +} + +KeyEvent Menu::GetActivationKey( sal_uInt16 nItemId ) const +{ + KeyEvent aRet; + MenuItemData* pData = pItemList->GetData( nItemId ); + if( pData ) + { + sal_Int32 nPos = pData->aText.indexOf( '~' ); + if( nPos != -1 && nPos < pData->aText.getLength()-1 ) + { + sal_uInt16 nCode = 0; + sal_Unicode cAccel = pData->aText[nPos+1]; + if( cAccel >= 'a' && cAccel <= 'z' ) + nCode = KEY_A + (cAccel-'a'); + else if( cAccel >= 'A' && cAccel <= 'Z' ) + nCode = KEY_A + (cAccel-'A'); + else if( cAccel >= '0' && cAccel <= '9' ) + nCode = KEY_0 + (cAccel-'0'); + + aRet = KeyEvent( cAccel, KeyCode( nCode, KEY_MOD2 ) ); + } + + } + return aRet; +} + +void Menu::CheckItem( sal_uInt16 nItemId, bool bCheck ) +{ + size_t nPos; + MenuItemData* pData = pItemList->GetData( nItemId, nPos ); + + if ( !pData || pData->bChecked == bCheck ) + return; + + // if radio-check, then uncheck previous + if ( bCheck && (pData->nBits & MenuItemBits::AUTOCHECK) && + (pData->nBits & MenuItemBits::RADIOCHECK) ) + { + MenuItemData* pGroupData; + sal_uInt16 nGroupPos; + sal_uInt16 nItemCount = GetItemCount(); + bool bFound = false; + + nGroupPos = nPos; + while ( nGroupPos ) + { + pGroupData = pItemList->GetDataFromPos( nGroupPos-1 ); + if ( pGroupData->nBits & MenuItemBits::RADIOCHECK ) + { + if ( IsItemChecked( pGroupData->nId ) ) + { + CheckItem( pGroupData->nId, false ); + bFound = true; + break; + } + } + else + break; + nGroupPos--; + } + + if ( !bFound ) + { + nGroupPos = nPos+1; + while ( nGroupPos < nItemCount ) + { + pGroupData = pItemList->GetDataFromPos( nGroupPos ); + if ( pGroupData->nBits & MenuItemBits::RADIOCHECK ) + { + if ( IsItemChecked( pGroupData->nId ) ) + { + CheckItem( pGroupData->nId, false ); + break; + } + } + else + break; + nGroupPos++; + } + } + } + + pData->bChecked = bCheck; + + // update native menu + if( ImplGetSalMenu() ) + ImplGetSalMenu()->CheckItem( nPos, bCheck ); + + ImplCallEventListeners( bCheck ? VclEventId::MenuItemChecked : VclEventId::MenuItemUnchecked, nPos ); +} + +void Menu::CheckItem( std::string_view rIdent , bool bCheck ) +{ + CheckItem( GetItemId( rIdent ), bCheck ); +} + +bool Menu::IsItemChecked( sal_uInt16 nItemId ) const +{ + size_t nPos; + MenuItemData* pData = pItemList->GetData( nItemId, nPos ); + + if ( !pData ) + return false; + + return pData->bChecked; +} + +void Menu::EnableItem( sal_uInt16 nItemId, bool bEnable ) +{ + size_t nPos; + MenuItemData* pItemData = pItemList->GetData( nItemId, nPos ); + + if ( !(pItemData && ( pItemData->bEnabled != bEnable )) ) + return; + + pItemData->bEnabled = bEnable; + + vcl::Window* pWin = ImplGetWindow(); + if ( pWin && pWin->IsVisible() ) + { + SAL_WARN_IF(!IsMenuBar(), "vcl", "Menu::EnableItem - Popup visible!" ); + tools::Long nX = 0; + size_t nCount = pItemList->size(); + for ( size_t n = 0; n < nCount; n++ ) + { + MenuItemData* pData = pItemList->GetDataFromPos( n ); + if ( n == nPos ) + { + pWin->Invalidate( tools::Rectangle( Point( nX, 0 ), Size( pData->aSz.Width(), pData->aSz.Height() ) ) ); + break; + } + nX += pData->aSz.Width(); + } + } + // update native menu + if( ImplGetSalMenu() ) + ImplGetSalMenu()->EnableItem( nPos, bEnable ); + + ImplCallEventListeners( bEnable ? VclEventId::MenuEnable : VclEventId::MenuDisable, nPos ); +} + +bool Menu::IsItemEnabled( sal_uInt16 nItemId ) const +{ + size_t nPos; + MenuItemData* pData = pItemList->GetData( nItemId, nPos ); + + if ( !pData ) + return false; + + return pData->bEnabled; +} + +void Menu::ShowItem( sal_uInt16 nItemId, bool bVisible ) +{ + size_t nPos; + MenuItemData* pData = pItemList->GetData( nItemId, nPos ); + + SAL_WARN_IF(IsMenuBar() && !bVisible , "vcl", "Menu::ShowItem - ignored for menu bar entries!"); + if (IsMenuBar() || !pData || (pData->bVisible == bVisible)) + return; + + vcl::Window* pWin = ImplGetWindow(); + if ( pWin && pWin->IsVisible() ) + { + SAL_WARN( "vcl", "Menu::ShowItem - ignored for visible popups!" ); + return; + } + pData->bVisible = bVisible; + + // update native menu + if( ImplGetSalMenu() ) + ImplGetSalMenu()->ShowItem( nPos, bVisible ); +} + +void Menu::SetItemText( sal_uInt16 nItemId, const OUString& rStr ) +{ + size_t nPos; + MenuItemData* pData = pItemList->GetData( nItemId, nPos ); + + if ( !pData ) + return; + + if ( rStr == pData->aText ) + return; + + pData->aText = rStr; + // Clear layout for aText. + pData->aTextGlyphs.Invalidate(); + ImplSetMenuItemData( pData ); + // update native menu + if( ImplGetSalMenu() && pData->pSalMenuItem ) + ImplGetSalMenu()->SetItemText( nPos, pData->pSalMenuItem.get(), rStr ); + + vcl::Window* pWin = ImplGetWindow(); + mpLayoutData.reset(); + if (pWin && IsMenuBar()) + { + ImplCalcSize( pWin ); + if ( pWin->IsVisible() ) + pWin->Invalidate(); + } + + ImplCallEventListeners( VclEventId::MenuItemTextChanged, nPos ); +} + +OUString Menu::GetItemText( sal_uInt16 nItemId ) const +{ + size_t nPos; + MenuItemData* pData = pItemList->GetData( nItemId, nPos ); + + if ( pData ) + return pData->aText; + + return OUString(); +} + +void Menu::SetItemImage( sal_uInt16 nItemId, const Image& rImage ) +{ + size_t nPos; + MenuItemData* pData = pItemList->GetData( nItemId, nPos ); + + if ( !pData ) + return; + + pData->aImage = rImage; + ImplSetMenuItemData( pData ); + + // update native menu + if( ImplGetSalMenu() && pData->pSalMenuItem ) + ImplGetSalMenu()->SetItemImage( nPos, pData->pSalMenuItem.get(), rImage ); +} + +Image Menu::GetItemImage( sal_uInt16 nItemId ) const +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if ( pData ) + return pData->aImage; + else + return Image(); +} + +void Menu::SetItemCommand( sal_uInt16 nItemId, const OUString& rCommand ) +{ + size_t nPos; + MenuItemData* pData = pItemList->GetData( nItemId, nPos ); + + if ( pData ) + pData->aCommandStr = rCommand; +} + +OUString Menu::GetItemCommand( sal_uInt16 nItemId ) const +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if (pData) + return pData->aCommandStr; + + return OUString(); +} + +void Menu::SetHelpCommand( sal_uInt16 nItemId, const OUString& rStr ) +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if ( pData ) + pData->aHelpCommandStr = rStr; +} + +OUString Menu::GetHelpCommand( sal_uInt16 nItemId ) const +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if ( pData ) + return pData->aHelpCommandStr; + + return OUString(); +} + +void Menu::SetHelpText( sal_uInt16 nItemId, const OUString& rStr ) +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if ( pData ) + pData->aHelpText = rStr; +} + +OUString Menu::ImplGetHelpText( sal_uInt16 nItemId ) const +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if (!pData) + return OUString(); + + if ( pData->aHelpText.isEmpty() && + (( !pData->aHelpId.isEmpty() ) || ( !pData->aCommandStr.isEmpty() ))) + { + Help* pHelp = Application::GetHelp(); + if ( pHelp ) + { + if (!pData->aCommandStr.isEmpty()) + pData->aHelpText = pHelp->GetHelpText( pData->aCommandStr, static_cast<weld::Widget*>(nullptr) ); + if (pData->aHelpText.isEmpty() && !pData->aHelpId.isEmpty()) + pData->aHelpText = pHelp->GetHelpText( OStringToOUString( pData->aHelpId, RTL_TEXTENCODING_UTF8 ), static_cast<weld::Widget*>(nullptr) ); + } + } + + return pData->aHelpText; +} + +OUString Menu::GetHelpText( sal_uInt16 nItemId ) const +{ + return ImplGetHelpText( nItemId ); +} + +void Menu::SetTipHelpText( sal_uInt16 nItemId, const OUString& rStr ) +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if ( pData ) + pData->aTipHelpText = rStr; +} + +OUString Menu::GetTipHelpText( sal_uInt16 nItemId ) const +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if ( pData ) + return pData->aTipHelpText; + + return OUString(); +} + +void Menu::SetHelpId( sal_uInt16 nItemId, const OString& rHelpId ) +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if ( pData ) + pData->aHelpId = rHelpId; +} + +OString Menu::GetHelpId( sal_uInt16 nItemId ) const +{ + OString aRet; + + MenuItemData* pData = pItemList->GetData( nItemId ); + + if ( pData ) + { + if ( !pData->aHelpId.isEmpty() ) + aRet = pData->aHelpId; + else + aRet = OUStringToOString( pData->aCommandStr, RTL_TEXTENCODING_UTF8 ); + } + + return aRet; +} + +Menu& Menu::operator=( const Menu& rMenu ) +{ + if(this == &rMenu) + return *this; + + // clean up + Clear(); + + // copy items + sal_uInt16 nCount = rMenu.GetItemCount(); + for ( sal_uInt16 i = 0; i < nCount; i++ ) + ImplCopyItem( this, rMenu, i, MENU_APPEND ); + + aActivateHdl = rMenu.aActivateHdl; + aDeactivateHdl = rMenu.aDeactivateHdl; + aSelectHdl = rMenu.aSelectHdl; + aTitleText = rMenu.aTitleText; + nTitleHeight = rMenu.nTitleHeight; + + return *this; +} + +// Returns true if the item is completely hidden on the GUI and shouldn't +// be possible to interact with +bool Menu::ImplCurrentlyHiddenOnGUI(sal_uInt16 nPos) const +{ + MenuItemData* pData = pItemList->GetDataFromPos(nPos); + if (pData) + { + MenuItemData* pPreviousData = pItemList->GetDataFromPos( nPos - 1 ); + if (pPreviousData && pPreviousData->bHiddenOnGUI) + { + return true; + } + } + return false; +} + +bool Menu::ImplIsVisible( sal_uInt16 nPos ) const +{ + bool bVisible = true; + + MenuItemData* pData = pItemList->GetDataFromPos( nPos ); + // check general visibility first + if( pData && !pData->bVisible ) + bVisible = false; + + if ( bVisible && pData && pData->eType == MenuItemType::SEPARATOR ) + { + if( nPos == 0 ) // no separator should be shown at the very beginning + bVisible = false; + else + { + // always avoid adjacent separators + size_t nCount = pItemList->size(); + size_t n; + MenuItemData* pNextData = nullptr; + // search next visible item + for( n = nPos + 1; n < nCount; n++ ) + { + pNextData = pItemList->GetDataFromPos( n ); + if( pNextData && pNextData->bVisible ) + { + if( pNextData->eType == MenuItemType::SEPARATOR || ImplIsVisible(n) ) + break; + } + } + if( n == nCount ) // no next visible item + bVisible = false; + // check for separator + if( pNextData && pNextData->bVisible && pNextData->eType == MenuItemType::SEPARATOR ) + bVisible = false; + + if( bVisible ) + { + for( n = nPos; n > 0; n-- ) + { + pNextData = pItemList->GetDataFromPos( n-1 ); + if( pNextData && pNextData->bVisible ) + { + if( pNextData->eType != MenuItemType::SEPARATOR && ImplIsVisible(n-1) ) + break; + } + } + if( n == 0 ) // no previous visible item + bVisible = false; + } + } + } + + // not allowed for menubar, as I do not know + // whether a menu-entry will disappear or will appear + if (bVisible && !IsMenuBar() && (nMenuFlags & MenuFlags::HideDisabledEntries) && + !(nMenuFlags & MenuFlags::AlwaysShowDisabledEntries)) + { + if( !pData ) // e.g. nPos == ITEMPOS_INVALID + bVisible = false; + else if ( pData->eType != MenuItemType::SEPARATOR ) // separators handled above + { + // tdf#86850 Always display clipboard functions + if ( pData->aCommandStr == ".uno:Cut" || pData->aCommandStr == ".uno:Copy" || pData->aCommandStr == ".uno:Paste" || + pData->sIdent == ".uno:Cut" || pData->sIdent == ".uno:Copy" || pData->sIdent == ".uno:Paste" ) + bVisible = true; + else + // bVisible = pData->bEnabled && ( !pData->pSubMenu || pData->pSubMenu->HasValidEntries( true ) ); + bVisible = pData->bEnabled; // do not check submenus as they might be filled at Activate(). + } + } + + return bVisible; +} + +bool Menu::IsItemPosVisible( sal_uInt16 nItemPos ) const +{ + return IsMenuVisible() && ImplIsVisible( nItemPos ); +} + +bool Menu::IsMenuVisible() const +{ + return pWindow && pWindow->IsReallyVisible(); +} + +bool Menu::ImplIsSelectable( sal_uInt16 nPos ) const +{ + bool bSelectable = true; + + MenuItemData* pData = pItemList->GetDataFromPos( nPos ); + // check general visibility first + if ( pData && ( pData->nBits & MenuItemBits::NOSELECT ) ) + bSelectable = false; + + return bSelectable; +} + +css::uno::Reference<css::accessibility::XAccessible> Menu::GetAccessible() +{ + // Since PopupMenu are sometimes shared by different instances of MenuBar, the mxAccessible member gets + // overwritten and may contain a disposed object when the initial menubar gets set again. So use the + // mxAccessible member only for sub menus. + if (pStartedFrom && pStartedFrom != this) + { + for ( sal_uInt16 i = 0, nCount = pStartedFrom->GetItemCount(); i < nCount; ++i ) + { + sal_uInt16 nItemId = pStartedFrom->GetItemId( i ); + if ( static_cast< Menu* >( pStartedFrom->GetPopupMenu( nItemId ) ) == this ) + { + css::uno::Reference<css::accessibility::XAccessible> xParent = pStartedFrom->GetAccessible(); + if ( xParent.is() ) + { + css::uno::Reference<css::accessibility::XAccessibleContext> xParentContext( xParent->getAccessibleContext() ); + if (xParentContext.is()) + return xParentContext->getAccessibleChild( i ); + } + } + } + } + else if ( !mxAccessible.is() ) + { + UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(); + if ( pWrapper ) + mxAccessible = pWrapper->CreateAccessible(this, IsMenuBar()); + } + + return mxAccessible; +} + +void Menu::SetAccessible(const css::uno::Reference<css::accessibility::XAccessible>& rxAccessible ) +{ + mxAccessible = rxAccessible; +} + +Size Menu::ImplGetNativeCheckAndRadioSize(vcl::RenderContext const & rRenderContext, tools::Long& rCheckHeight, tools::Long& rRadioHeight ) const +{ + tools::Long nCheckWidth = 0, nRadioWidth = 0; + rCheckHeight = rRadioHeight = 0; + + if (!IsMenuBar()) + { + ImplControlValue aVal; + tools::Rectangle aNativeBounds; + tools::Rectangle aNativeContent; + + tools::Rectangle aCtrlRegion(tools::Rectangle(Point(), Size(100, 15))); + if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemCheckMark)) + { + if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemCheckMark, + aCtrlRegion, ControlState::ENABLED, aVal, + aNativeBounds, aNativeContent)) + { + rCheckHeight = aNativeBounds.GetHeight() - 1; + nCheckWidth = aNativeContent.GetWidth() - 1; + } + } + if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItemRadioMark)) + { + if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::MenuItemRadioMark, + aCtrlRegion, ControlState::ENABLED, aVal, + aNativeBounds, aNativeContent)) + { + rRadioHeight = aNativeBounds.GetHeight() - 1; + nRadioWidth = aNativeContent.GetWidth() - 1; + } + } + } + return Size(std::max(nCheckWidth, nRadioWidth), std::max(rCheckHeight, rRadioHeight)); +} + +bool Menu::ImplGetNativeSubmenuArrowSize(vcl::RenderContext const & rRenderContext, Size& rArrowSize, tools::Long& rArrowSpacing) +{ + ImplControlValue aVal; + tools::Rectangle aCtrlRegion(tools::Rectangle(Point(), Size(100, 15))); + if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::SubmenuArrow)) + { + tools::Rectangle aNativeContent; + tools::Rectangle aNativeBounds; + if (rRenderContext.GetNativeControlRegion(ControlType::MenuPopup, ControlPart::SubmenuArrow, + aCtrlRegion, ControlState::ENABLED, + aVal, aNativeBounds, aNativeContent)) + { + Size aSize(aNativeContent.GetWidth(), aNativeContent.GetHeight()); + rArrowSize = aSize; + rArrowSpacing = aNativeBounds.GetWidth() - aNativeContent.GetWidth(); + return true; + } + } + return false; +} + +void Menu::ImplAddDel( ImplMenuDelData& rDel ) +{ + SAL_WARN_IF( rDel.mpMenu, "vcl", "Menu::ImplAddDel(): cannot add ImplMenuDelData twice !" ); + if( !rDel.mpMenu ) + { + rDel.mpMenu = this; + rDel.mpNext = mpFirstDel; + mpFirstDel = &rDel; + } +} + +void Menu::ImplRemoveDel( ImplMenuDelData& rDel ) +{ + rDel.mpMenu = nullptr; + if ( mpFirstDel == &rDel ) + { + mpFirstDel = rDel.mpNext; + } + else + { + ImplMenuDelData* pData = mpFirstDel; + while ( pData && (pData->mpNext != &rDel) ) + pData = pData->mpNext; + + SAL_WARN_IF( !pData, "vcl", "Menu::ImplRemoveDel(): ImplMenuDelData not registered !" ); + if( pData ) + pData->mpNext = rDel.mpNext; + } +} + +Size Menu::ImplCalcSize( vcl::Window* pWin ) +{ + // | Check/Radio/Image| Text| Accel/Popup| + + // for symbols: nFontHeight x nFontHeight + tools::Long nFontHeight = pWin->GetTextHeight(); + tools::Long nExtra = nFontHeight/4; + + tools::Long nMinMenuItemHeight = nFontHeight; + tools::Long nCheckHeight = 0, nRadioHeight = 0; + Size aMarkSize = ImplGetNativeCheckAndRadioSize(*pWin->GetOutDev(), nCheckHeight, nRadioHeight); + if( aMarkSize.Height() > nMinMenuItemHeight ) + nMinMenuItemHeight = aMarkSize.Height(); + + tools::Long aMaxImgWidth = 0; + + const StyleSettings& rSettings = pWin->GetSettings().GetStyleSettings(); + if ( rSettings.GetUseImagesInMenus() ) + { + if ( 16 > nMinMenuItemHeight ) + nMinMenuItemHeight = 16; + for ( size_t i = pItemList->size(); i; ) + { + MenuItemData* pData = pItemList->GetDataFromPos( --i ); + if ( ImplIsVisible( i ) + && ( ( pData->eType == MenuItemType::IMAGE ) + || ( pData->eType == MenuItemType::STRINGIMAGE ) + ) + ) + { + Size aImgSz = pData->aImage.GetSizePixel(); + if ( aImgSz.Width() > aMaxImgWidth ) + aMaxImgWidth = aImgSz.Width(); + if ( aImgSz.Height() > nMinMenuItemHeight ) + nMinMenuItemHeight = aImgSz.Height(); + break; + } + } + } + + Size aSz; + tools::Long nMaxWidth = 0; + + for ( size_t n = pItemList->size(); n; ) + { + MenuItemData* pData = pItemList->GetDataFromPos( --n ); + + pData->aSz.setHeight( 0 ); + pData->aSz.setWidth( 0 ); + + if ( ImplIsVisible( n ) ) + { + tools::Long nWidth = 0; + + // Separator + if (!IsMenuBar()&& (pData->eType == MenuItemType::SEPARATOR)) + { + pData->aSz.setHeight( 4 ); + } + + // Image: + if (!IsMenuBar() && ((pData->eType == MenuItemType::IMAGE) || (pData->eType == MenuItemType::STRINGIMAGE))) + { + tools::Long aImgHeight = pData->aImage.GetSizePixel().Height(); + + aImgHeight += 4; // add a border for native marks + if (aImgHeight > pData->aSz.Height()) + pData->aSz.setHeight(aImgHeight); + } + + // Check Buttons: + if (!IsMenuBar() && pData->HasCheck()) + { + // checks / images take the same place + if( ( pData->eType != MenuItemType::IMAGE ) && ( pData->eType != MenuItemType::STRINGIMAGE ) ) + { + nWidth += aMarkSize.Width() + nExtra * 2; + if (aMarkSize.Height() > pData->aSz.Height()) + pData->aSz.setHeight(aMarkSize.Height()); + } + } + + // Text: + if ( (pData->eType == MenuItemType::STRING) || (pData->eType == MenuItemType::STRINGIMAGE) ) + { + const SalLayoutGlyphs* pGlyphs = pData->GetTextGlyphs(pWin->GetOutDev()); + tools::Long nTextWidth = pWin->GetOutDev()->GetCtrlTextWidth(pData->aText, pGlyphs); + tools::Long nTextHeight = pWin->GetTextHeight() + EXTRAITEMHEIGHT; + + if (IsMenuBar()) + { + if ( nTextHeight > pData->aSz.Height() ) + pData->aSz.setHeight( nTextHeight ); + + pData->aSz.setWidth( nTextWidth + 4*nExtra ); + aSz.AdjustWidth(pData->aSz.Width() ); + } + else + pData->aSz.setHeight( std::max( std::max( nTextHeight, pData->aSz.Height() ), nMinMenuItemHeight ) ); + + nWidth += nTextWidth; + } + + // Accel + if (!IsMenuBar()&& pData->aAccelKey.GetCode() && !ImplAccelDisabled()) + { + OUString aName = pData->aAccelKey.GetName(); + tools::Long nAccWidth = pWin->GetTextWidth( aName ); + nAccWidth += nExtra; + nWidth += nAccWidth; + } + + // SubMenu? + if (!IsMenuBar() && pData->pSubMenu) + { + if ( nFontHeight > nWidth ) + nWidth += nFontHeight; + + pData->aSz.setHeight( std::max( std::max( nFontHeight, pData->aSz.Height() ), nMinMenuItemHeight ) ); + } + + if (!IsMenuBar()) + aSz.AdjustHeight(pData->aSz.Height() ); + + if ( nWidth > nMaxWidth ) + nMaxWidth = nWidth; + + } + } + + // Additional space for title + nTitleHeight = 0; + if (!IsMenuBar() && aTitleText.getLength() > 0) { + // Set expected font + pWin->GetOutDev()->Push(PushFlags::FONT); + vcl::Font aFont = pWin->GetFont(); + aFont.SetWeight(WEIGHT_BOLD); + pWin->SetFont(aFont); + + // Compute text bounding box + tools::Rectangle aTextBoundRect; + pWin->GetOutDev()->GetTextBoundRect(aTextBoundRect, aTitleText); + + // Vertically, one height of char + extra space for decoration + nTitleHeight = aTextBoundRect.GetSize().Height() + 4 * SPACE_AROUND_TITLE ; + aSz.AdjustHeight(nTitleHeight ); + + tools::Long nWidth = aTextBoundRect.GetSize().Width() + 4 * SPACE_AROUND_TITLE; + pWin->GetOutDev()->Pop(); + if ( nWidth > nMaxWidth ) + nMaxWidth = nWidth; + } + + if (!IsMenuBar()) + { + // popup menus should not be wider than half the screen + // except on rather small screens + // TODO: move GetScreenNumber from SystemWindow to Window ? + // currently we rely on internal privileges + unsigned int nDisplayScreen = pWin->ImplGetWindowImpl()->mpFrame->maGeometry.nDisplayScreenNumber; + tools::Rectangle aDispRect( Application::GetScreenPosSizePixel( nDisplayScreen ) ); + tools::Long nScreenWidth = aDispRect.GetWidth() >= 800 ? aDispRect.GetWidth() : 800; + if( nMaxWidth > nScreenWidth/2 ) + nMaxWidth = nScreenWidth/2; + + sal_uInt16 gfxExtra = static_cast<sal_uInt16>(std::max( nExtra, tools::Long(7) )); // #107710# increase space between checkmarks/images/text + nImgOrChkPos = static_cast<sal_uInt16>(nExtra); + tools::Long nImgOrChkWidth = 0; + if( aMarkSize.Height() > 0 ) // NWF case + nImgOrChkWidth = aMarkSize.Height() + nExtra; + else // non NWF case + nImgOrChkWidth = nFontHeight/2 + gfxExtra; + nImgOrChkWidth = std::max( nImgOrChkWidth, aMaxImgWidth + gfxExtra ); + nTextPos = static_cast<sal_uInt16>(nImgOrChkPos + nImgOrChkWidth); + nTextPos = nTextPos + gfxExtra; + + aSz.setWidth( nTextPos + nMaxWidth + nExtra ); + aSz.AdjustWidth(4*nExtra ); // a _little_ more ... + + aSz.AdjustWidth(2*ImplGetSVData()->maNWFData.mnMenuFormatBorderX ); + aSz.AdjustHeight(2*ImplGetSVData()->maNWFData.mnMenuFormatBorderY ); + } + else + { + nTextPos = static_cast<sal_uInt16>(2*nExtra); + aSz.setHeight( nFontHeight+6 ); + + // get menubar height from native methods if supported + if( pWindow->IsNativeControlSupported( ControlType::Menubar, ControlPart::Entire ) ) + { + ImplControlValue aVal; + tools::Rectangle aNativeBounds; + tools::Rectangle aNativeContent; + Point tmp( 0, 0 ); + tools::Rectangle aCtrlRegion( tmp, Size( 100, 15 ) ); + if( pWindow->GetNativeControlRegion( ControlType::Menubar, + ControlPart::Entire, + aCtrlRegion, + ControlState::ENABLED, + aVal, + aNativeBounds, + aNativeContent ) + ) + { + int nNativeHeight = aNativeBounds.GetHeight(); + if( nNativeHeight > aSz.Height() ) + aSz.setHeight( nNativeHeight ); + } + } + + // account for the size of the close button, which actually is a toolbox + // due to NWF this is variable + tools::Long nCloseButtonHeight = static_cast<MenuBarWindow*>(pWindow.get())->MinCloseButtonSize().Height(); + if (aSz.Height() < nCloseButtonHeight) + aSz.setHeight( nCloseButtonHeight ); + } + + return aSz; +} + +static void ImplPaintCheckBackground(vcl::RenderContext & rRenderContext, vcl::Window const & rWindow, const tools::Rectangle& i_rRect, bool i_bHighlight) +{ + bool bNativeOk = false; + if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Button)) + { + ImplControlValue aControlValue; + aControlValue.setTristateVal(ButtonValue::On); + tools::Rectangle r = i_rRect; + r.AdjustBottom(1); + + bNativeOk = rRenderContext.DrawNativeControl(ControlType::Toolbar, ControlPart::Button, + r, + ControlState::PRESSED | ControlState::ENABLED, + aControlValue, + OUString()); + } + + if (!bNativeOk) + { + const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings(); + Color aColor( i_bHighlight ? rSettings.GetMenuHighlightTextColor() : rSettings.GetHighlightColor() ); + RenderTools::DrawSelectionBackground(rRenderContext, rWindow, i_rRect, 0, i_bHighlight, true, false, nullptr, 2, &aColor); + } +} + +static OUString getShortenedString( const OUString& i_rLong, vcl::RenderContext const & rRenderContext, tools::Long i_nMaxWidth ) +{ + sal_Int32 nPos = -1; + OUString aNonMnem(OutputDevice::GetNonMnemonicString(i_rLong, nPos)); + aNonMnem = rRenderContext.GetEllipsisString( aNonMnem, i_nMaxWidth, DrawTextFlags::CenterEllipsis); + // re-insert mnemonic + if (nPos != -1) + { + if (nPos < aNonMnem.getLength() && i_rLong[nPos+1] == aNonMnem[nPos]) + { + OUString aTmp = OUString::Concat(aNonMnem.subView(0, nPos)) + "~" + aNonMnem.subView(nPos); + aNonMnem = aTmp; + } + } + return aNonMnem; +} + +void Menu::ImplPaintMenuTitle(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) const +{ + // Save previous graphical settings, set new one + rRenderContext.Push(PushFlags::FONT | PushFlags::FILLCOLOR); + Wallpaper aOldBackground = rRenderContext.GetBackground(); + + Color aBackgroundColor = rRenderContext.GetSettings().GetStyleSettings().GetMenuBarColor(); + rRenderContext.SetBackground(Wallpaper(aBackgroundColor)); + rRenderContext.SetFillColor(aBackgroundColor); + vcl::Font aFont = rRenderContext.GetFont(); + aFont.SetWeight(WEIGHT_BOLD); + rRenderContext.SetFont(aFont); + + // Draw background rectangle + tools::Rectangle aBgRect(rRect); + int nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX; + aBgRect.Move(SPACE_AROUND_TITLE, SPACE_AROUND_TITLE); + aBgRect.setWidth(aBgRect.getWidth() - 2 * SPACE_AROUND_TITLE - 2 * nOuterSpaceX); + aBgRect.setHeight(nTitleHeight - 2 * SPACE_AROUND_TITLE); + rRenderContext.DrawRect(aBgRect); + + // Draw the text centered + Point aTextTopLeft(aBgRect.TopLeft()); + tools::Rectangle aTextBoundRect; + rRenderContext.GetTextBoundRect( aTextBoundRect, aTitleText ); + aTextTopLeft.AdjustX((aBgRect.getWidth() - aTextBoundRect.GetSize().Width()) / 2 ); + aTextTopLeft.AdjustY((aBgRect.GetHeight() - aTextBoundRect.GetSize().Height()) / 2 + - aTextBoundRect.Top() ); + rRenderContext.DrawText(aTextTopLeft, aTitleText, 0, aTitleText.getLength()); + + // Restore + rRenderContext.Pop(); + rRenderContext.SetBackground(aOldBackground); +} + +void Menu::ImplPaint(vcl::RenderContext& rRenderContext, Size const & rSize, + sal_uInt16 nBorder, tools::Long nStartY, MenuItemData const * pThisItemOnly, + bool bHighlighted, bool bLayout, bool bRollover) const +{ + // for symbols: nFontHeight x nFontHeight + tools::Long nFontHeight = rRenderContext.GetTextHeight(); + tools::Long nExtra = nFontHeight / 4; + + tools::Long nCheckHeight = 0, nRadioHeight = 0; + ImplGetNativeCheckAndRadioSize(rRenderContext, nCheckHeight, nRadioHeight); + + DecorationView aDecoView(&rRenderContext); + const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings(); + + Point aTopLeft, aTmpPos; + + int nOuterSpaceX = 0; + if (!IsMenuBar()) + { + nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX; + aTopLeft.AdjustX(nOuterSpaceX ); + aTopLeft.AdjustY(ImplGetSVData()->maNWFData.mnMenuFormatBorderY ); + } + + // for the computations, use size of the underlying window, not of RenderContext + Size aOutSz(rSize); + + size_t nCount = pItemList->size(); + if (bLayout) + mpLayoutData->m_aVisibleItemBoundRects.clear(); + + // Paint title + if (!pThisItemOnly && !IsMenuBar() && nTitleHeight > 0) + ImplPaintMenuTitle(rRenderContext, tools::Rectangle(aTopLeft, aOutSz)); + + bool bHiddenItems = false; // are any items on the GUI hidden + + for (size_t n = 0; n < nCount; n++) + { + MenuItemData* pData = pItemList->GetDataFromPos( n ); + if (ImplIsVisible(n) && (!pThisItemOnly || (pData == pThisItemOnly))) + { + if (pThisItemOnly) + { + if (IsMenuBar()) + { + if (!ImplGetSVData()->maNWFData.mbRolloverMenubar) + { + if (bRollover) + rRenderContext.SetTextColor(rSettings.GetMenuBarRolloverTextColor()); + else if (bHighlighted) + rRenderContext.SetTextColor(rSettings.GetMenuBarHighlightTextColor()); + } + else + { + if (bHighlighted) + rRenderContext.SetTextColor(rSettings.GetMenuBarHighlightTextColor()); + else if (bRollover) + rRenderContext.SetTextColor(rSettings.GetMenuBarRolloverTextColor()); + } + if (!bRollover && !bHighlighted) + rRenderContext.SetTextColor(rSettings.GetMenuBarTextColor()); + } + else if (bHighlighted) + rRenderContext.SetTextColor(rSettings.GetMenuHighlightTextColor()); + } + + Point aPos(aTopLeft); + aPos.AdjustY(nBorder ); + aPos.AdjustY(nStartY ); + + if (aPos.Y() >= 0) + { + tools::Long nTextOffsetY = (pData->aSz.Height() - nFontHeight) / 2; + if (IsMenuBar()) + nTextOffsetY += (aOutSz.Height()-pData->aSz.Height()) / 2; + DrawTextFlags nTextStyle = DrawTextFlags::NONE; + DrawSymbolFlags nSymbolStyle = DrawSymbolFlags::NONE; + DrawImageFlags nImageStyle = DrawImageFlags::NONE; + + // submenus without items are not disabled when no items are + // contained. The application itself should check for this! + // Otherwise it could happen entries are disabled due to + // asynchronous loading + if (!pData->bEnabled || !pWindow->IsEnabled()) + { + nTextStyle |= DrawTextFlags::Disable; + nSymbolStyle |= DrawSymbolFlags::Disable; + nImageStyle |= DrawImageFlags::Disable; + } + + // Separator + if (!bLayout && !IsMenuBar() && (pData->eType == MenuItemType::SEPARATOR)) + { + bool bNativeOk = false; + if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Separator)) + { + ControlState nState = ControlState::NONE; + if (pData->bEnabled && pWindow->IsEnabled()) + nState |= ControlState::ENABLED; + if (bHighlighted) + nState |= ControlState::SELECTED; + Size aSz(pData->aSz); + aSz.setWidth( aOutSz.Width() - 2*nOuterSpaceX ); + tools::Rectangle aItemRect(aPos, aSz); + MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect); + bNativeOk = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Separator, + aItemRect, nState, aVal, OUString()); + } + if (!bNativeOk) + { + aTmpPos.setY( aPos.Y() + ((pData->aSz.Height() - 2) / 2) ); + aTmpPos.setX( aPos.X() + 2 + nOuterSpaceX ); + rRenderContext.SetLineColor(rSettings.GetShadowColor()); + rRenderContext.DrawLine(aTmpPos, Point(aOutSz.Width() - 3 - 2 * nOuterSpaceX, aTmpPos.Y())); + aTmpPos.AdjustY( 1 ); + rRenderContext.SetLineColor(rSettings.GetLightColor()); + rRenderContext.DrawLine(aTmpPos, Point(aOutSz.Width() - 3 - 2 * nOuterSpaceX, aTmpPos.Y())); + rRenderContext.SetLineColor(); + } + } + + tools::Rectangle aOuterCheckRect(Point(aPos.X()+nImgOrChkPos, aPos.Y()), + Size(pData->aSz.Height(), pData->aSz.Height())); + + // CheckMark + if (!bLayout && !IsMenuBar() && pData->HasCheck()) + { + // draw selection transparent marker if checked + // onto that either a checkmark or the item image + // will be painted + // however do not do this if native checks will be painted since + // the selection color too often does not fit the theme's check and/or radio + + if( (pData->eType != MenuItemType::IMAGE) && (pData->eType != MenuItemType::STRINGIMAGE)) + { + if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, + (pData->nBits & MenuItemBits::RADIOCHECK) + ? ControlPart::MenuItemCheckMark + : ControlPart::MenuItemRadioMark)) + { + ControlPart nPart = ((pData->nBits & MenuItemBits::RADIOCHECK) + ? ControlPart::MenuItemRadioMark + : ControlPart::MenuItemCheckMark); + + ControlState nState = ControlState::NONE; + + if (pData->bChecked) + nState |= ControlState::PRESSED; + + if (pData->bEnabled && pWindow->IsEnabled()) + nState |= ControlState::ENABLED; + + if (bHighlighted) + nState |= ControlState::SELECTED; + + tools::Long nCtrlHeight = (pData->nBits & MenuItemBits::RADIOCHECK) ? nCheckHeight : nRadioHeight; + aTmpPos.setX( aOuterCheckRect.Left() + (aOuterCheckRect.GetWidth() - nCtrlHeight) / 2 ); + aTmpPos.setY( aOuterCheckRect.Top() + (aOuterCheckRect.GetHeight() - nCtrlHeight) / 2 ); + + tools::Rectangle aCheckRect(aTmpPos, Size(nCtrlHeight, nCtrlHeight)); + Size aSz(pData->aSz); + aSz.setWidth( aOutSz.Width() - 2 * nOuterSpaceX ); + tools::Rectangle aItemRect(aPos, aSz); + MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect); + rRenderContext.DrawNativeControl(ControlType::MenuPopup, nPart, aCheckRect, + nState, aVal, OUString()); + } + else if (pData->bChecked) // by default do nothing for unchecked items + { + ImplPaintCheckBackground(rRenderContext, *pWindow, aOuterCheckRect, pThisItemOnly && bHighlighted); + + SymbolType eSymbol; + Size aSymbolSize; + if (pData->nBits & MenuItemBits::RADIOCHECK) + { + eSymbol = SymbolType::RADIOCHECKMARK; + aSymbolSize = Size(nFontHeight / 2, nFontHeight / 2); + } + else + { + eSymbol = SymbolType::CHECKMARK; + aSymbolSize = Size((nFontHeight * 25) / 40, nFontHeight / 2); + } + aTmpPos.setX( aOuterCheckRect.Left() + (aOuterCheckRect.GetWidth() - aSymbolSize.Width()) / 2 ); + aTmpPos.setY( aOuterCheckRect.Top() + (aOuterCheckRect.GetHeight() - aSymbolSize.Height()) / 2 ); + tools::Rectangle aRect(aTmpPos, aSymbolSize); + aDecoView.DrawSymbol(aRect, eSymbol, rRenderContext.GetTextColor(), nSymbolStyle); + } + } + } + + // Image: + if (!bLayout && !IsMenuBar() && ((pData->eType == MenuItemType::IMAGE) || (pData->eType == MenuItemType::STRINGIMAGE))) + { + // Don't render an image for a check thing + if (pData->bChecked) + ImplPaintCheckBackground(rRenderContext, *pWindow, aOuterCheckRect, pThisItemOnly && bHighlighted); + + Image aImage = pData->aImage; + + aTmpPos = aOuterCheckRect.TopLeft(); + aTmpPos.AdjustX((aOuterCheckRect.GetWidth() - aImage.GetSizePixel().Width()) / 2 ); + aTmpPos.AdjustY((aOuterCheckRect.GetHeight() - aImage.GetSizePixel().Height()) / 2 ); + rRenderContext.DrawImage(aTmpPos, aImage, nImageStyle); + } + + // Text: + if ((pData->eType == MenuItemType::STRING ) || (pData->eType == MenuItemType::STRINGIMAGE)) + { + aTmpPos.setX( aPos.X() + nTextPos ); + aTmpPos.setY( aPos.Y() ); + aTmpPos.AdjustY(nTextOffsetY ); + DrawTextFlags nStyle = nTextStyle | DrawTextFlags::Mnemonic; + + if (pData->bIsTemporary) + nStyle |= DrawTextFlags::Disable; + std::vector< tools::Rectangle >* pVector = bLayout ? &mpLayoutData->m_aUnicodeBoundRects : nullptr; + OUString* pDisplayText = bLayout ? &mpLayoutData->m_aDisplayText : nullptr; + if (bLayout) + { + mpLayoutData->m_aLineIndices.push_back(mpLayoutData->m_aDisplayText.getLength()); + mpLayoutData->m_aLineItemIds.push_back(pData->nId); + } + // #i47946# with NWF painted menus the background is transparent + // since DrawCtrlText can depend on the background (e.g. for + // DrawTextFlags::Disable), temporarily set a background which + // hopefully matches the NWF background since it is read + // from the system style settings + bool bSetTmpBackground = !rRenderContext.IsBackground() + && rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire); + if (bSetTmpBackground) + { + Color aBg = IsMenuBar() ? rRenderContext.GetSettings().GetStyleSettings().GetMenuBarColor() + : rRenderContext.GetSettings().GetStyleSettings().GetMenuColor(); + rRenderContext.SetBackground(Wallpaper(aBg)); + } + // how much space is there for the text? + tools::Long nMaxItemTextWidth = aOutSz.Width() - aTmpPos.X() - nExtra - nOuterSpaceX; + if (!IsMenuBar() && pData->aAccelKey.GetCode() && !ImplAccelDisabled()) + { + OUString aAccText = pData->aAccelKey.GetName(); + nMaxItemTextWidth -= rRenderContext.GetTextWidth(aAccText) + 3 * nExtra; + } + if (!IsMenuBar() && pData->pSubMenu) + { + nMaxItemTextWidth -= nFontHeight - nExtra; + } + + OUString aItemText(pData->aText); + pData->bHiddenOnGUI = false; + + if (IsMenuBar()) // In case of menubar if we are out of bounds we shouldn't paint the item + { + if (nMaxItemTextWidth < rRenderContext.GetTextWidth(aItemText)) + { + aItemText = ""; + pData->bHiddenOnGUI = true; + bHiddenItems = true; + } + } + else + { + aItemText = getShortenedString(aItemText, rRenderContext, nMaxItemTextWidth); + pData->bHiddenOnGUI = false; + } + + const SalLayoutGlyphs* pGlyphs = pData->GetTextGlyphs(&rRenderContext); + if (aItemText != pData->aText) + // Can't use pre-computed glyphs, item text was + // changed. + pGlyphs = nullptr; + rRenderContext.DrawCtrlText(aTmpPos, aItemText, 0, aItemText.getLength(), + nStyle, pVector, pDisplayText, pGlyphs); + if (bSetTmpBackground) + rRenderContext.SetBackground(); + } + + // Accel + if (!bLayout && !IsMenuBar() && pData->aAccelKey.GetCode() && !ImplAccelDisabled()) + { + OUString aAccText = pData->aAccelKey.GetName(); + aTmpPos.setX( aOutSz.Width() - rRenderContext.GetTextWidth(aAccText) ); + aTmpPos.AdjustX( -(4 * nExtra) ); + + aTmpPos.AdjustX( -nOuterSpaceX ); + aTmpPos.setY( aPos.Y() ); + aTmpPos.AdjustY(nTextOffsetY ); + rRenderContext.DrawCtrlText(aTmpPos, aAccText, 0, aAccText.getLength(), nTextStyle); + } + + // SubMenu? + if (!bLayout && !IsMenuBar() && pData->pSubMenu) + { + bool bNativeOk = false; + if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::SubmenuArrow)) + { + ControlState nState = ControlState::NONE; + Size aTmpSz(0, 0); + tools::Long aSpacing = 0; + + if (!ImplGetNativeSubmenuArrowSize(rRenderContext, aTmpSz, aSpacing)) + { + aTmpSz = Size(nFontHeight, nFontHeight); + aSpacing = nOuterSpaceX; + } + + if (pData->bEnabled && pWindow->IsEnabled()) + nState |= ControlState::ENABLED; + if (bHighlighted) + nState |= ControlState::SELECTED; + + aTmpPos.setX( aOutSz.Width() - aTmpSz.Width() - aSpacing - nOuterSpaceX ); + aTmpPos.setY( aPos.Y() + ( pData->aSz.Height() - aTmpSz.Height() ) / 2 ); + aTmpPos.AdjustY(nExtra / 2 ); + + tools::Rectangle aItemRect(aTmpPos, aTmpSz); + MenupopupValue aVal(nTextPos - GUTTERBORDER, aItemRect); + bNativeOk = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::SubmenuArrow, + aItemRect, nState, aVal, OUString()); + } + if (!bNativeOk) + { + aTmpPos.setX( aOutSz.Width() - nFontHeight + nExtra - nOuterSpaceX ); + aTmpPos.setY( aPos.Y() ); + aTmpPos.AdjustY(nExtra/2 ); + aTmpPos.AdjustY((pData->aSz.Height() / 2) - (nFontHeight / 4) ); + if (pData->nBits & MenuItemBits::POPUPSELECT) + { + rRenderContext.SetTextColor(rSettings.GetMenuTextColor()); + Point aTmpPos2(aPos); + aTmpPos2.setX( aOutSz.Width() - nFontHeight - nFontHeight/4 ); + aDecoView.DrawFrame(tools::Rectangle(aTmpPos2, Size(nFontHeight + nFontHeight / 4, + pData->aSz.Height())), + DrawFrameStyle::Group); + } + aDecoView.DrawSymbol(tools::Rectangle(aTmpPos, Size(nFontHeight / 2, nFontHeight / 2)), + SymbolType::SPIN_RIGHT, rRenderContext.GetTextColor(), nSymbolStyle); + } + } + + if (pThisItemOnly && bHighlighted) + { + // This restores the normal menu or menu bar text + // color for when it is no longer highlighted. + if (IsMenuBar()) + rRenderContext.SetTextColor(rSettings.GetMenuBarTextColor()); + else + rRenderContext.SetTextColor(rSettings.GetMenuTextColor()); + } + } + if( bLayout ) + { + if (!IsMenuBar()) + mpLayoutData->m_aVisibleItemBoundRects[ n ] = tools::Rectangle(aTopLeft, Size(aOutSz.Width(), pData->aSz.Height())); + else + mpLayoutData->m_aVisibleItemBoundRects[ n ] = tools::Rectangle(aTopLeft, pData->aSz); + } + } + + if (!IsMenuBar()) + aTopLeft.AdjustY(pData->aSz.Height() ); + else + aTopLeft.AdjustX(pData->aSz.Width() ); + } + + // draw "more" (">>") indicator if some items have been hidden as they go out of visible area + if (bHiddenItems) + { + sal_Int32 nSize = nFontHeight; + tools::Rectangle aRectangle(Point(aOutSz.Width() - nSize, (aOutSz.Height() / 2) - (nSize / 2)), Size(nSize, nSize)); + lclDrawMoreIndicator(rRenderContext, aRectangle); + } +} + +Menu* Menu::ImplGetStartMenu() +{ + Menu* pStart = this; + while ( pStart && pStart->pStartedFrom && ( pStart->pStartedFrom != pStart ) ) + pStart = pStart->pStartedFrom; + return pStart; +} + +void Menu::ImplCallHighlight(sal_uInt16 nItem) +{ + ImplMenuDelData aDelData( this ); + + nSelectedId = 0; + sSelectedIdent.clear(); + MenuItemData* pData = pItemList->GetDataFromPos(nItem); + if (pData) + { + nSelectedId = pData->nId; + sSelectedIdent = pData->sIdent; + } + ImplCallEventListeners( VclEventId::MenuHighlight, GetItemPos( GetCurItemId() ) ); + + if( !aDelData.isDeleted() ) + { + nSelectedId = 0; + sSelectedIdent.clear(); + } +} + +IMPL_LINK_NOARG(Menu, ImplCallSelect, void*, void) +{ + nEventId = nullptr; + Select(); +} + +Menu* Menu::ImplFindSelectMenu() +{ + Menu* pSelMenu = nEventId ? this : nullptr; + + for ( size_t n = GetItemList()->size(); n && !pSelMenu; ) + { + MenuItemData* pData = GetItemList()->GetDataFromPos( --n ); + + if ( pData->pSubMenu ) + pSelMenu = pData->pSubMenu->ImplFindSelectMenu(); + } + + return pSelMenu; +} + +Menu* Menu::ImplFindMenu( sal_uInt16 nItemId ) +{ + Menu* pSelMenu = nullptr; + + for ( size_t n = GetItemList()->size(); n && !pSelMenu; ) + { + MenuItemData* pData = GetItemList()->GetDataFromPos( --n ); + + if( pData->nId == nItemId ) + pSelMenu = this; + else if ( pData->pSubMenu ) + pSelMenu = pData->pSubMenu->ImplFindMenu( nItemId ); + } + + return pSelMenu; +} + +void Menu::RemoveDisabledEntries( bool bRemoveEmptyPopups ) +{ + for ( sal_uInt16 n = 0; n < GetItemCount(); n++ ) + { + bool bRemove = false; + MenuItemData* pItem = pItemList->GetDataFromPos( n ); + if ( pItem->eType == MenuItemType::SEPARATOR ) + { + if ( !n || ( GetItemType( n-1 ) == MenuItemType::SEPARATOR ) ) + bRemove = true; + } + else + bRemove = !pItem->bEnabled; + + if ( pItem->pSubMenu ) + { + pItem->pSubMenu->RemoveDisabledEntries(); + if ( bRemoveEmptyPopups && !pItem->pSubMenu->GetItemCount() ) + bRemove = true; + } + + if ( bRemove ) + RemoveItem( n-- ); + } + + if ( GetItemCount() ) + { + sal_uInt16 nLast = GetItemCount() - 1; + MenuItemData* pItem = pItemList->GetDataFromPos( nLast ); + if ( pItem->eType == MenuItemType::SEPARATOR ) + RemoveItem( nLast ); + } + mpLayoutData.reset(); +} + +void Menu::UpdateNativeMenu() +{ + if ( ImplGetSalMenu() ) + ImplGetSalMenu()->Update(); +} + +void Menu::MenuBarKeyInput(const KeyEvent&) +{ +} + +void Menu::ImplKillLayoutData() const +{ + mpLayoutData.reset(); +} + +void Menu::ImplFillLayoutData() const +{ + if (!(pWindow && pWindow->IsReallyVisible())) + return; + + mpLayoutData.reset(new MenuLayoutData); + if (IsMenuBar()) + { + ImplPaint(*pWindow->GetOutDev(), pWindow->GetOutputSizePixel(), 0, 0, nullptr, false, true); // FIXME + } + else + { + MenuFloatingWindow* pFloat = static_cast<MenuFloatingWindow*>(pWindow.get()); + ImplPaint(*pWindow->GetOutDev(), pWindow->GetOutputSizePixel(), pFloat->nScrollerHeight, pFloat->ImplGetStartY(), + nullptr, false, true); //FIXME + } +} + +tools::Rectangle Menu::GetCharacterBounds( sal_uInt16 nItemID, tools::Long nIndex ) const +{ + tools::Long nItemIndex = -1; + if( ! mpLayoutData ) + ImplFillLayoutData(); + if( mpLayoutData ) + { + for( size_t i = 0; i < mpLayoutData->m_aLineItemIds.size(); i++ ) + { + if( mpLayoutData->m_aLineItemIds[i] == nItemID ) + { + nItemIndex = mpLayoutData->m_aLineIndices[i]; + break; + } + } + } + return (mpLayoutData && nItemIndex != -1) ? mpLayoutData->GetCharacterBounds( nItemIndex+nIndex ) : tools::Rectangle(); +} + +tools::Long Menu::GetIndexForPoint( const Point& rPoint, sal_uInt16& rItemID ) const +{ + tools::Long nIndex = -1; + rItemID = 0; + if( ! mpLayoutData ) + ImplFillLayoutData(); + if( mpLayoutData ) + { + nIndex = mpLayoutData->GetIndexForPoint( rPoint ); + for( size_t i = 0; i < mpLayoutData->m_aLineIndices.size(); i++ ) + { + if( mpLayoutData->m_aLineIndices[i] <= nIndex && + (i == mpLayoutData->m_aLineIndices.size()-1 || mpLayoutData->m_aLineIndices[i+1] > nIndex) ) + { + // make index relative to item + nIndex -= mpLayoutData->m_aLineIndices[i]; + rItemID = mpLayoutData->m_aLineItemIds[i]; + break; + } + } + } + return nIndex; +} + +tools::Rectangle Menu::GetBoundingRectangle( sal_uInt16 nPos ) const +{ + tools::Rectangle aRet; + + if (!mpLayoutData ) + ImplFillLayoutData(); + if (mpLayoutData) + { + std::map< sal_uInt16, tools::Rectangle >::const_iterator it = mpLayoutData->m_aVisibleItemBoundRects.find( nPos ); + if( it != mpLayoutData->m_aVisibleItemBoundRects.end() ) + aRet = it->second; + } + return aRet; +} + +void Menu::SetAccessibleName( sal_uInt16 nItemId, const OUString& rStr ) +{ + size_t nPos; + MenuItemData* pData = pItemList->GetData( nItemId, nPos ); + + if (pData && !rStr.equals(pData->aAccessibleName)) + { + pData->aAccessibleName = rStr; + ImplCallEventListeners(VclEventId::MenuAccessibleNameChanged, nPos); + } +} + +OUString Menu::GetAccessibleName( sal_uInt16 nItemId ) const +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if ( pData ) + return pData->aAccessibleName; + + return OUString(); +} + +void Menu::SetAccessibleDescription( sal_uInt16 nItemId, const OUString& rStr ) +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if ( pData ) + pData->aAccessibleDescription = rStr; +} + +OUString Menu::GetAccessibleDescription( sal_uInt16 nItemId ) const +{ + MenuItemData* pData = pItemList->GetData( nItemId ); + + if (pData && !pData->aAccessibleDescription.isEmpty()) + return pData->aAccessibleDescription; + + return GetHelpText(nItemId); +} + +void Menu::GetSystemMenuData( SystemMenuData* pData ) const +{ + Menu* pMenu = const_cast<Menu*>(this); + if( pData && pMenu->ImplGetSalMenu() ) + { + pMenu->ImplGetSalMenu()->GetSystemMenuData( pData ); + } +} + +bool Menu::IsHighlighted( sal_uInt16 nItemPos ) const +{ + bool bRet = false; + + if( pWindow ) + { + if (IsMenuBar()) + bRet = ( nItemPos == static_cast< MenuBarWindow * > (pWindow.get())->GetHighlightedItem() ); + else + bRet = ( nItemPos == static_cast< MenuFloatingWindow * > (pWindow.get())->GetHighlightedItem() ); + } + + return bRet; +} + +void Menu::HighlightItem( sal_uInt16 nItemPos ) +{ + if ( !pWindow ) + return; + + if (IsMenuBar()) + { + MenuBarWindow* pMenuWin = static_cast< MenuBarWindow* >( pWindow.get() ); + pMenuWin->SetAutoPopup( false ); + pMenuWin->ChangeHighlightItem( nItemPos, false ); + } + else + { + static_cast< MenuFloatingWindow* >( pWindow.get() )->ChangeHighlightItem( nItemPos, false ); + } +} + +MenuBarWindow* MenuBar::getMenuBarWindow() +{ + // so far just a dynamic_cast, hopefully to be turned into something saner + // at some stage + MenuBarWindow *pWin = dynamic_cast<MenuBarWindow*>(pWindow.get()); + //either there is no window (fdo#87663) or it is a MenuBarWindow + assert(!pWindow || pWin); + return pWin; +} + +MenuBar::MenuBar() + : mbCloseBtnVisible(false), + mbFloatBtnVisible(false), + mbHideBtnVisible(false), + mbDisplayable(true) +{ + mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(true, this); +} + +MenuBar::MenuBar( const MenuBar& rMenu ) + : mbCloseBtnVisible(false), + mbFloatBtnVisible(false), + mbHideBtnVisible(false), + mbDisplayable(true) +{ + mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(true, this); + *this = rMenu; +} + +MenuBar::~MenuBar() +{ + disposeOnce(); +} + +void MenuBar::dispose() +{ + ImplDestroy( this, true ); + Menu::dispose(); +} + +void MenuBar::ClosePopup(Menu *pMenu) +{ + MenuBarWindow* pMenuWin = getMenuBarWindow(); + if (!pMenuWin) + return; + pMenuWin->PopupClosed(pMenu); +} + +void MenuBar::MenuBarKeyInput(const KeyEvent& rEvent) +{ + pWindow->KeyInput(rEvent); +} + +void MenuBar::ShowCloseButton(bool bShow) +{ + ShowButtons( bShow, mbFloatBtnVisible, mbHideBtnVisible ); +} + +void MenuBar::ShowButtons( bool bClose, bool bFloat, bool bHide ) +{ + if ((bClose != mbCloseBtnVisible) || + (bFloat != mbFloatBtnVisible) || + (bHide != mbHideBtnVisible)) + { + mbCloseBtnVisible = bClose; + mbFloatBtnVisible = bFloat; + mbHideBtnVisible = bHide; + MenuBarWindow* pMenuWin = getMenuBarWindow(); + if (pMenuWin) + pMenuWin->ShowButtons(bClose, bFloat, bHide); + } +} + +void MenuBar::LayoutChanged() +{ + MenuBarWindow* pMenuWin = getMenuBarWindow(); + if (pMenuWin) + pMenuWin->LayoutChanged(); +} + +void MenuBar::SetDisplayable( bool bDisplayable ) +{ + if( bDisplayable != mbDisplayable ) + { + if ( ImplGetSalMenu() ) + ImplGetSalMenu()->ShowMenuBar( bDisplayable ); + + mbDisplayable = bDisplayable; + LayoutChanged(); + } +} + +VclPtr<vcl::Window> MenuBar::ImplCreate(vcl::Window* pParent, vcl::Window* pWindow, MenuBar* pMenu) +{ + VclPtr<MenuBarWindow> pMenuBarWindow = dynamic_cast<MenuBarWindow*>(pWindow); + if (!pMenuBarWindow) + { + pWindow = pMenuBarWindow = VclPtr<MenuBarWindow>::Create( pParent ); + } + + pMenu->pStartedFrom = nullptr; + pMenu->pWindow = pWindow; + pMenuBarWindow->SetMenu(pMenu); + tools::Long nHeight = pWindow ? pMenu->ImplCalcSize(pWindow).Height() : 0; + + // depending on the native implementation or the displayable flag + // the menubar windows is suppressed (ie, height=0) + if (!pMenu->IsDisplayable() || (pMenu->ImplGetSalMenu() && pMenu->ImplGetSalMenu()->VisibleMenuBar())) + { + nHeight = 0; + } + + pMenuBarWindow->SetHeight(nHeight); + return pWindow; +} + +void MenuBar::ImplDestroy( MenuBar* pMenu, bool bDelete ) +{ + vcl::Window *pWindow = pMenu->ImplGetWindow(); + if (pWindow && bDelete) + { + MenuBarWindow* pMenuWin = pMenu->getMenuBarWindow(); + if (pMenuWin) + pMenuWin->KillActivePopup(); + pWindow->disposeOnce(); + } + pMenu->pWindow = nullptr; + if (pMenu->mpSalMenu) { + pMenu->mpSalMenu->ShowMenuBar(false); + } +} + +bool MenuBar::ImplHandleKeyEvent( const KeyEvent& rKEvent ) +{ + // No keyboard processing when our menubar is invisible + if (!IsDisplayable()) + return false; + + // No keyboard processing when system handles the menu. + SalMenu *pNativeMenu = ImplGetSalMenu(); + if (pNativeMenu && pNativeMenu->VisibleMenuBar()) + { + // Except when the event is the F6 cycle pane event and we can put our + // focus into it (i.e. the gtk3 menubar case but not the mac/unity case + // where it's not part of the application window) + if (!TaskPaneList::IsCycleKey(rKEvent.GetKeyCode())) + return false; + if (!pNativeMenu->CanGetFocus()) + return false; + } + + bool bDone = false; + // check for enabled, if this method is called from another window... + vcl::Window* pWin = ImplGetWindow(); + if (pWin && pWin->IsEnabled() && pWin->IsInputEnabled() && !pWin->IsInModalMode()) + { + MenuBarWindow* pMenuWin = getMenuBarWindow(); + bDone = pMenuWin && pMenuWin->HandleKeyEvent(rKEvent, false/*bFromMenu*/); + } + return bDone; +} + +void MenuBar::SelectItem(sal_uInt16 nId) +{ + if (!pWindow) + return; + + pWindow->GrabFocus(); + nId = GetItemPos( nId ); + + MenuBarWindow* pMenuWin = getMenuBarWindow(); + if (pMenuWin) + { + // #99705# popup the selected menu + pMenuWin->SetAutoPopup( true ); + if (ITEMPOS_INVALID != pMenuWin->GetHighlightedItem()) + { + pMenuWin->KillActivePopup(); + pMenuWin->ChangeHighlightItem( ITEMPOS_INVALID, false ); + } + if (nId != ITEMPOS_INVALID) + pMenuWin->ChangeHighlightItem( nId, false ); + } +} + +// handler for native menu selection and command events +bool Menu::HandleMenuActivateEvent( Menu *pMenu ) const +{ + if( pMenu ) + { + ImplMenuDelData aDelData( this ); + + pMenu->pStartedFrom = const_cast<Menu*>(this); + pMenu->bInCallback = true; + pMenu->Activate(); + + if( !aDelData.isDeleted() ) + pMenu->bInCallback = false; + } + return true; +} + +bool Menu::HandleMenuDeActivateEvent( Menu *pMenu ) const +{ + if( pMenu ) + { + ImplMenuDelData aDelData( this ); + + pMenu->pStartedFrom = const_cast<Menu*>(this); + pMenu->bInCallback = true; + pMenu->Deactivate(); + if( !aDelData.isDeleted() ) + pMenu->bInCallback = false; + } + return true; +} + +bool MenuBar::HandleMenuHighlightEvent( Menu *pMenu, sal_uInt16 nHighlightEventId ) const +{ + if( !pMenu ) + pMenu = const_cast<MenuBar*>(this)->ImplFindMenu(nHighlightEventId); + if( pMenu ) + { + ImplMenuDelData aDelData( pMenu ); + + if( mnHighlightedItemPos != ITEMPOS_INVALID ) + pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, mnHighlightedItemPos ); + + if( !aDelData.isDeleted() ) + { + pMenu->mnHighlightedItemPos = pMenu->GetItemPos( nHighlightEventId ); + pMenu->nSelectedId = nHighlightEventId; + pMenu->sSelectedIdent = pMenu->GetItemIdent( nHighlightEventId ); + pMenu->pStartedFrom = const_cast<MenuBar*>(this); + pMenu->ImplCallHighlight( pMenu->mnHighlightedItemPos ); + } + return true; + } + else + return false; +} + +bool Menu::HandleMenuCommandEvent( Menu *pMenu, sal_uInt16 nCommandEventId ) const +{ + if( !pMenu ) + pMenu = const_cast<Menu*>(this)->ImplFindMenu(nCommandEventId); + if( pMenu ) + { + pMenu->nSelectedId = nCommandEventId; + pMenu->sSelectedIdent = pMenu->GetItemIdent(nCommandEventId); + pMenu->pStartedFrom = const_cast<Menu*>(this); + pMenu->ImplSelect(); + return true; + } + else + return false; +} + +sal_uInt16 MenuBar::AddMenuBarButton( const Image& i_rImage, const Link<MenuBarButtonCallbackArg&,bool>& i_rLink, const OUString& i_rToolTip ) +{ + MenuBarWindow* pMenuWin = getMenuBarWindow(); + return pMenuWin ? pMenuWin->AddMenuBarButton(i_rImage, i_rLink, i_rToolTip) : 0; +} + +void MenuBar::SetMenuBarButtonHighlightHdl( sal_uInt16 nId, const Link<MenuBarButtonCallbackArg&,bool>& rLink ) +{ + MenuBarWindow* pMenuWin = getMenuBarWindow(); + if (!pMenuWin) + return; + pMenuWin->SetMenuBarButtonHighlightHdl(nId, rLink); +} + +void MenuBar::RemoveMenuBarButton( sal_uInt16 nId ) +{ + MenuBarWindow* pMenuWin = getMenuBarWindow(); + if (!pMenuWin) + return; + pMenuWin->RemoveMenuBarButton(nId); +} + +tools::Rectangle MenuBar::GetMenuBarButtonRectPixel( sal_uInt16 nId ) +{ + MenuBarWindow* pMenuWin = getMenuBarWindow(); + return pMenuWin ? pMenuWin->GetMenuBarButtonRectPixel(nId) : tools::Rectangle(); +} + +bool MenuBar::HandleMenuButtonEvent( sal_uInt16 i_nButtonId ) +{ + MenuBarWindow* pMenuWin = getMenuBarWindow(); + return pMenuWin && pMenuWin->HandleMenuButtonEvent(i_nButtonId); +} + +int MenuBar::GetMenuBarHeight() const +{ + MenuBar* pMenuBar = const_cast<MenuBar*>(this); + const SalMenu *pNativeMenu = pMenuBar->ImplGetSalMenu(); + int nMenubarHeight; + if (pNativeMenu) + nMenubarHeight = pNativeMenu->GetMenuBarHeight(); + else + { + vcl::Window* pMenubarWin = GetWindow(); + nMenubarHeight = pMenubarWin ? pMenubarWin->GetOutputSizePixel().Height() : 0; + } + return nMenubarHeight; +} + +// bool PopupMenu::bAnyPopupInExecute = false; + +MenuFloatingWindow * PopupMenu::ImplGetFloatingWindow() const { + return static_cast<MenuFloatingWindow *>(Menu::ImplGetWindow()); +} + +PopupMenu::PopupMenu() +{ + mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(false, this); +} + +PopupMenu::PopupMenu( const PopupMenu& rMenu ) +{ + mpSalMenu = ImplGetSVData()->mpDefInst->CreateMenu(false, this); + *this = rMenu; +} + +PopupMenu::~PopupMenu() +{ + disposeOnce(); +} + +void PopupMenu::ClosePopup(Menu* pMenu) +{ + MenuFloatingWindow* p = dynamic_cast<MenuFloatingWindow*>(ImplGetWindow()); + PopupMenu *pPopup = dynamic_cast<PopupMenu*>(pMenu); + if (p && pPopup) + p->KillActivePopup(pPopup); +} + +namespace vcl +{ + bool IsInPopupMenuExecute() + { + return PopupMenu::GetActivePopupMenu() != nullptr; + } +} + +PopupMenu* PopupMenu::GetActivePopupMenu() +{ + ImplSVData* pSVData = ImplGetSVData(); + return pSVData->maAppData.mpActivePopupMenu; +} + +void PopupMenu::EndExecute() +{ + if ( ImplGetWindow() ) + ImplGetFloatingWindow()->EndExecute( 0 ); +} + +void PopupMenu::SelectItem(sal_uInt16 nId) +{ + if ( !ImplGetWindow() ) + return; + + if( nId != ITEMPOS_INVALID ) + { + size_t nPos = 0; + MenuItemData* pData = GetItemList()->GetData( nId, nPos ); + if (pData && pData->pSubMenu) + ImplGetFloatingWindow()->ChangeHighlightItem( nPos, true ); + else + ImplGetFloatingWindow()->EndExecute( nId ); + } + else + { + MenuFloatingWindow* pFloat = ImplGetFloatingWindow(); + pFloat->GrabFocus(); + + for( size_t nPos = 0; nPos < GetItemList()->size(); nPos++ ) + { + MenuItemData* pData = GetItemList()->GetDataFromPos( nPos ); + if( pData->pSubMenu ) + { + pFloat->KillActivePopup(); + } + } + pFloat->ChangeHighlightItem( ITEMPOS_INVALID, false ); + } +} + +void PopupMenu::SetSelectedEntry( sal_uInt16 nId ) +{ + nSelectedId = nId; + sSelectedIdent = GetItemIdent(nId); +} + +sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const Point& rPopupPos ) +{ + return Execute( pExecWindow, tools::Rectangle( rPopupPos, rPopupPos ), PopupMenuFlags::ExecuteDown ); +} + +sal_uInt16 PopupMenu::Execute( vcl::Window* pExecWindow, const tools::Rectangle& rRect, PopupMenuFlags nFlags ) +{ + ENSURE_OR_RETURN( pExecWindow, "PopupMenu::Execute: need a non-NULL window!", 0 ); + + FloatWinPopupFlags nPopupModeFlags = FloatWinPopupFlags::NONE; + if ( nFlags & PopupMenuFlags::ExecuteDown ) + nPopupModeFlags = FloatWinPopupFlags::Down; + else if ( nFlags & PopupMenuFlags::ExecuteUp ) + nPopupModeFlags = FloatWinPopupFlags::Up; + else if ( nFlags & PopupMenuFlags::ExecuteRight ) + nPopupModeFlags = FloatWinPopupFlags::Right; + else + nPopupModeFlags = FloatWinPopupFlags::Down; + + if (nFlags & PopupMenuFlags::NoMouseUpClose ) // allow popup menus to stay open on mouse button up + nPopupModeFlags |= FloatWinPopupFlags::NoMouseUpClose; // useful if the menu was opened on mousebutton down (eg toolbox configuration) + + return ImplExecute( pExecWindow, rRect, nPopupModeFlags, nullptr, false ); +} + +void PopupMenu::ImplFlushPendingSelect() +{ + // is there still Select? + Menu* pSelect = ImplFindSelectMenu(); + if (pSelect) + { + // Select should be called prior to leaving execute in a popup menu! + Application::RemoveUserEvent( pSelect->nEventId ); + pSelect->nEventId = nullptr; + pSelect->Select(); + } +} + +sal_uInt16 PopupMenu::ImplExecute( const VclPtr<vcl::Window>& pW, const tools::Rectangle& rRect, FloatWinPopupFlags nPopupModeFlags, Menu* pSFrom, bool bPreSelectFirst ) +{ + if ( !pSFrom && ( vcl::IsInPopupMenuExecute() || !GetItemCount() ) ) + return 0; + + mpLayoutData.reset(); + + ImplSVData* pSVData = ImplGetSVData(); + + pStartedFrom = pSFrom; + nSelectedId = 0; + sSelectedIdent.clear(); + bCanceled = false; + + VclPtr<vcl::Window> xFocusId; + bool bRealExecute = false; + if ( !pStartedFrom ) + { + pSVData->mpWinData->mbNoDeactivate = true; + xFocusId = Window::SaveFocus(); + bRealExecute = true; + } + else + { + // assure that only one menu is open at a time + if (pStartedFrom->IsMenuBar() && pSVData->mpWinData->mpFirstFloat) + pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel + | FloatWinPopupEndFlags::CloseAll); + } + + SAL_WARN_IF( ImplGetWindow(), "vcl", "Win?!" ); + tools::Rectangle aRect( rRect ); + aRect.SetPos( pW->OutputToScreenPixel( aRect.TopLeft() ) ); + + if (bRealExecute) + nPopupModeFlags |= FloatWinPopupFlags::NewLevel; + nPopupModeFlags |= FloatWinPopupFlags::NoKeyClose | FloatWinPopupFlags::AllMouseButtonClose; + + bInCallback = true; // set it here, if Activate overridden + Activate(); + bInCallback = false; + + if ( pW->isDisposed() ) + return 0; // Error + + if ( bCanceled || bKilled ) + return 0; + + if ( !GetItemCount() ) + return 0; + + // The flag MenuFlags::HideDisabledEntries is inherited. + if ( pSFrom ) + { + if ( pSFrom->nMenuFlags & MenuFlags::HideDisabledEntries ) + nMenuFlags |= MenuFlags::HideDisabledEntries; + else + nMenuFlags &= ~MenuFlags::HideDisabledEntries; + } + else + // #102790# context menus shall never show disabled entries + nMenuFlags |= MenuFlags::HideDisabledEntries; + + sal_uInt16 nVisibleEntries = ImplGetVisibleItemCount(); + if ( !nVisibleEntries ) + { + OUString aTmpEntryText(VclResId(SV_RESID_STRING_NOSELECTIONPOSSIBLE)); + + MenuItemData* pData = NbcInsertItem(0xFFFF, MenuItemBits::NONE, aTmpEntryText, nullptr, 0xFFFF, OString()); + size_t nPos = 0; + pData = pItemList->GetData( pData->nId, nPos ); + assert(pData); + if (pData) + { + pData->bIsTemporary = true; + } + ImplCallEventListeners(VclEventId::MenuSubmenuChanged, nPos); + } + + VclPtrInstance<MenuFloatingWindow> pWin( this, pW, WB_BORDER | WB_SYSTEMWINDOW ); + if (comphelper::LibreOfficeKit::isActive() && get_id() == "editviewspellmenu") + { + VclPtr<vcl::Window> xNotifierParent = pW->GetParentWithLOKNotifier(); + assert(xNotifierParent && xNotifierParent->GetLOKNotifier() && "editview menu without LOKNotifier"); + pWin->SetLOKNotifier(xNotifierParent->GetLOKNotifier()); + } + + if( pSVData->maNWFData.mbFlatMenu ) + pWin->SetBorderStyle( WindowBorderStyle::NOBORDER ); + else + pWin->SetBorderStyle( pWin->GetBorderStyle() | WindowBorderStyle::MENU ); + pWindow = pWin; + + Size aSz = ImplCalcSize( pWin ); + + tools::Rectangle aDesktopRect(pWin->GetDesktopRectPixel()); + if( Application::GetScreenCount() > 1 && Application::IsUnifiedDisplay() ) + { + vcl::Window* pDeskW = pWindow->GetWindow( GetWindowType::RealParent ); + if( ! pDeskW ) + pDeskW = pWindow; + Point aDesktopTL( pDeskW->OutputToAbsoluteScreenPixel( aRect.TopLeft() ) ); + aDesktopRect = Application::GetScreenPosSizePixel( + Application::GetBestScreen( tools::Rectangle( aDesktopTL, aRect.GetSize() ) )); + } + + tools::Long nMaxHeight = aDesktopRect.GetHeight(); + + //rhbz#1021915. If a menu won't fit in the desired location the default + //mode is to place it somewhere it will fit. e.g. above, left, right. For + //some cases, e.g. menubars, it's desirable to limit the options to + //above/below and force the menu to scroll if it won't fit + if (nPopupModeFlags & FloatWinPopupFlags::NoHorzPlacement) + { + vcl::Window* pRef = pWin; + if ( pRef->GetParent() ) + pRef = pRef->GetParent(); + + tools::Rectangle devRect( pRef->OutputToAbsoluteScreenPixel( aRect.TopLeft() ), + pRef->OutputToAbsoluteScreenPixel( aRect.BottomRight() ) ); + + tools::Long nHeightAbove = devRect.Top() - aDesktopRect.Top(); + tools::Long nHeightBelow = aDesktopRect.Bottom() - devRect.Bottom(); + nMaxHeight = std::min(nMaxHeight, std::max(nHeightAbove, nHeightBelow)); + } + + // In certain cases this might be misdetected with a height of 0, leading to menus not being displayed. + // So assume that the available screen size matches at least the system requirements + SAL_WARN_IF(nMaxHeight < 768, "vcl", + "Available height misdetected as " << nMaxHeight + << "px. Setting to 768px instead."); + nMaxHeight = std::max(nMaxHeight, tools::Long(768)); + + if (pStartedFrom && pStartedFrom->IsMenuBar()) + nMaxHeight -= pW->GetSizePixel().Height(); + sal_Int32 nLeft, nTop, nRight, nBottom; + pWindow->GetBorder( nLeft, nTop, nRight, nBottom ); + nMaxHeight -= nTop+nBottom; + if ( aSz.Height() > nMaxHeight ) + { + pWin->EnableScrollMenu( true ); + sal_uInt16 nStart = ImplGetFirstVisible(); + sal_uInt16 nEntries = ImplCalcVisEntries( nMaxHeight, nStart ); + aSz.setHeight( ImplCalcHeight( nEntries ) ); + } + + // tdf#126054 hold this until after function completes + VclPtr<PopupMenu> xThis(this); + + pWin->SetFocusId( xFocusId ); + pWin->SetOutputSizePixel( aSz ); + if ( GetItemCount() ) + { + SalMenu* pMenu = ImplGetSalMenu(); + if( pMenu && bRealExecute && pMenu->ShowNativePopupMenu( pWin, aRect, nPopupModeFlags | FloatWinPopupFlags::GrabFocus ) ) + { + pWin->StopExecute(); + pWin->doShutdown(); + pWindow.disposeAndClear(); + ImplClosePopupToolBox(pW); + ImplFlushPendingSelect(); + return nSelectedId; + } + else + { + pWin->StartPopupMode( aRect, nPopupModeFlags | FloatWinPopupFlags::GrabFocus ); + } + if( pSFrom ) + { + sal_uInt16 aPos; + if (pSFrom->IsMenuBar()) + aPos = static_cast<MenuBarWindow *>(pSFrom->pWindow.get())->GetHighlightedItem(); + else + aPos = static_cast<MenuFloatingWindow *>(pSFrom->pWindow.get())->GetHighlightedItem(); + + pWin->SetPosInParent( aPos ); // store position to be sent in SUBMENUDEACTIVATE + pSFrom->ImplCallEventListeners( VclEventId::MenuSubmenuActivate, aPos ); + } + } + if ( bPreSelectFirst ) + { + size_t nCount = pItemList->size(); + for ( size_t n = 0; n < nCount; n++ ) + { + MenuItemData* pData = pItemList->GetDataFromPos( n ); + if ( ( pData->bEnabled + || !Application::GetSettings().GetStyleSettings().GetSkipDisabledInMenus() + ) + && ( pData->eType != MenuItemType::SEPARATOR ) + && ImplIsVisible( n ) + && ImplIsSelectable( n ) + ) + { + pWin->ChangeHighlightItem( n, false ); + break; + } + } + } + if ( bRealExecute ) + { + pWin->Execute(); + if (pWin->isDisposed()) + return 0; + + xFocusId = pWin->GetFocusId(); + assert(xFocusId == nullptr && "Focus should already be restored by MenuFloatingWindow::End"); + pWin->ImplEndPopupMode(FloatWinPopupEndFlags::NONE, xFocusId); + + if ( nSelectedId ) // then clean up .. ( otherwise done by TH ) + { + PopupMenu* pSub = pWin->GetActivePopup(); + while ( pSub ) + { + pSub->ImplGetFloatingWindow()->EndPopupMode(); + pSub = pSub->ImplGetFloatingWindow()->GetActivePopup(); + } + } + pWin->doShutdown(); + pWindow.disposeAndClear(); + ImplClosePopupToolBox(pW); + ImplFlushPendingSelect(); + } + + return bRealExecute ? nSelectedId : 0; +} + +sal_uInt16 PopupMenu::ImplCalcVisEntries( tools::Long nMaxHeight, sal_uInt16 nStartEntry, sal_uInt16* pLastVisible ) const +{ + nMaxHeight -= 2 * ImplGetFloatingWindow()->GetScrollerHeight(); + + tools::Long nHeight = 0; + size_t nEntries = pItemList->size(); + sal_uInt16 nVisEntries = 0; + + if ( pLastVisible ) + *pLastVisible = 0; + + for ( size_t n = nStartEntry; n < nEntries; n++ ) + { + if ( ImplIsVisible( n ) ) + { + MenuItemData* pData = pItemList->GetDataFromPos( n ); + nHeight += pData->aSz.Height(); + if ( nHeight > nMaxHeight ) + break; + + if ( pLastVisible ) + *pLastVisible = n; + nVisEntries++; + } + } + return nVisEntries; +} + +tools::Long PopupMenu::ImplCalcHeight( sal_uInt16 nEntries ) const +{ + tools::Long nHeight = 0; + + sal_uInt16 nFound = 0; + for ( size_t n = 0; ( nFound < nEntries ) && ( n < pItemList->size() ); n++ ) + { + if ( ImplIsVisible( static_cast<sal_uInt16>(n) ) ) + { + MenuItemData* pData = pItemList->GetDataFromPos( n ); + nHeight += pData->aSz.Height(); + nFound++; + } + } + + nHeight += 2*ImplGetFloatingWindow()->GetScrollerHeight(); + + return nHeight; +} + +css::uno::Reference<css::awt::XPopupMenu> PopupMenu::CreateMenuInterface() +{ + UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(); + if ( pWrapper ) + return pWrapper->CreateMenuInterface(this); + return nullptr; +} + +ImplMenuDelData::ImplMenuDelData( const Menu* pMenu ) +: mpNext( nullptr ) +, mpMenu( nullptr ) +{ + if( pMenu ) + const_cast< Menu* >( pMenu )->ImplAddDel( *this ); +} + +ImplMenuDelData::~ImplMenuDelData() +{ + if( mpMenu ) + const_cast< Menu* >( mpMenu.get() )->ImplRemoveDel( *this ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/menubarwindow.cxx b/vcl/source/window/menubarwindow.cxx new file mode 100644 index 000000000..51bad55d4 --- /dev/null +++ b/vcl/source/window/menubarwindow.cxx @@ -0,0 +1,1222 @@ +/* -*- 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 "menubarwindow.hxx" +#include "menuitemlist.hxx" +#include "menufloatingwindow.hxx" + +#include <vcl/dockingarea.hxx> +#include <vcl/settings.hxx> +#include <vcl/taskpanelist.hxx> +#include <sal/log.hxx> + +#include <salframe.hxx> +#include <salmenu.hxx> +#include <svdata.hxx> +#include <strings.hrc> +#include <bitmaps.hlst> +#include <window.h> +#include "bufferdevice.hxx" + +// document closing button +#define IID_DOCUMENTCLOSE 1 + +DecoToolBox::DecoToolBox( vcl::Window* pParent ) : + ToolBox( pParent, 0 ), + lastSize(-1) +{ + calcMinSize(); +} + +void DecoToolBox::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + + if ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE ) + { + calcMinSize(); + SetBackground(); + SetImages( 0, true); + } +} + +void DecoToolBox::calcMinSize() +{ + ScopedVclPtrInstance<ToolBox> aTbx( GetParent() ); + if( GetItemCount() == 0 ) + { + aTbx->InsertItem(ToolBoxItemId(IID_DOCUMENTCLOSE), Image(StockImage::Yes, SV_RESID_BITMAP_CLOSEDOC)); + } + else + { + ImplToolItems::size_type nItems = GetItemCount(); + for( ImplToolItems::size_type i = 0; i < nItems; i++ ) + { + ToolBoxItemId nId = GetItemId( i ); + aTbx->InsertItem( nId, GetItemImage( nId ) ); + } + } + maMinSize = aTbx->CalcWindowSizePixel(); + + aTbx.disposeAndClear(); +} + +void DecoToolBox::SetImages( tools::Long nMaxHeight, bool bForce ) +{ + tools::Long border = getMinSize().Height() - maImage.GetSizePixel().Height(); + + if( !nMaxHeight && lastSize != -1 ) + nMaxHeight = lastSize + border; // don't change anything if called with 0 + + if( nMaxHeight < getMinSize().Height() ) + nMaxHeight = getMinSize().Height(); + + if( (lastSize == nMaxHeight - border) && !bForce ) + return; + + lastSize = nMaxHeight - border; + + Color aEraseColor( ColorTransparency, 255, 255, 255, 255 ); + BitmapEx aBmpExDst( maImage.GetBitmapEx() ); + BitmapEx aBmpExSrc( aBmpExDst ); + + aEraseColor.SetAlpha( 0 ); + aBmpExDst.Erase( aEraseColor ); + aBmpExDst.Scale( Size( lastSize, lastSize ) ); + + tools::Rectangle aSrcRect( Point(0,0), maImage.GetSizePixel() ); + tools::Rectangle aDestRect( Point((lastSize - maImage.GetSizePixel().Width())/2, + (lastSize - maImage.GetSizePixel().Height())/2 ), + maImage.GetSizePixel() ); + + aBmpExDst.CopyPixel( aDestRect, aSrcRect, &aBmpExSrc ); + SetItemImage( ToolBoxItemId(IID_DOCUMENTCLOSE), Image( aBmpExDst ) ); + +} + +MenuBarWindow::MenuBarWindow( vcl::Window* pParent ) : + Window( pParent, 0 ), + m_aCloseBtn(VclPtr<DecoToolBox>::Create(this)), + m_aFloatBtn(VclPtr<PushButton>::Create(this, WB_NOPOINTERFOCUS | WB_SMALLSTYLE | WB_RECTSTYLE)), + m_aHideBtn(VclPtr<PushButton>::Create(this, WB_NOPOINTERFOCUS | WB_SMALLSTYLE | WB_RECTSTYLE)) +{ + SetType(WindowType::MENUBARWINDOW); + m_pMenu = nullptr; + m_pActivePopup = nullptr; + m_nHighlightedItem = ITEMPOS_INVALID; + m_nRolloveredItem = ITEMPOS_INVALID; + mbAutoPopup = true; + m_bIgnoreFirstMove = true; + + m_aCloseBtn->maImage = Image(StockImage::Yes, SV_RESID_BITMAP_CLOSEDOC); + + m_aCloseBtn->SetBackground(); + m_aCloseBtn->SetPaintTransparent(true); + m_aCloseBtn->SetParentClipMode(ParentClipMode::NoClip); + + m_aCloseBtn->InsertItem(ToolBoxItemId(IID_DOCUMENTCLOSE), m_aCloseBtn->maImage); + m_aCloseBtn->SetSelectHdl(LINK(this, MenuBarWindow, CloseHdl)); + m_aCloseBtn->AddEventListener(LINK(this, MenuBarWindow, ToolboxEventHdl)); + m_aCloseBtn->SetQuickHelpText(ToolBoxItemId(IID_DOCUMENTCLOSE), VclResId(SV_HELPTEXT_CLOSEDOCUMENT)); + + m_aFloatBtn->SetSymbol( SymbolType::FLOAT ); + m_aFloatBtn->SetQuickHelpText(VclResId(SV_HELPTEXT_RESTORE)); + + m_aHideBtn->SetSymbol( SymbolType::HIDE ); + m_aHideBtn->SetQuickHelpText(VclResId(SV_HELPTEXT_MINIMIZE)); + + ImplInitStyleSettings(); + + AddEventListener(LINK(this, MenuBarWindow, ShowHideListener)); +} + +MenuBarWindow::~MenuBarWindow() +{ + disposeOnce(); +} + +void MenuBarWindow::dispose() +{ + m_aCloseBtn->RemoveEventListener(LINK(this, MenuBarWindow, ToolboxEventHdl)); + RemoveEventListener(LINK(this, MenuBarWindow, ShowHideListener)); + + mpParentPopup.disposeAndClear(); + m_aHideBtn.disposeAndClear(); + m_aFloatBtn.disposeAndClear(); + m_aCloseBtn.disposeAndClear(); + m_pMenu.clear(); + m_pActivePopup.clear(); + m_xSaveFocusId.clear(); + + Window::dispose(); +} + +void MenuBarWindow::SetMenu( MenuBar* pMen ) +{ + m_pMenu = pMen; + KillActivePopup(); + m_nHighlightedItem = ITEMPOS_INVALID; + if (pMen) + { + m_aCloseBtn->ShowItem(ToolBoxItemId(IID_DOCUMENTCLOSE), pMen->HasCloseButton()); + m_aCloseBtn->Show(pMen->HasCloseButton() || !m_aAddButtons.empty()); + m_aFloatBtn->Show(pMen->HasFloatButton()); + m_aHideBtn->Show(pMen->HasHideButton()); + } + Invalidate(); + + // show and connect native menubar + if( m_pMenu && m_pMenu->ImplGetSalMenu() ) + { + if( m_pMenu->ImplGetSalMenu()->VisibleMenuBar() ) + ImplGetFrame()->SetMenu( m_pMenu->ImplGetSalMenu() ); + + m_pMenu->ImplGetSalMenu()->SetFrame( ImplGetFrame() ); + m_pMenu->ImplGetSalMenu()->ShowMenuBar(true); + } +} + +void MenuBarWindow::SetHeight(tools::Long nHeight) +{ + setPosSizePixel(0, 0, 0, nHeight, PosSizeFlags::Height); +} + +void MenuBarWindow::ShowButtons( bool bClose, bool bFloat, bool bHide ) +{ + m_aCloseBtn->ShowItem(ToolBoxItemId(IID_DOCUMENTCLOSE), bClose); + m_aCloseBtn->Show(bClose || !m_aAddButtons.empty()); + if (m_pMenu->mpSalMenu) + m_pMenu->mpSalMenu->ShowCloseButton(bClose); + m_aFloatBtn->Show( bFloat ); + m_aHideBtn->Show( bHide ); + Resize(); +} + +Size const & MenuBarWindow::MinCloseButtonSize() const +{ + return m_aCloseBtn->getMinSize(); +} + +IMPL_LINK_NOARG(MenuBarWindow, CloseHdl, ToolBox *, void) +{ + if( ! m_pMenu ) + return; + + if( m_aCloseBtn->GetCurItemId() == ToolBoxItemId(IID_DOCUMENTCLOSE) ) + { + // #i106052# call close hdl asynchronously to ease handler implementation + // this avoids still being in the handler while the DecoToolBox already + // gets destroyed + Application::PostUserEvent(static_cast<MenuBar*>(m_pMenu.get())->GetCloseButtonClickHdl()); + } + else + { + std::map<sal_uInt16,AddButtonEntry>::iterator it = m_aAddButtons.find(sal_uInt16(m_aCloseBtn->GetCurItemId())); + if( it != m_aAddButtons.end() ) + { + MenuBarButtonCallbackArg aArg; + aArg.nId = it->first; + aArg.bHighlight = (sal_uInt16(m_aCloseBtn->GetHighlightItemId()) == it->first); + it->second.m_aSelectLink.Call( aArg ); + } + } +} + +IMPL_LINK( MenuBarWindow, ToolboxEventHdl, VclWindowEvent&, rEvent, void ) +{ + if( ! m_pMenu ) + return; + + MenuBarButtonCallbackArg aArg; + aArg.nId = 0xffff; + aArg.bHighlight = (rEvent.GetId() == VclEventId::ToolboxHighlight); + if( rEvent.GetId() == VclEventId::ToolboxHighlight ) + aArg.nId =sal_uInt16(m_aCloseBtn->GetHighlightItemId()); + else if( rEvent.GetId() == VclEventId::ToolboxHighlightOff ) + { + auto nPos = static_cast<ToolBox::ImplToolItems::size_type>(reinterpret_cast<sal_IntPtr>(rEvent.GetData())); + aArg.nId = sal_uInt16(m_aCloseBtn->GetItemId(nPos)); + } + std::map< sal_uInt16, AddButtonEntry >::iterator it = m_aAddButtons.find( aArg.nId ); + if( it != m_aAddButtons.end() ) + { + it->second.m_aHighlightLink.Call( aArg ); + } +} + +IMPL_LINK( MenuBarWindow, ShowHideListener, VclWindowEvent&, rEvent, void ) +{ + if( ! m_pMenu ) + return; + + if( rEvent.GetId() == VclEventId::WindowShow ) + m_pMenu->ImplCallEventListeners( VclEventId::MenuShow, ITEMPOS_INVALID ); + else if( rEvent.GetId() == VclEventId::WindowHide ) + m_pMenu->ImplCallEventListeners( VclEventId::MenuHide, ITEMPOS_INVALID ); +} + +void MenuBarWindow::ImplCreatePopup( bool bPreSelectFirst ) +{ + MenuItemData* pItemData = m_pMenu ? m_pMenu->GetItemList()->GetDataFromPos( m_nHighlightedItem ) : nullptr; + if ( !pItemData ) + return; + + m_bIgnoreFirstMove = true; + if ( m_pActivePopup && ( m_pActivePopup != pItemData->pSubMenu ) ) + { + KillActivePopup(); + } + if ( !(pItemData->bEnabled && pItemData->pSubMenu && ( m_nHighlightedItem != ITEMPOS_INVALID ) && + ( pItemData->pSubMenu != m_pActivePopup )) ) + return; + + m_pActivePopup = static_cast<PopupMenu*>(pItemData->pSubMenu.get()); + tools::Long nX = 0; + MenuItemData* pData = nullptr; + for ( sal_uLong n = 0; n < m_nHighlightedItem; n++ ) + { + pData = m_pMenu->GetItemList()->GetDataFromPos( n ); + nX += pData->aSz.Width(); + } + pData = m_pMenu->pItemList->GetDataFromPos( m_nHighlightedItem ); + Point aItemTopLeft( nX, 0 ); + Point aItemBottomRight( aItemTopLeft ); + aItemBottomRight.AdjustX(pData->aSz.Width() ); + + if (pData->bHiddenOnGUI) + { + mpParentPopup.disposeAndClear(); + mpParentPopup = VclPtr<PopupMenu>::Create(); + m_pActivePopup = mpParentPopup.get(); + + for (sal_uInt16 i = m_nHighlightedItem; i < m_pMenu->GetItemCount(); ++i) + { + sal_uInt16 nId = m_pMenu->GetItemId(i); + + MenuItemData* pParentItemData = m_pMenu->GetItemList()->GetData(nId); + assert(pParentItemData); + mpParentPopup->InsertItem(nId, pParentItemData->aText, pParentItemData->nBits, pParentItemData->sIdent); + mpParentPopup->SetHelpId(nId, pParentItemData->aHelpId); + mpParentPopup->SetHelpText(nId, pParentItemData->aHelpText); + mpParentPopup->SetAccelKey(nId, pParentItemData->aAccelKey); + mpParentPopup->SetItemCommand(nId, pParentItemData->aCommandStr); + mpParentPopup->SetHelpCommand(nId, pParentItemData->aHelpCommandStr); + + PopupMenu* pPopup = m_pMenu->GetPopupMenu(nId); + mpParentPopup->SetPopupMenu(nId, pPopup); + } + } + // the menu bar could have height 0 in fullscreen mode: + // so do not use always WindowHeight, as ItemHeight < WindowHeight. + if ( GetSizePixel().Height() ) + { + // #107747# give menuitems the height of the menubar + aItemBottomRight.AdjustY(GetOutputSizePixel().Height()-1 ); + } + + // ImplExecute is not modal... + // #99071# do not grab the focus, otherwise it will be restored to the menubar + // when the frame is reactivated later + //GrabFocus(); + m_pActivePopup->ImplExecute( this, tools::Rectangle( aItemTopLeft, aItemBottomRight ), FloatWinPopupFlags::Down | FloatWinPopupFlags::NoHorzPlacement, m_pMenu, bPreSelectFirst ); + // does not have a window, if aborted before or if there are no entries + if ( m_pActivePopup->ImplGetFloatingWindow() ) + m_pActivePopup->ImplGetFloatingWindow()->AddPopupModeWindow( this ); + else + m_pActivePopup = nullptr; +} + +void MenuBarWindow::KillActivePopup() +{ + if ( !m_pActivePopup ) + return; + + if( m_pActivePopup->pWindow ) + if( static_cast<FloatingWindow *>(m_pActivePopup->pWindow.get())->IsInCleanUp() ) + return; // kill it later + + if ( m_pActivePopup->bInCallback ) + m_pActivePopup->bCanceled = true; + + m_pActivePopup->bInCallback = true; + m_pActivePopup->Deactivate(); + m_pActivePopup->bInCallback = false; + // check for pActivePopup, if stopped by deactivate... + if ( m_pActivePopup->ImplGetWindow() ) + { + if (mpParentPopup) + { + for (sal_uInt16 i = 0; i < mpParentPopup->GetItemCount(); ++i) + { + sal_uInt16 nId = mpParentPopup->GetItemId(i); + MenuItemData* pParentItemData = mpParentPopup->GetItemList()->GetData(nId); + assert(pParentItemData); + pParentItemData->pSubMenu = nullptr; + } + } + m_pActivePopup->ImplGetFloatingWindow()->StopExecute(); + m_pActivePopup->ImplGetFloatingWindow()->doShutdown(); + m_pActivePopup->pWindow.disposeAndClear(); + } + m_pActivePopup = nullptr; +} + +void MenuBarWindow::PopupClosed( Menu const * pPopup ) +{ + if ( pPopup == m_pActivePopup ) + { + KillActivePopup(); + ChangeHighlightItem( ITEMPOS_INVALID, false, ImplGetFrameWindow()->ImplGetFrameData()->mbHasFocus, false ); + } +} + +void MenuBarWindow::MouseButtonDown( const MouseEvent& rMEvt ) +{ + mbAutoPopup = true; + sal_uInt16 nEntry = ImplFindEntry( rMEvt.GetPosPixel() ); + if ( ( nEntry != ITEMPOS_INVALID ) && !m_pActivePopup ) + { + ChangeHighlightItem( nEntry, false ); + } + else + { + KillActivePopup(); + ChangeHighlightItem( ITEMPOS_INVALID, false ); + } +} + +void MenuBarWindow::MouseButtonUp( const MouseEvent& ) +{ +} + +void MenuBarWindow::MouseMove( const MouseEvent& rMEvt ) +{ + if ( rMEvt.IsSynthetic() || rMEvt.IsEnterWindow() ) + return; + + if ( rMEvt.IsLeaveWindow() ) + { + if ( m_nRolloveredItem != ITEMPOS_INVALID && m_nRolloveredItem != m_nHighlightedItem ) + Invalidate(); //HighlightItem( nRolloveredItem, false ); + + m_nRolloveredItem = ITEMPOS_INVALID; + return; + } + + sal_uInt16 nEntry = ImplFindEntry( rMEvt.GetPosPixel() ); + if ( m_nHighlightedItem == ITEMPOS_INVALID ) + { + if ( m_nRolloveredItem != nEntry ) + { + if ( m_nRolloveredItem != ITEMPOS_INVALID ) + Invalidate(); //HighlightItem( nRolloveredItem, false ); + + m_nRolloveredItem = nEntry; + Invalidate(); //HighlightItem( nRolloveredItem, true ); + } + return; + } + m_nRolloveredItem = nEntry; + + if( m_bIgnoreFirstMove ) + { + m_bIgnoreFirstMove = false; + return; + } + + if ( ( nEntry != ITEMPOS_INVALID ) + && ( nEntry != m_nHighlightedItem ) ) + ChangeHighlightItem( nEntry, false ); +} + +void MenuBarWindow::ChangeHighlightItem( sal_uInt16 n, bool bSelectEntry, bool bAllowRestoreFocus, bool bDefaultToDocument) +{ + if( ! m_pMenu ) + return; + + // #57934# close active popup if applicable, as TH's background storage works. + MenuItemData* pNextData = m_pMenu->pItemList->GetDataFromPos( n ); + if ( m_pActivePopup && m_pActivePopup->ImplGetWindow() && ( !pNextData || ( m_pActivePopup != pNextData->pSubMenu ) ) ) + KillActivePopup(); // pActivePopup when applicable without pWin, if Rescheduled in Activate() + + // activate menubar only ones per cycle... + bool bJustActivated = false; + if ( ( m_nHighlightedItem == ITEMPOS_INVALID ) && ( n != ITEMPOS_INVALID ) ) + { + ImplGetSVData()->mpWinData->mbNoDeactivate = true; + // #105406# avoid saving the focus when we already have the focus + bool bNoSaveFocus = (this == ImplGetSVData()->mpWinData->mpFocusWin.get()); + + if( m_xSaveFocusId != nullptr ) + { + if (!ImplGetSVData()->mpWinData->mbNoSaveFocus) + { + m_xSaveFocusId = nullptr; + if( !bNoSaveFocus ) + m_xSaveFocusId = Window::SaveFocus(); // only save focus when initially activated + } + else { + ; // do nothing: we 're activated again from taskpanelist, focus was already saved + } + } + else + { + if( !bNoSaveFocus ) + m_xSaveFocusId = Window::SaveFocus(); // only save focus when initially activated + } + m_pMenu->bInCallback = true; // set here if Activate overridden + m_pMenu->Activate(); + m_pMenu->bInCallback = false; + bJustActivated = true; + } + else if ( ( m_nHighlightedItem != ITEMPOS_INVALID ) && ( n == ITEMPOS_INVALID ) ) + { + m_pMenu->bInCallback = true; + m_pMenu->Deactivate(); + m_pMenu->bInCallback = false; + ImplGetSVData()->mpWinData->mbNoDeactivate = false; + if (!ImplGetSVData()->mpWinData->mbNoSaveFocus) + { + VclPtr<vcl::Window> xTempFocusId; + if (m_xSaveFocusId && !m_xSaveFocusId->isDisposed()) + xTempFocusId = m_xSaveFocusId; + m_xSaveFocusId = nullptr; + + if (bAllowRestoreFocus) + { + // tdf#115227 the popup is already killed, so temporarily set us as the + // focus window, so we could avoid sending superfluous activate events + // to top window listeners. + if (xTempFocusId || bDefaultToDocument) + ImplGetSVData()->mpWinData->mpFocusWin = this; + + // #105406# restore focus to document if we could not save focus before + if (!xTempFocusId && bDefaultToDocument) + GrabFocusToDocument(); + else + Window::EndSaveFocus(xTempFocusId); + } + } + } + + if ( m_nHighlightedItem != ITEMPOS_INVALID ) + { + if ( m_nHighlightedItem != m_nRolloveredItem ) + Invalidate(); //HighlightItem( nHighlightedItem, false ); + + m_pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, m_nHighlightedItem ); + } + + m_nHighlightedItem = n; + SAL_WARN_IF( ( m_nHighlightedItem != ITEMPOS_INVALID ) && !m_pMenu->ImplIsVisible( m_nHighlightedItem ), "vcl", "ChangeHighlightItem: Not visible!" ); + if ( m_nHighlightedItem != ITEMPOS_INVALID ) + Invalidate(); //HighlightItem( nHighlightedItem, true ); + else if ( m_nRolloveredItem != ITEMPOS_INVALID ) + Invalidate(); //HighlightItem( nRolloveredItem, true ); + m_pMenu->ImplCallHighlight(m_nHighlightedItem); + + if( mbAutoPopup ) + ImplCreatePopup( bSelectEntry ); + + // #58935# #73659# Focus, if no popup underneath... + if ( bJustActivated && !m_pActivePopup ) + GrabFocus(); +} + +static int ImplGetTopDockingAreaHeight( vcl::Window const *pWindow ) +{ + // find docking area that is top aligned and return its height + // note: dockingareas are direct children of the SystemWindow + if( pWindow->ImplGetFrameWindow() ) + { + vcl::Window *pWin = pWindow->ImplGetFrameWindow()->GetWindow( GetWindowType::FirstChild ); //mpWindowImpl->mpFirstChild; + while( pWin ) + { + if( pWin->IsSystemWindow() ) + { + vcl::Window *pChildWin = pWin->GetWindow( GetWindowType::FirstChild ); //mpWindowImpl->mpFirstChild; + while( pChildWin ) + { + DockingAreaWindow *pDockingArea = nullptr; + if ( pChildWin->GetType() == WindowType::DOCKINGAREA ) + pDockingArea = static_cast< DockingAreaWindow* >( pChildWin ); + + if( pDockingArea && pDockingArea->GetAlign() == WindowAlign::Top && + pDockingArea->IsVisible() && pDockingArea->GetOutputSizePixel().Height() != 0 ) + { + return pDockingArea->GetOutputSizePixel().Height(); + } + + pChildWin = pChildWin->GetWindow( GetWindowType::Next ); //mpWindowImpl->mpNext; + } + + } + + pWin = pWin->GetWindow( GetWindowType::Next ); //mpWindowImpl->mpNext; + } + } + return 0; +} + +static void ImplAddNWFSeparator(vcl::RenderContext& rRenderContext, const Size& rSize, const MenubarValue& rMenubarValue) +{ + // add a separator if + // - we have an adjacent docking area + // - and if toolbars would draw them as well (mbDockingAreaSeparateTB must not be set, see dockingarea.cxx) + if (rMenubarValue.maTopDockingAreaHeight + && !ImplGetSVData()->maNWFData.mbDockingAreaSeparateTB + && !ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames) + { + // note: the menubar only provides the upper (dark) half of it, the rest (bright part) is drawn by the docking area + + rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetSeparatorColor()); + tools::Rectangle aRect(Point(), rSize); + rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight()); + } +} + +void MenuBarWindow::HighlightItem(vcl::RenderContext& rRenderContext, sal_uInt16 nPos) +{ + if (!m_pMenu) + return; + + tools::Long nX = 0; + size_t nCount = m_pMenu->pItemList->size(); + + Size aOutputSize = GetOutputSizePixel(); + aOutputSize.AdjustWidth( -(m_aCloseBtn->GetSizePixel().Width()) ); + + for (size_t n = 0; n < nCount; n++) + { + MenuItemData* pData = m_pMenu->pItemList->GetDataFromPos( n ); + if (n == nPos) + { + if (pData->eType != MenuItemType::SEPARATOR) + { + // #107747# give menuitems the height of the menubar + tools::Rectangle aRect(Point(nX, 1), Size(pData->aSz.Width(), aOutputSize.Height() - 2)); + rRenderContext.Push(vcl::PushFlags::CLIPREGION); + rRenderContext.IntersectClipRegion(aRect); + bool bRollover, bHighlight; + if (!ImplGetSVData()->maNWFData.mbRolloverMenubar) + { + bHighlight = true; + bRollover = nPos != m_nHighlightedItem; + } + else + { + bRollover = nPos == m_nRolloveredItem; + bHighlight = nPos == m_nHighlightedItem; + } + if (rRenderContext.IsNativeControlSupported(ControlType::Menubar, ControlPart::MenuItem) && + rRenderContext.IsNativeControlSupported(ControlType::Menubar, ControlPart::Entire)) + { + // draw background (transparency) + MenubarValue aControlValue; + aControlValue.maTopDockingAreaHeight = ImplGetTopDockingAreaHeight( this ); + + if (!Application::GetSettings().GetStyleSettings().GetPersonaHeader().IsEmpty() ) + Erase(rRenderContext); + else + { + tools::Rectangle aBgRegion(Point(), aOutputSize); + rRenderContext.DrawNativeControl(ControlType::Menubar, ControlPart::Entire, aBgRegion, + ControlState::ENABLED, aControlValue, OUString()); + } + + ImplAddNWFSeparator(rRenderContext, aOutputSize, aControlValue); + + // draw selected item + ControlState nState = ControlState::ENABLED; + if (bRollover) + nState |= ControlState::ROLLOVER; + else + nState |= ControlState::SELECTED; + rRenderContext.DrawNativeControl(ControlType::Menubar, ControlPart::MenuItem, + aRect, nState, aControlValue, OUString() ); + } + else + { + if (bRollover) + rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuBarRolloverColor()); + else + rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor()); + rRenderContext.SetLineColor(); + rRenderContext.DrawRect(aRect); + } + rRenderContext.Pop(); + + m_pMenu->ImplPaint(rRenderContext, aOutputSize, 0, 0, pData, bHighlight, false, bRollover); + } + return; + } + + nX += pData->aSz.Width(); + } +} + +tools::Rectangle MenuBarWindow::ImplGetItemRect( sal_uInt16 nPos ) const +{ + tools::Rectangle aRect; + if( m_pMenu ) + { + tools::Long nX = 0; + size_t nCount = m_pMenu->pItemList->size(); + for ( size_t n = 0; n < nCount; n++ ) + { + MenuItemData* pData = m_pMenu->pItemList->GetDataFromPos( n ); + if ( n == nPos ) + { + if ( pData->eType != MenuItemType::SEPARATOR ) + // #107747# give menuitems the height of the menubar + aRect = tools::Rectangle( Point( nX, 1 ), Size( pData->aSz.Width(), GetOutputSizePixel().Height()-2 ) ); + break; + } + + nX += pData->aSz.Width(); + } + } + return aRect; +} + +void MenuBarWindow::KeyInput( const KeyEvent& rKEvent ) +{ + if ( !HandleKeyEvent( rKEvent ) ) + Window::KeyInput( rKEvent ); +} + +bool MenuBarWindow::HandleKeyEvent( const KeyEvent& rKEvent, bool bFromMenu ) +{ + if (!m_pMenu) + return false; + + if (m_pMenu->bInCallback) + return true; // swallow + + bool bDone = false; + sal_uInt16 nCode = rKEvent.GetKeyCode().GetCode(); + + if( GetParent() ) + { + if( GetParent()->GetWindow( GetWindowType::Client )->IsSystemWindow() ) + { + SystemWindow *pSysWin = static_cast<SystemWindow*>(GetParent()->GetWindow( GetWindowType::Client )); + if( pSysWin->GetTaskPaneList() ) + if( pSysWin->GetTaskPaneList()->HandleKeyEvent( rKEvent ) ) + return true; + } + } + + // no key events if native menus + if (m_pMenu->ImplGetSalMenu() && m_pMenu->ImplGetSalMenu()->VisibleMenuBar()) + { + return false; + } + + if ( nCode == KEY_MENU && !rKEvent.GetKeyCode().IsShift() ) // only F10, not Shift-F10 + { + mbAutoPopup = false; + if ( m_nHighlightedItem == ITEMPOS_INVALID ) + { + ChangeHighlightItem( 0, false ); + GrabFocus(); + } + else + { + ChangeHighlightItem( ITEMPOS_INVALID, false ); + m_xSaveFocusId = nullptr; + } + bDone = true; + } + else if ( bFromMenu ) + { + if ( ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) || + ( nCode == KEY_HOME ) || ( nCode == KEY_END ) ) + { + sal_uInt16 n = m_nHighlightedItem; + if ( n == ITEMPOS_INVALID ) + { + if ( nCode == KEY_LEFT) + n = 0; + else + n = m_pMenu->GetItemCount()-1; + } + + sal_uInt16 nLoop = n; + + if( nCode == KEY_HOME ) + { n = sal_uInt16(-1); nLoop = n+1; } + if( nCode == KEY_END ) + { n = m_pMenu->GetItemCount(); nLoop = n-1; } + + do + { + if ( nCode == KEY_LEFT || nCode == KEY_END ) + { + if ( n ) + n--; + else + n = m_pMenu->GetItemCount()-1; + } + if ( nCode == KEY_RIGHT || nCode == KEY_HOME ) + { + n++; + if ( n >= m_pMenu->GetItemCount() ) + n = 0; + } + + MenuItemData* pData = m_pMenu->GetItemList()->GetDataFromPos( n ); + if (pData->eType != MenuItemType::SEPARATOR && + m_pMenu->ImplIsVisible(n) && + !m_pMenu->ImplCurrentlyHiddenOnGUI(n)) + { + ChangeHighlightItem( n, true ); + break; + } + } while ( n != nLoop ); + bDone = true; + } + else if ( nCode == KEY_RETURN ) + { + if( m_pActivePopup ) KillActivePopup(); + else + if ( !mbAutoPopup ) + { + ImplCreatePopup( true ); + mbAutoPopup = true; + } + bDone = true; + } + else if ( ( nCode == KEY_UP ) || ( nCode == KEY_DOWN ) ) + { + if ( !mbAutoPopup ) + { + ImplCreatePopup( true ); + mbAutoPopup = true; + } + bDone = true; + } + else if ( nCode == KEY_ESCAPE || ( nCode == KEY_F6 && rKEvent.GetKeyCode().IsMod1() ) ) + { + if( m_pActivePopup ) + { + // hide the menu and remove the focus... + mbAutoPopup = false; + KillActivePopup(); + } + + ChangeHighlightItem( ITEMPOS_INVALID, false ); + + if( nCode == KEY_F6 && rKEvent.GetKeyCode().IsMod1() ) + { + // put focus into document + GrabFocusToDocument(); + } + + bDone = true; + } + } + + if ( !bDone && ( bFromMenu || rKEvent.GetKeyCode().IsMod2() ) ) + { + sal_Unicode nCharCode = rKEvent.GetCharCode(); + if ( nCharCode ) + { + size_t nEntry, nDuplicates; + MenuItemData* pData = m_pMenu->GetItemList()->SearchItem( nCharCode, rKEvent.GetKeyCode(), nEntry, nDuplicates, m_nHighlightedItem ); + if ( pData && (nEntry != ITEMPOS_INVALID) ) + { + mbAutoPopup = true; + ChangeHighlightItem( nEntry, true ); + bDone = true; + } + } + } + + return bDone; +} + +void MenuBarWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + if (!m_pMenu) + return; + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + Size aOutputSize = GetOutputSizePixel(); + + // no VCL paint if native menus + if (m_pMenu->ImplGetSalMenu() && m_pMenu->ImplGetSalMenu()->VisibleMenuBar()) + { + ImplGetFrame()->DrawMenuBar(); + return; + } + + // Make sure that all actual rendering happens in one go to avoid flicker. + vcl::BufferDevice pBuffer(this, rRenderContext); + + if (rRenderContext.IsNativeControlSupported(ControlType::Menubar, ControlPart::Entire)) + { + MenubarValue aMenubarValue; + aMenubarValue.maTopDockingAreaHeight = ImplGetTopDockingAreaHeight(this); + + if (!rStyleSettings.GetPersonaHeader().IsEmpty()) + Erase(*pBuffer); + else + { + tools::Rectangle aCtrlRegion( Point(), aOutputSize ); + + pBuffer->DrawNativeControl(ControlType::Menubar, ControlPart::Entire, aCtrlRegion, + ControlState::ENABLED, aMenubarValue, OUString()); + } + + ImplAddNWFSeparator(*pBuffer, aOutputSize, aMenubarValue); + } + + // shrink the area of the buttons + aOutputSize.AdjustWidth( -(m_aCloseBtn->GetSizePixel().Width()) ); + + pBuffer->SetFillColor(rStyleSettings.GetMenuColor()); + m_pMenu->ImplPaint(*pBuffer, aOutputSize, 0); + + if (m_nHighlightedItem != ITEMPOS_INVALID && m_pMenu && !m_pMenu->GetItemList()->GetDataFromPos(m_nHighlightedItem)->bHiddenOnGUI) + HighlightItem(*pBuffer, m_nHighlightedItem); + else if (m_nRolloveredItem != ITEMPOS_INVALID) + HighlightItem(*pBuffer, m_nRolloveredItem); + + // in high contrast mode draw a separating line on the lower edge + if (!rRenderContext.IsNativeControlSupported( ControlType::Menubar, ControlPart::Entire) && + rStyleSettings.GetHighContrastMode()) + { + pBuffer->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::MAPMODE); + pBuffer->SetLineColor(COL_WHITE); + pBuffer->SetMapMode(MapMode(MapUnit::MapPixel)); + Size aSize = GetSizePixel(); + pBuffer->DrawLine(Point(0, aSize.Height() - 1), + Point(aSize.Width() - 1, aSize.Height() - 1)); + pBuffer->Pop(); + } +} + +void MenuBarWindow::Resize() +{ + Size aOutSz = GetOutputSizePixel(); + tools::Long n = aOutSz.Height()-4; + tools::Long nX = aOutSz.Width()-3; + tools::Long nY = 2; + + if ( m_aCloseBtn->IsVisible() ) + { + m_aCloseBtn->Hide(); + m_aCloseBtn->SetImages(n); + Size aTbxSize( m_aCloseBtn->CalcWindowSizePixel() ); + nX -= aTbxSize.Width(); + tools::Long nTbxY = (aOutSz.Height() - aTbxSize.Height())/2; + m_aCloseBtn->setPosSizePixel(nX, nTbxY, aTbxSize.Width(), aTbxSize.Height()); + nX -= 3; + m_aCloseBtn->Show(); + } + if ( m_aFloatBtn->IsVisible() ) + { + nX -= n; + m_aFloatBtn->setPosSizePixel( nX, nY, n, n ); + } + if ( m_aHideBtn->IsVisible() ) + { + nX -= n; + m_aHideBtn->setPosSizePixel( nX, nY, n, n ); + } + + m_aFloatBtn->SetSymbol( SymbolType::FLOAT ); + m_aHideBtn->SetSymbol( SymbolType::HIDE ); + + Invalidate(); +} + +sal_uInt16 MenuBarWindow::ImplFindEntry( const Point& rMousePos ) const +{ + if( m_pMenu ) + { + tools::Long nX = 0; + size_t nCount = m_pMenu->pItemList->size(); + for ( size_t n = 0; n < nCount; n++ ) + { + MenuItemData* pData = m_pMenu->pItemList->GetDataFromPos( n ); + if ( m_pMenu->ImplIsVisible( n ) ) + { + nX += pData->aSz.Width(); + if ( nX > rMousePos.X() ) + return static_cast<sal_uInt16>(n); + } + } + } + return ITEMPOS_INVALID; +} + +void MenuBarWindow::RequestHelp( const HelpEvent& rHEvt ) +{ + sal_uInt16 nId = m_nHighlightedItem; + if ( rHEvt.GetMode() & HelpEventMode::CONTEXT ) + ChangeHighlightItem( ITEMPOS_INVALID, true ); + + tools::Rectangle aHighlightRect( ImplGetItemRect( m_nHighlightedItem ) ); + if( !ImplHandleHelpEvent( this, m_pMenu, nId, rHEvt, aHighlightRect ) ) + Window::RequestHelp( rHEvt ); +} + +void MenuBarWindow::StateChanged( StateChangedType nType ) +{ + Window::StateChanged( nType ); + + if (nType == StateChangedType::ControlForeground || + nType == StateChangedType::ControlBackground) + { + ApplySettings(*GetOutDev()); + Invalidate(); + } + else if (nType == StateChangedType::Enable) + { + Invalidate(); + } + else if(m_pMenu) + { + m_pMenu->ImplKillLayoutData(); + } +} + +void MenuBarWindow::LayoutChanged() +{ + if (!m_pMenu) + return; + + ApplySettings(*GetOutDev()); + + // if the font was changed. + tools::Long nHeight = m_pMenu->ImplCalcSize(this).Height(); + + // depending on the native implementation or the displayable flag + // the menubar windows is suppressed (ie, height=0) + if (!static_cast<MenuBar*>(m_pMenu.get())->IsDisplayable() || + (m_pMenu->ImplGetSalMenu() && m_pMenu->ImplGetSalMenu()->VisibleMenuBar())) + { + nHeight = 0; + } + setPosSizePixel(0, 0, 0, nHeight, PosSizeFlags::Height); + GetParent()->Resize(); + Invalidate(); + Resize(); + + m_pMenu->ImplKillLayoutData(); +} + +void MenuBarWindow::ApplySettings(vcl::RenderContext& rRenderContext) +{ + Window::ApplySettings(rRenderContext); + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + SetPointFont(rRenderContext, rStyleSettings.GetMenuFont()); + + const BitmapEx& rPersonaBitmap = Application::GetSettings().GetStyleSettings().GetPersonaHeader(); + SalMenu *pNativeMenu = m_pMenu ? m_pMenu->ImplGetSalMenu() : nullptr; + if (pNativeMenu) + pNativeMenu->ApplyPersona(); + if (!rPersonaBitmap.IsEmpty()) + { + Wallpaper aWallpaper(rPersonaBitmap); + aWallpaper.SetStyle(WallpaperStyle::TopRight); + aWallpaper.SetColor(Application::GetSettings().GetStyleSettings().GetWorkspaceColor()); + + rRenderContext.SetBackground(aWallpaper); + SetPaintTransparent(false); + SetParentClipMode(); + } + else if (rRenderContext.IsNativeControlSupported(ControlType::Menubar, ControlPart::Entire)) + { + rRenderContext.SetBackground(); // background will be drawn by NWF + } + else + { + Wallpaper aWallpaper; + aWallpaper.SetStyle(WallpaperStyle::ApplicationGradient); + rRenderContext.SetBackground(aWallpaper); + SetPaintTransparent(false); + SetParentClipMode(); + } + + rRenderContext.SetTextColor(rStyleSettings.GetMenuBarTextColor()); + rRenderContext.SetTextFillColor(); + rRenderContext.SetLineColor(); +} + +void MenuBarWindow::ImplInitStyleSettings() +{ + if (!(IsNativeControlSupported(ControlType::Menubar, ControlPart::MenuItem) && + IsNativeControlSupported(ControlType::Menubar, ControlPart::Entire))) + return; + + AllSettings aSettings(GetSettings()); + ImplGetFrame()->UpdateSettings(aSettings); // to update persona + StyleSettings aStyle(aSettings.GetStyleSettings()); + Color aHighlightTextColor = ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor; + if (aHighlightTextColor != COL_TRANSPARENT) + { + aStyle.SetMenuHighlightTextColor(aHighlightTextColor); + } + aSettings.SetStyleSettings(aStyle); + GetOutDev()->SetSettings(aSettings); +} + +void MenuBarWindow::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) ) + { + ApplySettings(*GetOutDev()); + ImplInitStyleSettings(); + LayoutChanged(); + } +} + +void MenuBarWindow::LoseFocus() +{ + if ( !HasChildPathFocus( true ) ) + ChangeHighlightItem( ITEMPOS_INVALID, false, false ); +} + +void MenuBarWindow::GetFocus() +{ + SalMenu *pNativeMenu = m_pMenu ? m_pMenu->ImplGetSalMenu() : nullptr; + if (pNativeMenu && pNativeMenu->TakeFocus()) + return; + + if ( m_nHighlightedItem == ITEMPOS_INVALID ) + { + mbAutoPopup = false; // do not open menu when activated by focus handling like taskpane cycling + ChangeHighlightItem( 0, false ); + } +} + +css::uno::Reference<css::accessibility::XAccessible> MenuBarWindow::CreateAccessible() +{ + css::uno::Reference<css::accessibility::XAccessible> xAcc; + + if (m_pMenu) + xAcc = m_pMenu->GetAccessible(); + + return xAcc; +} + +sal_uInt16 MenuBarWindow::AddMenuBarButton( const Image& i_rImage, const Link<MenuBarButtonCallbackArg&,bool>& i_rLink, const OUString& i_rToolTip ) +{ + // find first free button id + sal_uInt16 nId = IID_DOCUMENTCLOSE; + std::map< sal_uInt16, AddButtonEntry >::const_iterator it; + do + { + nId++; + it = m_aAddButtons.find( nId ); + } while( it != m_aAddButtons.end() && nId < 128 ); + SAL_WARN_IF( nId >= 128, "vcl", "too many addbuttons in menubar" ); + AddButtonEntry& rNewEntry = m_aAddButtons[nId]; + rNewEntry.m_aSelectLink = i_rLink; + m_aCloseBtn->InsertItem(ToolBoxItemId(nId), i_rImage, ToolBoxItemBits::NONE, 0); + m_aCloseBtn->calcMinSize(); + ShowButtons(m_aCloseBtn->IsItemVisible(ToolBoxItemId(IID_DOCUMENTCLOSE)), m_aFloatBtn->IsVisible(), m_aHideBtn->IsVisible()); + LayoutChanged(); + + if( m_pMenu->mpSalMenu ) + m_pMenu->mpSalMenu->AddMenuBarButton( SalMenuButtonItem( nId, i_rImage, i_rToolTip ) ); + + return nId; +} + +void MenuBarWindow::SetMenuBarButtonHighlightHdl( sal_uInt16 nId, const Link<MenuBarButtonCallbackArg&,bool>& rLink ) +{ + std::map< sal_uInt16, AddButtonEntry >::iterator it = m_aAddButtons.find( nId ); + if( it != m_aAddButtons.end() ) + it->second.m_aHighlightLink = rLink; +} + +tools::Rectangle MenuBarWindow::GetMenuBarButtonRectPixel( sal_uInt16 nId ) +{ + tools::Rectangle aRect; + if( m_aAddButtons.find( nId ) != m_aAddButtons.end() ) + { + if( m_pMenu->mpSalMenu ) + { + aRect = m_pMenu->mpSalMenu->GetMenuBarButtonRectPixel( nId, ImplGetWindowImpl()->mpFrame ); + if( aRect == tools::Rectangle( Point( -1, -1 ), Size( 1, 1 ) ) ) + { + // system menu button is somewhere but location cannot be determined + return tools::Rectangle(); + } + } + + if( aRect.IsEmpty() ) + { + aRect = m_aCloseBtn->GetItemRect(ToolBoxItemId(nId)); + Point aOffset = m_aCloseBtn->OutputToScreenPixel(Point()); + aRect.Move( aOffset.X(), aOffset.Y() ); + } + } + return aRect; +} + +void MenuBarWindow::RemoveMenuBarButton( sal_uInt16 nId ) +{ + ToolBox::ImplToolItems::size_type nPos = m_aCloseBtn->GetItemPos(ToolBoxItemId(nId)); + m_aCloseBtn->RemoveItem(nPos); + m_aAddButtons.erase( nId ); + m_aCloseBtn->calcMinSize(); + LayoutChanged(); + + if( m_pMenu->mpSalMenu ) + m_pMenu->mpSalMenu->RemoveMenuBarButton( nId ); +} + +bool MenuBarWindow::HandleMenuButtonEvent( sal_uInt16 i_nButtonId ) +{ + std::map< sal_uInt16, AddButtonEntry >::iterator it = m_aAddButtons.find( i_nButtonId ); + if( it != m_aAddButtons.end() ) + { + MenuBarButtonCallbackArg aArg; + aArg.nId = it->first; + aArg.bHighlight = true; + return it->second.m_aSelectLink.Call( aArg ); + } + return false; +} + +bool MenuBarWindow::CanGetFocus() const +{ + /* #i83908# do not use the menubar if it is native or invisible + this relies on MenuBar::ImplCreate setting the height of the menubar + to 0 in this case + */ + SalMenu *pNativeMenu = m_pMenu ? m_pMenu->ImplGetSalMenu() : nullptr; + if (pNativeMenu && pNativeMenu->VisibleMenuBar()) + return pNativeMenu->CanGetFocus(); + return GetSizePixel().Height() > 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/menubarwindow.hxx b/vcl/source/window/menubarwindow.hxx new file mode 100644 index 000000000..cc7963a1b --- /dev/null +++ b/vcl/source/window/menubarwindow.hxx @@ -0,0 +1,142 @@ +/* -*- 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 "menuwindow.hxx" + +#include <vcl/toolkit/button.hxx> +#include <vcl/menu.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/window.hxx> + +#include <map> + +class Button; + +/** Toolbox that holds the close button (right hand side of the menubar). + +This is also used by the online update check; when an update is available, it +inserts here the button that leads to the download of the update. +*/ +class DecoToolBox : public ToolBox +{ + tools::Long lastSize; + Size maMinSize; + +public: + explicit DecoToolBox(vcl::Window* pParent); + + void DataChanged( const DataChangedEvent& rDCEvt ) override; + + void SetImages( tools::Long nMaxHeight, bool bForce = false ); + + void calcMinSize(); + const Size& getMinSize() const { return maMinSize;} + + Image maImage; +}; + + +/** Class that implements the actual window of the menu bar. +*/ +class MenuBarWindow : public vcl::Window, public MenuWindow +{ + friend class MenuBar; + friend class Menu; + +private: + struct AddButtonEntry + { + Link<MenuBarButtonCallbackArg&,bool> m_aSelectLink; + Link<MenuBarButtonCallbackArg&,bool> m_aHighlightLink; + }; + + VclPtr<Menu> m_pMenu; + VclPtr<PopupMenu> m_pActivePopup; + VclPtr<PopupMenu> mpParentPopup; + sal_uInt16 m_nHighlightedItem; + sal_uInt16 m_nRolloveredItem; + VclPtr<vcl::Window> m_xSaveFocusId; + bool mbAutoPopup; + bool m_bIgnoreFirstMove; + + VclPtr<DecoToolBox> m_aCloseBtn; + VclPtr<PushButton> m_aFloatBtn; + VclPtr<PushButton> m_aHideBtn; + + std::map< sal_uInt16, AddButtonEntry > m_aAddButtons; + + void HighlightItem(vcl::RenderContext& rRenderContext, sal_uInt16 nPos); + void ChangeHighlightItem(sal_uInt16 n, bool bSelectPopupEntry, bool bAllowRestoreFocus = true, bool bDefaultToDocument = true); + + sal_uInt16 ImplFindEntry( const Point& rMousePos ) const; + void ImplCreatePopup( bool bPreSelectFirst ); + bool HandleKeyEvent(const KeyEvent& rKEvent, bool bFromMenu = true); + tools::Rectangle ImplGetItemRect( sal_uInt16 nPos ) const; + + void ImplInitStyleSettings(); + + virtual void ApplySettings(vcl::RenderContext& rRenderContext) override; + + DECL_LINK( CloseHdl, ToolBox*, void ); + DECL_LINK( ToolboxEventHdl, VclWindowEvent&, void ); + DECL_LINK( ShowHideListener, VclWindowEvent&, void ); + + void StateChanged( StateChangedType nType ) override; + void DataChanged( const DataChangedEvent& rDCEvt ) override; + void LoseFocus() override; + void GetFocus() override; + +public: + explicit MenuBarWindow( vcl::Window* pParent ); + virtual ~MenuBarWindow() override; + virtual void dispose() override; + + void ShowButtons(bool bClose, bool bFloat, bool bHide); + + virtual void MouseMove( const MouseEvent& rMEvt ) override; + virtual void MouseButtonDown( const MouseEvent& rMEvt ) override; + virtual void MouseButtonUp( const MouseEvent& rMEvt ) override; + virtual void KeyInput( const KeyEvent& rKEvent ) override; + virtual void Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) override; + virtual void Resize() override; + virtual void RequestHelp( const HelpEvent& rHEvt ) override; + + void SetMenu(MenuBar* pMenu); + void SetHeight(tools::Long nHeight); + void KillActivePopup(); + void PopupClosed(Menu const * pMenu); + sal_uInt16 GetHighlightedItem() const { return m_nHighlightedItem; } + virtual css::uno::Reference<css::accessibility::XAccessible> CreateAccessible() override; + + void SetAutoPopup(bool bAuto) { mbAutoPopup = bAuto; } + void LayoutChanged(); + Size const & MinCloseButtonSize() const; + + /// Add an arbitrary button to the menubar that will appear next to the close button. + sal_uInt16 AddMenuBarButton(const Image&, const Link<MenuBarButtonCallbackArg&,bool>&, const OUString&); + void SetMenuBarButtonHighlightHdl(sal_uInt16 nId, const Link<MenuBarButtonCallbackArg&,bool>&); + tools::Rectangle GetMenuBarButtonRectPixel(sal_uInt16 nId); + void RemoveMenuBarButton(sal_uInt16 nId); + bool HandleMenuButtonEvent(sal_uInt16 i_nButtonId); + bool CanGetFocus() const; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/menufloatingwindow.cxx b/vcl/source/window/menufloatingwindow.cxx new file mode 100644 index 000000000..cfd6a6ae1 --- /dev/null +++ b/vcl/source/window/menufloatingwindow.cxx @@ -0,0 +1,1318 @@ +/* -*- 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 "menufloatingwindow.hxx" +#include "menuitemlist.hxx" +#include "bufferdevice.hxx" + +#include <sal/log.hxx> +#include <salframe.hxx> +#include <svdata.hxx> +#include <vcl/decoview.hxx> +#include <vcl/settings.hxx> +#include <window.h> + +MenuFloatingWindow::MenuFloatingWindow( Menu* pMen, vcl::Window* pParent, WinBits nStyle ) : + FloatingWindow( pParent, nStyle ), + pMenu(pMen), + aHighlightChangedTimer("vcl::MenuFloatingWindow aHighlightChangedTimer"), + aSubmenuCloseTimer( "vcl::MenuFloatingWindow aSubmenuCloseTimer" ), + aScrollTimer( "vcl::MenuFloatingWindow aScrollTimer" ), + nHighlightedItem(ITEMPOS_INVALID), + nMBDownPos(ITEMPOS_INVALID), + nScrollerHeight(0), + nFirstEntry(0), + nPosInParent(ITEMPOS_INVALID), + bInExecute(false), + bScrollMenu(false), + bScrollUp(false), + bScrollDown(false), + bIgnoreFirstMove(true), + bKeyInput(false) +{ + mpWindowImpl->mbMenuFloatingWindow= true; + + ApplySettings(*GetOutDev()); + + SetPopupModeEndHdl( LINK( this, MenuFloatingWindow, PopupEnd ) ); + + aHighlightChangedTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, HighlightChanged ) ); + aHighlightChangedTimer.SetTimeout( GetSettings().GetMouseSettings().GetMenuDelay() ); + + aSubmenuCloseTimer.SetTimeout( GetSettings().GetMouseSettings().GetMenuDelay() ); + aSubmenuCloseTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, SubmenuClose ) ); + + aScrollTimer.SetInvokeHandler( LINK( this, MenuFloatingWindow, AutoScroll ) ); + + AddEventListener( LINK( this, MenuFloatingWindow, ShowHideListener ) ); +} + +void MenuFloatingWindow::doShutdown() +{ + if( !pMenu ) + return; + + // #105373# notify toolkit that highlight was removed + // otherwise the entry will not be read when the menu is opened again + if( nHighlightedItem != ITEMPOS_INVALID ) + pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, nHighlightedItem ); + if (!bKeyInput && pMenu && pMenu->pStartedFrom && !pMenu->pStartedFrom->IsMenuBar()) + { + // #102461# remove highlight in parent + size_t i, nCount = pMenu->pStartedFrom->pItemList->size(); + for(i = 0; i < nCount; i++) + { + MenuItemData* pData = pMenu->pStartedFrom->pItemList->GetDataFromPos( i ); + if( pData && ( pData->pSubMenu == pMenu ) ) + break; + } + if( i < nCount ) + { + MenuFloatingWindow* pPWin = static_cast<MenuFloatingWindow*>(pMenu->pStartedFrom->ImplGetWindow()); + if (pPWin) + pPWin->InvalidateItem(i); + } + } + + // free the reference to the accessible component + SetAccessible( css::uno::Reference< css::accessibility::XAccessible >() ); + + aHighlightChangedTimer.Stop(); + + // #95056# invalidate screen area covered by system window + // so this can be taken into account if the commandhandler performs a scroll operation + if( GetParent() ) + { + tools::Rectangle aInvRect( GetWindowExtentsRelative( GetParent() ) ); + GetParent()->Invalidate( aInvRect ); + } + pMenu = nullptr; + RemoveEventListener( LINK( this, MenuFloatingWindow, ShowHideListener ) ); + + aScrollTimer.Stop(); + aSubmenuCloseTimer.Stop(); + aSubmenuCloseTimer.Stop(); + aHighlightChangedTimer.Stop(); + aHighlightChangedTimer.Stop(); + +} + +MenuFloatingWindow::~MenuFloatingWindow() +{ + disposeOnce(); +} + +void MenuFloatingWindow::dispose() +{ + doShutdown(); + pMenu.clear(); + pActivePopup.clear(); + xSaveFocusId.clear(); + FloatingWindow::dispose(); +} + +void MenuFloatingWindow::Resize() +{ + InitMenuClipRegion(*GetOutDev()); // FIXME +} + +void MenuFloatingWindow::ApplySettings(vcl::RenderContext& rRenderContext) +{ + FloatingWindow::ApplySettings(rRenderContext); + + if (IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItem) && + IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire)) + { + AllSettings aSettings(GetSettings()); + ImplGetFrame()->UpdateSettings(aSettings); // Update theme colors. + StyleSettings aStyle(aSettings.GetStyleSettings()); + Color aHighlightTextColor = ImplGetSVData()->maNWFData.maMenuBarHighlightTextColor; + if (aHighlightTextColor != COL_TRANSPARENT) + { + aStyle.SetMenuHighlightTextColor(aHighlightTextColor); + } + aSettings.SetStyleSettings(aStyle); + GetOutDev()->SetSettings(aSettings); + } + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + SetPointFont(rRenderContext, rStyleSettings.GetMenuFont()); + + if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire)) + { + rRenderContext.SetBackground(); // background will be drawn by NWF + } + else + rRenderContext.SetBackground(Wallpaper(rStyleSettings.GetMenuColor())); + + rRenderContext.SetTextColor(rStyleSettings.GetMenuTextColor()); + rRenderContext.SetTextFillColor(); + rRenderContext.SetLineColor(); +} + +/// Get a negative pixel offset for an offset menu +tools::Long MenuFloatingWindow::ImplGetStartY() const +{ + tools::Long nY = 0; + if( pMenu ) + { + // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686) + if ( nFirstEntry > 0 && !pMenu->GetItemList()->GetDataFromPos(nFirstEntry - 1) ) + { + return 0; + } + + for ( sal_uInt16 n = 0; n < nFirstEntry; n++ ) + nY += pMenu->GetItemList()->GetDataFromPos( n )->aSz.Height(); + nY -= pMenu->GetTitleHeight(); + } + return -nY; +} + +vcl::Region MenuFloatingWindow::ImplCalcClipRegion() const +{ + Size aOutSz = GetOutputSizePixel(); + tools::Rectangle aRect( Point(), aOutSz ); + aRect.AdjustTop(nScrollerHeight ); + aRect.AdjustBottom( -nScrollerHeight ); + + vcl::Region aRegion(aRect); + + return aRegion; +} + +void MenuFloatingWindow::InitMenuClipRegion(vcl::RenderContext& rRenderContext) +{ + if (IsScrollMenu()) + { + rRenderContext.SetClipRegion(ImplCalcClipRegion()); + } + else + { + rRenderContext.SetClipRegion(); + } +} + +void MenuFloatingWindow::ImplHighlightItem( const MouseEvent& rMEvt, bool bMBDown ) +{ + if( ! pMenu ) + return; + + tools::Long nY = GetInitialItemY(); + tools::Long nMouseY = rMEvt.GetPosPixel().Y(); + Size aOutSz = GetOutputSizePixel(); + if ( ( nMouseY >= nY ) && ( nMouseY < aOutSz.Height() ) ) + { + bool bHighlighted = false; + size_t nCount = pMenu->pItemList->size(); + for ( size_t n = 0; !bHighlighted && ( n < nCount ); n++ ) + { + if ( pMenu->ImplIsVisible( n ) ) + { + MenuItemData* pItemData = pMenu->pItemList->GetDataFromPos( n ); + tools::Long nOldY = nY; + nY += pItemData->aSz.Height(); + if ( ( nOldY <= nMouseY ) && ( nY > nMouseY ) && pMenu->ImplIsSelectable( n ) ) + { + bool bPopupArea = true; + if ( pItemData->nBits & MenuItemBits::POPUPSELECT ) + { + // only when clicked over the arrow... + Size aSz = GetOutputSizePixel(); + tools::Long nFontHeight = GetTextHeight(); + bPopupArea = ( rMEvt.GetPosPixel().X() >= ( aSz.Width() - nFontHeight - nFontHeight/4 ) ); + } + + if ( bMBDown ) + { + if ( n != nHighlightedItem ) + { + ChangeHighlightItem( static_cast<sal_uInt16>(n), false ); + } + + bool bAllowNewPopup = true; + if ( pActivePopup ) + { + MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n ); + bAllowNewPopup = pData && ( pData->pSubMenu != pActivePopup ); + if ( bAllowNewPopup ) + KillActivePopup(); + } + + if ( bPopupArea && bAllowNewPopup ) + { + HighlightChanged( nullptr ); + } + } + else + { + if ( n != nHighlightedItem ) + { + ChangeHighlightItem( static_cast<sal_uInt16>(n), true ); + } + else if ( pItemData->nBits & MenuItemBits::POPUPSELECT ) + { + if ( bPopupArea && ( pActivePopup != pItemData->pSubMenu ) ) + HighlightChanged( nullptr ); + } + } + bHighlighted = true; + } + } + } + if ( !bHighlighted ) + ChangeHighlightItem( ITEMPOS_INVALID, true ); + } + else + { + ImplScroll( rMEvt.GetPosPixel() ); + ChangeHighlightItem( ITEMPOS_INVALID, true ); + } +} + +IMPL_LINK_NOARG(MenuFloatingWindow, PopupEnd, FloatingWindow*, void) +{ + // "this" will be deleted before the end of this method! + Menu* pM = pMenu; + if ( bInExecute ) + { + End(); + if ( pActivePopup ) + { + KillActivePopup(); // should be ok to just remove it + //pActivePopup->bCanceled = true; + } + pMenu->bInCallback = true; + pMenu->Deactivate(); + pMenu->bInCallback = false; + } + else + { + if (pMenu && pMenu->pStartedFrom) + pMenu->pStartedFrom->ClosePopup(pMenu); + } + + if ( pM ) + pM->pStartedFrom = nullptr; +} + +IMPL_LINK_NOARG(MenuFloatingWindow, AutoScroll, Timer *, void) +{ + ImplScroll( GetPointerPosPixel() ); +} + +IMPL_LINK( MenuFloatingWindow, HighlightChanged, Timer*, pTimer, void ) +{ + if( ! pMenu ) + return; + + MenuItemData* pItemData = pMenu->pItemList->GetDataFromPos( nHighlightedItem ); + if ( !pItemData ) + return; + + if ( pActivePopup && ( pActivePopup != pItemData->pSubMenu ) ) + { + FloatWinPopupFlags nOldFlags = GetPopupModeFlags(); + SetPopupModeFlags( GetPopupModeFlags() | FloatWinPopupFlags::NoAppFocusClose ); + KillActivePopup(); + SetPopupModeFlags( nOldFlags ); + } + if ( !(pItemData->bEnabled && pItemData->pSubMenu && pItemData->pSubMenu->GetItemCount() && ( pItemData->pSubMenu != pActivePopup )) ) + return; + + pActivePopup = static_cast<PopupMenu*>(pItemData->pSubMenu.get()); + tools::Long nY = nScrollerHeight+ImplGetStartY(); + MenuItemData* pData = nullptr; + for ( sal_uLong n = 0; n < nHighlightedItem; n++ ) + { + pData = pMenu->pItemList->GetDataFromPos( n ); + nY += pData->aSz.Height(); + } + pData = pMenu->pItemList->GetDataFromPos( nHighlightedItem ); + Size MySize = GetOutputSizePixel(); + Point aItemTopLeft( 0, nY ); + Point aItemBottomRight( aItemTopLeft ); + aItemBottomRight.AdjustX(MySize.Width() ); + aItemBottomRight.AdjustY(pData->aSz.Height() ); + + // shift the popups a little + aItemTopLeft.AdjustX(2 ); + aItemBottomRight.AdjustX( -2 ); + if ( nHighlightedItem ) + aItemTopLeft.AdjustY( -2 ); + else + { + sal_Int32 nL, nT, nR, nB; + GetBorder( nL, nT, nR, nB ); + aItemTopLeft.AdjustY( -nT ); + } + + // pTest: crash due to Reschedule() in call of Activate() + // Also it is prevented that submenus are displayed which + // were for long in Activate Rescheduled and which should not be + // displayed now. + Menu* pTest = pActivePopup; + FloatWinPopupFlags nOldFlags = GetPopupModeFlags(); + SetPopupModeFlags( GetPopupModeFlags() | FloatWinPopupFlags::NoAppFocusClose ); + sal_uInt16 nRet = pActivePopup->ImplExecute( this, tools::Rectangle( aItemTopLeft, aItemBottomRight ), FloatWinPopupFlags::Right, pMenu, pTimer == nullptr ); + SetPopupModeFlags( nOldFlags ); + + // nRet != 0, if it was stopped during Activate()... + if ( !nRet && ( pActivePopup == pTest ) && pActivePopup->ImplGetWindow() ) + pActivePopup->ImplGetFloatingWindow()->AddPopupModeWindow( this ); +} + +IMPL_LINK_NOARG(MenuFloatingWindow, SubmenuClose, Timer *, void) +{ + if( pMenu && pMenu->pStartedFrom ) + { + MenuFloatingWindow* pWin = static_cast<MenuFloatingWindow*>(pMenu->pStartedFrom->GetWindow()); + if( pWin ) + pWin->KillActivePopup(); + } +} + +IMPL_LINK( MenuFloatingWindow, ShowHideListener, VclWindowEvent&, rEvent, void ) +{ + if( ! pMenu ) + return; + + if( rEvent.GetId() == VclEventId::WindowShow ) + pMenu->ImplCallEventListeners( VclEventId::MenuShow, ITEMPOS_INVALID ); + else if( rEvent.GetId() == VclEventId::WindowHide ) + pMenu->ImplCallEventListeners( VclEventId::MenuHide, ITEMPOS_INVALID ); +} + +void MenuFloatingWindow::EnableScrollMenu( bool b ) +{ + bScrollMenu = b; + nScrollerHeight = b ? static_cast<sal_uInt16>(GetSettings().GetStyleSettings().GetScrollBarSize()) /2 : 0; + bScrollDown = true; + InitMenuClipRegion(*GetOutDev()); +} + +void MenuFloatingWindow::Start() +{ + if (bInExecute) + return; + bInExecute = true; + if (GetParent()) + GetParent()->IncModalCount(); +} + +bool MenuFloatingWindow::MenuInHierarchyHasFocus() const +{ + if (HasChildPathFocus()) + return true; + PopupMenu* pSub = GetActivePopup(); + if (!pSub) + return false; + return pSub->ImplGetFloatingWindow()->HasChildPathFocus(); +} + +void MenuFloatingWindow::End() +{ + if (!bInExecute) + return; + + if (GetParent() && !GetParent()->isDisposed()) + GetParent()->DecModalCount(); + + // restore focus to previous window if we still have the focus + VclPtr<vcl::Window> xFocusId(xSaveFocusId); + xSaveFocusId = nullptr; + if (xFocusId != nullptr && MenuInHierarchyHasFocus()) + { + ImplGetSVData()->mpWinData->mbNoDeactivate = false; + Window::EndSaveFocus(xFocusId); + } + + bInExecute = false; +} + +void MenuFloatingWindow::Execute() +{ + ImplSVData* pSVData = ImplGetSVData(); + + pSVData->maAppData.mpActivePopupMenu = static_cast<PopupMenu*>(pMenu.get()); + + Start(); + + while (bInExecute && !Application::IsQuit()) + Application::Yield(); + + pSVData->maAppData.mpActivePopupMenu = nullptr; +} + +void MenuFloatingWindow::StopExecute() +{ + End(); + + ImplEndPopupMode(FloatWinPopupEndFlags::NONE, xSaveFocusId); + + aHighlightChangedTimer.Stop(); + if (pActivePopup) + { + KillActivePopup(); + } + // notify parent, needed for accessibility + if( pMenu && pMenu->pStartedFrom ) + pMenu->pStartedFrom->ImplCallEventListeners( VclEventId::MenuSubmenuDeactivate, nPosInParent ); +} + +void MenuFloatingWindow::KillActivePopup( PopupMenu* pThisOnly ) +{ + if ( !pActivePopup || ( pThisOnly && ( pThisOnly != pActivePopup ) ) ) + return; + + if( pActivePopup->pWindow ) + if( static_cast<FloatingWindow *>(pActivePopup->pWindow.get())->IsInCleanUp() ) + return; // kill it later + if ( pActivePopup->bInCallback ) + pActivePopup->bCanceled = true; + + // For all actions pActivePopup = 0, if e.g. + // PopupModeEndHdl the popups to destroy were called synchronous + PopupMenu* pPopup = pActivePopup; + pActivePopup = nullptr; + pPopup->bInCallback = true; + pPopup->Deactivate(); + pPopup->bInCallback = false; + if ( pPopup->ImplGetWindow() ) + { + pPopup->ImplGetFloatingWindow()->StopExecute(); + pPopup->ImplGetFloatingWindow()->doShutdown(); + pPopup->pWindow.disposeAndClear(); + + PaintImmediately(); + } +} + +void MenuFloatingWindow::EndExecute() +{ + Menu* pStart = pMenu ? pMenu->ImplGetStartMenu() : nullptr; + + // if started elsewhere, cleanup there as well + MenuFloatingWindow* pCleanUpFrom = this; + MenuFloatingWindow* pWin = this; + while (pWin && !pWin->bInExecute && + pWin->pMenu->pStartedFrom && !pWin->pMenu->pStartedFrom->IsMenuBar()) + { + pWin = static_cast<PopupMenu*>(pWin->pMenu->pStartedFrom.get())->ImplGetFloatingWindow(); + } + if ( pWin ) + pCleanUpFrom = pWin; + + // this window will be destroyed => store date locally... + Menu* pM = pMenu; + sal_uInt16 nItem = nHighlightedItem; + + pCleanUpFrom->StopExecute(); + + if ( !(nItem != ITEMPOS_INVALID && pM) ) + return; + + MenuItemData* pItemData = pM->GetItemList()->GetDataFromPos( nItem ); + if ( pItemData && !pItemData->bIsTemporary ) + { + pM->nSelectedId = pItemData->nId; + pM->sSelectedIdent = pItemData->sIdent; + if (pStart) + { + pStart->nSelectedId = pItemData->nId; + pStart->sSelectedIdent = pItemData->sIdent; + } + + pM->ImplSelect(); + } +} + +void MenuFloatingWindow::EndExecute( sal_uInt16 nId ) +{ + size_t nPos; + if ( pMenu && pMenu->GetItemList()->GetData( nId, nPos ) ) + nHighlightedItem = nPos; + else + nHighlightedItem = ITEMPOS_INVALID; + + EndExecute(); +} + +void MenuFloatingWindow::MouseButtonDown( const MouseEvent& rMEvt ) +{ + // TH creates a ToTop on this window, but the active popup + // should stay on top... + // due to focus change this would close all menus -> don't do it (#94123) + //if ( pActivePopup && pActivePopup->ImplGetWindow() && !pActivePopup->ImplGetFloatingWindow()->pActivePopup ) + // pActivePopup->ImplGetFloatingWindow()->ToTop( ToTopFlags::NoGrabFocus ); + + ImplHighlightItem( rMEvt, true ); + + nMBDownPos = nHighlightedItem; +} + +void MenuFloatingWindow::MouseButtonUp( const MouseEvent& rMEvt ) +{ + MenuItemData* pData = pMenu ? pMenu->GetItemList()->GetDataFromPos( nHighlightedItem ) : nullptr; + // nMBDownPos store in local variable and reset immediately, + // as it will be too late after EndExecute + sal_uInt16 _nMBDownPos = nMBDownPos; + nMBDownPos = ITEMPOS_INVALID; + if ( !(pData && pData->bEnabled && ( pData->eType != MenuItemType::SEPARATOR )) ) + return; + + if ( !pData->pSubMenu ) + { + EndExecute(); + } + else if ( ( pData->nBits & MenuItemBits::POPUPSELECT ) && ( nHighlightedItem == _nMBDownPos ) && ( rMEvt.GetClicks() == 2 ) ) + { + // not when clicked over the arrow... + Size aSz = GetOutputSizePixel(); + tools::Long nFontHeight = GetTextHeight(); + if ( rMEvt.GetPosPixel().X() < ( aSz.Width() - nFontHeight - nFontHeight/4 ) ) + EndExecute(); + } + +} + +void MenuFloatingWindow::MouseMove( const MouseEvent& rMEvt ) +{ + if ( !IsVisible() || rMEvt.IsSynthetic() || rMEvt.IsEnterWindow() ) + return; + + if ( rMEvt.IsLeaveWindow() ) + { + // #102461# do not remove highlight if a popup menu is open at this position + MenuItemData* pData = pMenu ? pMenu->pItemList->GetDataFromPos( nHighlightedItem ) : nullptr; + // close popup with some delayed if we leave somewhere else + if( pActivePopup && pData && pData->pSubMenu != pActivePopup ) + pActivePopup->ImplGetFloatingWindow()->aSubmenuCloseTimer.Start(); + + if( !pActivePopup || (pData && pData->pSubMenu != pActivePopup ) ) + ChangeHighlightItem( ITEMPOS_INVALID, false ); + + if ( IsScrollMenu() ) + ImplScroll( rMEvt.GetPosPixel() ); + } + else + { + aSubmenuCloseTimer.Stop(); + if( bIgnoreFirstMove ) + bIgnoreFirstMove = false; + else + ImplHighlightItem( rMEvt, false ); + } +} + +void MenuFloatingWindow::ImplScroll( bool bUp ) +{ + KillActivePopup(); + PaintImmediately(); + + if (!pMenu) + return; + + Invalidate(); + + pMenu->ImplKillLayoutData(); + + if ( bScrollUp && bUp ) + { + nFirstEntry = pMenu->ImplGetPrevVisible( nFirstEntry ); + SAL_WARN_IF( nFirstEntry == ITEMPOS_INVALID, "vcl", "Scroll?!" ); + + // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686) + const auto pItemData = pMenu->GetItemList()->GetDataFromPos( nFirstEntry ); + if ( pItemData ) + { + tools::Long nScrollEntryHeight = pItemData->aSz.Height(); + + if ( !bScrollDown ) + { + bScrollDown = true; + Invalidate(); + } + + if ( pMenu->ImplGetPrevVisible( nFirstEntry ) == ITEMPOS_INVALID ) + { + bScrollUp = false; + Invalidate(); + } + + Scroll( 0, nScrollEntryHeight, ImplCalcClipRegion().GetBoundRect(), ScrollFlags::Clip ); + } + } + else if ( bScrollDown && !bUp ) + { + // avoid crash if somehow menu got disposed, and MenuItemList is empty (workaround for tdf#104686) + const auto pItemData = pMenu->GetItemList()->GetDataFromPos( nFirstEntry ); + if ( pItemData ) + { + tools::Long nScrollEntryHeight = pItemData->aSz.Height(); + + nFirstEntry = pMenu->ImplGetNextVisible( nFirstEntry ); + SAL_WARN_IF( nFirstEntry == ITEMPOS_INVALID, "vcl", "Scroll?!" ); + + if ( !bScrollUp ) + { + bScrollUp = true; + Invalidate(); + } + + tools::Long nHeight = GetOutputSizePixel().Height(); + sal_uInt16 nLastVisible; + static_cast<PopupMenu*>(pMenu.get())->ImplCalcVisEntries( nHeight, nFirstEntry, &nLastVisible ); + if ( pMenu->ImplGetNextVisible( nLastVisible ) == ITEMPOS_INVALID ) + { + bScrollDown = false; + Invalidate(); + } + + Scroll( 0, -nScrollEntryHeight, ImplCalcClipRegion().GetBoundRect(), ScrollFlags::Clip ); + } + } + + Invalidate(); +} + +void MenuFloatingWindow::ImplScroll( const Point& rMousePos ) +{ + Size aOutSz = GetOutputSizePixel(); + + tools::Long nY = nScrollerHeight; + tools::Long nMouseY = rMousePos.Y(); + tools::Long nDelta = 0; + + if ( bScrollUp && ( nMouseY < nY ) ) + { + ImplScroll( true ); + nDelta = nY - nMouseY; + } + else if ( bScrollDown && ( nMouseY > ( aOutSz.Height() - nY ) ) ) + { + ImplScroll( false ); + nDelta = nMouseY - ( aOutSz.Height() - nY ); + } + + if ( !nDelta ) + return; + + aScrollTimer.Stop(); // if scrolled through MouseMove. + tools::Long nTimeout; + if ( nDelta < 3 ) + nTimeout = 200; + else if ( nDelta < 5 ) + nTimeout = 100; + else if ( nDelta < 8 ) + nTimeout = 70; + else if ( nDelta < 12 ) + nTimeout = 40; + else + nTimeout = 20; + aScrollTimer.SetTimeout( nTimeout ); + aScrollTimer.Start(); +} +void MenuFloatingWindow::ChangeHighlightItem( sal_uInt16 n, bool bStartPopupTimer ) +{ + // #57934# if necessary, immediately close the active, as TH's backgroundstorage works. + // #65750# we prefer to refrain from the background storage of small lines. + // otherwise the menus are difficult to operate. + // MenuItemData* pNextData = pMenu->pItemList->GetDataFromPos( n ); + // if ( pActivePopup && pNextData && ( pActivePopup != pNextData->pSubMenu ) ) + // KillActivePopup(); + + aSubmenuCloseTimer.Stop(); + if( ! pMenu ) + return; + + if ( nHighlightedItem != ITEMPOS_INVALID ) + { + InvalidateItem(nHighlightedItem); + pMenu->ImplCallEventListeners( VclEventId::MenuDehighlight, nHighlightedItem ); + } + + nHighlightedItem = n; + SAL_WARN_IF( !pMenu->ImplIsVisible( nHighlightedItem ) && nHighlightedItem != ITEMPOS_INVALID, "vcl", "ChangeHighlightItem: Not visible!" ); + if( nHighlightedItem != ITEMPOS_INVALID ) + { + if (pMenu->pStartedFrom && !pMenu->pStartedFrom->IsMenuBar()) + { + // #102461# make sure parent entry is highlighted as well + size_t i, nCount = pMenu->pStartedFrom->pItemList->size(); + for(i = 0; i < nCount; i++) + { + MenuItemData* pData = pMenu->pStartedFrom->pItemList->GetDataFromPos( i ); + if( pData && ( pData->pSubMenu == pMenu ) ) + break; + } + if( i < nCount ) + { + MenuFloatingWindow* pPWin = static_cast<MenuFloatingWindow*>(pMenu->pStartedFrom->ImplGetWindow()); + if( pPWin && pPWin->nHighlightedItem != i ) + { + pPWin->InvalidateItem(i); + pPWin->nHighlightedItem = i; + } + } + } + InvalidateItem(nHighlightedItem); + pMenu->ImplCallHighlight( nHighlightedItem ); + } + else + { + pMenu->nSelectedId = 0; + pMenu->sSelectedIdent.clear(); + } + + if ( bStartPopupTimer ) + { + // #102438# Menu items are not selectable + // If a menu item is selected by an AT-tool via the XAccessibleAction, XAccessibleValue + // or XAccessibleSelection interface, and the parent popup menus are not executed yet, + // the parent popup menus must be executed SYNCHRONOUSLY, before the menu item is selected. + if ( GetSettings().GetMouseSettings().GetMenuDelay() ) + aHighlightChangedTimer.Start(); + else + HighlightChanged( &aHighlightChangedTimer ); + } +} + +/// Calculate the initial vertical pixel offset of the first item. +/// May be negative for scrolled windows. +tools::Long MenuFloatingWindow::GetInitialItemY(tools::Long *pStartY) const +{ + tools::Long nStartY = ImplGetStartY(); + if (pStartY) + *pStartY = nStartY; + return nScrollerHeight + nStartY + + ImplGetSVData()->maNWFData.mnMenuFormatBorderY; +} + +/// Emit an Invalidate just for this item's area +void MenuFloatingWindow::InvalidateItem(sal_uInt16 nPos) +{ + if (!pMenu) + return; + + tools::Long nY = GetInitialItemY(); + size_t nCount = pMenu->pItemList->size(); + for (size_t n = 0; n < nCount; n++) + { + MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n ); + tools::Long nHeight = pData->aSz.Height(); + if (n == nPos) + { + Size aWidth( GetSizePixel() ); + tools::Rectangle aRect(Point(0, nY), Size(aWidth.Width(), nHeight)); + Invalidate( aRect ); + } + nY += nHeight; + } +} + +void MenuFloatingWindow::RenderHighlightItem(vcl::RenderContext& rRenderContext, sal_uInt16 nPos) +{ + if (!pMenu) + return; + + Size aSz(GetOutputSizePixel()); + + tools::Long nX = 0; + tools::Long nStartY; + tools::Long nY = GetInitialItemY(&nStartY); + + int nOuterSpaceX = ImplGetSVData()->maNWFData.mnMenuFormatBorderX; + + size_t nCount = pMenu->pItemList->size(); + for (size_t n = 0; n < nCount; n++) + { + MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n ); + if (n == nPos) + { + SAL_WARN_IF(!pMenu->ImplIsVisible(n), "vcl", "Highlight: Item not visible!"); + if (pData->eType != MenuItemType::SEPARATOR) + { + bool bRestoreLineColor = false; + Color oldLineColor; + bool bDrawItemRect = true; + + tools::Rectangle aItemRect(Point(nX + nOuterSpaceX, nY), Size(aSz.Width() - 2 * nOuterSpaceX, pData->aSz.Height())); + if (pData->nBits & MenuItemBits::POPUPSELECT) + { + tools::Long nFontHeight = GetTextHeight(); + aItemRect.AdjustRight( -(nFontHeight + nFontHeight / 4) ); + } + + if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire)) + { + Size aPxSize(GetOutputSizePixel()); + rRenderContext.Push(vcl::PushFlags::CLIPREGION); + rRenderContext.IntersectClipRegion(tools::Rectangle(Point(nX, nY), Size(aSz.Width(), pData->aSz.Height()))); + tools::Rectangle aCtrlRect(Point(nX, 0), Size(aPxSize.Width()-nX, aPxSize.Height())); + MenupopupValue aVal(pMenu->nTextPos-GUTTERBORDER, aItemRect); + rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire, + aCtrlRect, ControlState::ENABLED, aVal, OUString()); + if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItem)) + { + bDrawItemRect = false; + if (!rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::MenuItem, aItemRect, + ControlState::SELECTED | (pData->bEnabled + ? ControlState::ENABLED + : ControlState::NONE), + aVal, OUString())) + { + bDrawItemRect = true; + } + } + else + bDrawItemRect = true; + rRenderContext.Pop(); + } + if (bDrawItemRect) + { + if (pData->bEnabled) + rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor()); + else + { + rRenderContext.SetFillColor(); + oldLineColor = rRenderContext.GetLineColor(); + rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuHighlightColor()); + bRestoreLineColor = true; + } + + rRenderContext.DrawRect(aItemRect); + } + pMenu->ImplPaint(rRenderContext, GetOutputSizePixel(), nScrollerHeight, nStartY, pData, true/*bHighlight*/); + if (bRestoreLineColor) + rRenderContext.SetLineColor(oldLineColor); + } + return; + } + + nY += pData->aSz.Height(); + } +} + +tools::Rectangle MenuFloatingWindow::ImplGetItemRect( sal_uInt16 nPos ) const +{ + if( ! pMenu ) + return tools::Rectangle(); + + tools::Rectangle aRect; + Size aSz = GetOutputSizePixel(); + tools::Long nStartY = ImplGetStartY(); + tools::Long nY = nScrollerHeight+nStartY; + + size_t nCount = pMenu->pItemList->size(); + for ( size_t n = 0; n < nCount; n++ ) + { + MenuItemData* pData = pMenu->pItemList->GetDataFromPos( n ); + if ( n == nPos ) + { + SAL_WARN_IF( !pMenu->ImplIsVisible( n ), "vcl", "ImplGetItemRect: Item not visible!" ); + if ( pData->eType != MenuItemType::SEPARATOR ) + { + aRect = tools::Rectangle( Point( 0, nY ), Size( aSz.Width(), pData->aSz.Height() ) ); + if ( pData->nBits & MenuItemBits::POPUPSELECT ) + { + tools::Long nFontHeight = GetTextHeight(); + aRect.AdjustRight( -(nFontHeight + nFontHeight/4) ); + } + } + break; + } + nY += pData->aSz.Height(); + } + return aRect; +} + +void MenuFloatingWindow::ImplCursorUpDown( bool bUp, bool bHomeEnd ) +{ + if( ! pMenu ) + return; + + const StyleSettings& rSettings = GetSettings().GetStyleSettings(); + + sal_uInt16 n = nHighlightedItem; + if ( n == ITEMPOS_INVALID ) + { + if ( bUp ) + n = 0; + else + n = pMenu->GetItemCount()-1; + } + + sal_uInt16 nLoop = n; + + if( bHomeEnd ) + { + // absolute positioning + if( bUp ) + { + n = pMenu->GetItemCount(); + nLoop = n-1; + } + else + { + n = sal_uInt16(-1); + nLoop = n+1; + } + } + + do + { + if ( bUp ) + { + if ( n ) + n--; + else + if ( !IsScrollMenu() || ( nHighlightedItem == ITEMPOS_INVALID ) ) + n = pMenu->GetItemCount()-1; + else + break; + } + else + { + n++; + if ( n >= pMenu->GetItemCount() ) + { + if ( !IsScrollMenu() || ( nHighlightedItem == ITEMPOS_INVALID ) ) + n = 0; + else + break; + } + } + + MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( n ); + if ( ( pData->bEnabled || !rSettings.GetSkipDisabledInMenus() ) + && ( pData->eType != MenuItemType::SEPARATOR ) && pMenu->ImplIsVisible( n ) && pMenu->ImplIsSelectable( n ) ) + { + // Is selection in visible area? + if ( IsScrollMenu() ) + { + ChangeHighlightItem( ITEMPOS_INVALID, false ); + + while ( n < nFirstEntry ) + ImplScroll( true ); + + Size aOutSz = GetOutputSizePixel(); + sal_uInt16 nLastVisible; + static_cast<PopupMenu*>(pMenu.get())->ImplCalcVisEntries( aOutSz.Height(), nFirstEntry, &nLastVisible ); + while ( n > nLastVisible ) + { + ImplScroll( false ); + static_cast<PopupMenu*>(pMenu.get())->ImplCalcVisEntries( aOutSz.Height(), nFirstEntry, &nLastVisible ); + } + } + ChangeHighlightItem( n, false ); + break; + } + } while ( n != nLoop ); +} + +void MenuFloatingWindow::KeyInput( const KeyEvent& rKEvent ) +{ + VclPtr<vcl::Window> xWindow = this; + + sal_uInt16 nCode = rKEvent.GetKeyCode().GetCode(); + bKeyInput = true; + switch ( nCode ) + { + case KEY_UP: + case KEY_DOWN: + { + ImplCursorUpDown( nCode == KEY_UP ); + } + break; + case KEY_END: + case KEY_HOME: + { + ImplCursorUpDown( nCode == KEY_END, true ); + } + break; + case KEY_F6: + case KEY_ESCAPE: + { + // Ctrl-F6 acts like ESC here, the menu bar however will then put the focus in the document + if( nCode == KEY_F6 && !rKEvent.GetKeyCode().IsMod1() ) + break; + if( pMenu ) + { + if ( !pMenu->pStartedFrom ) + { + StopExecute(); + KillActivePopup(); + } + else if (pMenu->pStartedFrom->IsMenuBar()) + { + pMenu->pStartedFrom->MenuBarKeyInput(rKEvent); + } + else + { + StopExecute(); + PopupMenu* pPopupMenu = static_cast<PopupMenu*>(pMenu->pStartedFrom.get()); + MenuFloatingWindow* pFloat = pPopupMenu->ImplGetFloatingWindow(); + pFloat->GrabFocus(); + pFloat->KillActivePopup(); + pPopupMenu->ImplCallHighlight(pFloat->nHighlightedItem); + } + } + } + break; + case KEY_LEFT: + { + if ( pMenu && pMenu->pStartedFrom ) + { + StopExecute(); + if (pMenu->pStartedFrom->IsMenuBar()) + { + pMenu->pStartedFrom->MenuBarKeyInput(rKEvent); + } + else + { + MenuFloatingWindow* pFloat = static_cast<PopupMenu*>(pMenu->pStartedFrom.get())->ImplGetFloatingWindow(); + pFloat->GrabFocus(); + pFloat->KillActivePopup(); + sal_uInt16 highlightItem = pFloat->GetHighlightedItem(); + pFloat->ChangeHighlightItem(highlightItem, false); + } + } + } + break; + case KEY_RIGHT: + { + if( pMenu ) + { + bool bDone = false; + if ( nHighlightedItem != ITEMPOS_INVALID ) + { + MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem ); + if ( pData && pData->pSubMenu ) + { + HighlightChanged( nullptr ); + bDone = true; + } + } + if ( !bDone ) + { + Menu* pStart = pMenu->ImplGetStartMenu(); + if (pStart && pStart->IsMenuBar()) + { + // Forward... + pStart->ImplGetWindow()->KeyInput( rKEvent ); + } + } + } + } + break; + case KEY_RETURN: + { + if( pMenu ) + { + MenuItemData* pData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem ); + if ( pData && pData->bEnabled ) + { + if ( pData->pSubMenu ) + HighlightChanged( nullptr ); + else + EndExecute(); + } + else + StopExecute(); + } + } + break; + case KEY_MENU: + { + if( pMenu ) + { + Menu* pStart = pMenu->ImplGetStartMenu(); + if (pStart && pStart->IsMenuBar()) + { + // Forward... + pStart->ImplGetWindow()->KeyInput( rKEvent ); + } + } + } + break; + default: + { + sal_Unicode nCharCode = rKEvent.GetCharCode(); + size_t nPos = 0; + size_t nDuplicates = 0; + MenuItemData* pData = (nCharCode && pMenu) ? + pMenu->GetItemList()->SearchItem(nCharCode, rKEvent.GetKeyCode(), nPos, nDuplicates, nHighlightedItem) : nullptr; + if (pData) + { + if ( pData->pSubMenu || nDuplicates > 1 ) + { + ChangeHighlightItem( nPos, false ); + HighlightChanged( nullptr ); + } + else + { + nHighlightedItem = nPos; + EndExecute(); + } + } + else + FloatingWindow::KeyInput( rKEvent ); + } + } + + // #105474# check if menu window was not destroyed + if ( !xWindow->isDisposed() ) + { + bKeyInput = false; + } +} + +void MenuFloatingWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle &rPaintRect) +{ + if (!pMenu) + return; + + // Set the clip before the buffering starts: rPaintRect may be larger than the current clip, + // this way the buffer -> render context copy happens with this clip. + rRenderContext.Push(vcl::PushFlags::CLIPREGION); + rRenderContext.SetClipRegion(vcl::Region(rPaintRect)); + + // Make sure that all actual rendering happens in one go to avoid flicker. + vcl::BufferDevice pBuffer(this, rRenderContext); + + if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire)) + { + pBuffer->SetClipRegion(); + tools::Long nX = 0; + Size aPxSize(GetOutputSizePixel()); + aPxSize.AdjustWidth( -nX ); + ImplControlValue aVal(pMenu->nTextPos - GUTTERBORDER); + pBuffer->DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire, + tools::Rectangle(Point(nX, 0), aPxSize), ControlState::ENABLED, + aVal, OUString()); + InitMenuClipRegion(*pBuffer); + } + if (IsScrollMenu()) + { + ImplDrawScroller(*pBuffer, true); + ImplDrawScroller(*pBuffer, false); + } + pBuffer->SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetMenuColor()); + pMenu->ImplPaint(*pBuffer, GetOutputSizePixel(), nScrollerHeight, ImplGetStartY()); + if (nHighlightedItem != ITEMPOS_INVALID) + RenderHighlightItem(*pBuffer, nHighlightedItem); + + pBuffer.Dispose(); + rRenderContext.Pop(); +} + +void MenuFloatingWindow::ImplDrawScroller(vcl::RenderContext& rRenderContext, bool bUp) +{ + if (!pMenu) + return; + + rRenderContext.SetClipRegion(); + + Size aOutSz(GetOutputSizePixel()); + tools::Long nY = bUp ? 0 : (aOutSz.Height() - nScrollerHeight); + tools::Long nX = 0; + tools::Rectangle aRect(Point(nX, nY), Size(aOutSz.Width() - nX, nScrollerHeight)); + + DecorationView aDecoView(&rRenderContext); + SymbolType eSymbol = bUp ? SymbolType::SPIN_UP : SymbolType::SPIN_DOWN; + + DrawSymbolFlags nStyle = DrawSymbolFlags::NONE; + if ((bUp && !bScrollUp) || (!bUp && !bScrollDown)) + nStyle |= DrawSymbolFlags::Disable; + + aDecoView.DrawSymbol(aRect, eSymbol, rRenderContext.GetSettings().GetStyleSettings().GetButtonTextColor(), nStyle); + + InitMenuClipRegion(rRenderContext); +} + +void MenuFloatingWindow::RequestHelp( const HelpEvent& rHEvt ) +{ + sal_uInt16 nId = nHighlightedItem; + Menu* pM = pMenu; + vcl::Window* pW = this; + + // #102618# Get item rect before destroying the window in EndExecute() call + tools::Rectangle aHighlightRect( ImplGetItemRect( nHighlightedItem ) ); + + if ( rHEvt.GetMode() & HelpEventMode::CONTEXT ) + { + nHighlightedItem = ITEMPOS_INVALID; + EndExecute(); + pW = nullptr; + } + + if( !ImplHandleHelpEvent( pW, pM, nId, rHEvt, aHighlightRect ) ) + Window::RequestHelp( rHEvt ); +} + +void MenuFloatingWindow::StateChanged( StateChangedType nType ) +{ + FloatingWindow::StateChanged( nType ); + + if ( ( nType == StateChangedType::ControlForeground ) || ( nType == StateChangedType::ControlBackground ) ) + { + ApplySettings(*GetOutDev()); + Invalidate(); + } +} + +void MenuFloatingWindow::DataChanged( const DataChangedEvent& rDCEvt ) +{ + FloatingWindow::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) ) + { + ApplySettings(*GetOutDev()); + Invalidate(); + } +} + +void MenuFloatingWindow::Command( const CommandEvent& rCEvt ) +{ + if ( rCEvt.GetCommand() == CommandEventId::Wheel ) + { + const CommandWheelData* pData = rCEvt.GetWheelData(); + if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) ) + { + ImplScroll( pData->GetDelta() > 0 ); + MouseMove( MouseEvent( GetPointerPosPixel(), 0 ) ); + } + } +} + +css::uno::Reference<css::accessibility::XAccessible> MenuFloatingWindow::CreateAccessible() +{ + css::uno::Reference<css::accessibility::XAccessible> xAcc; + + if (pMenu && !pMenu->pStartedFrom) + xAcc = pMenu->GetAccessible(); + + return xAcc; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/menufloatingwindow.hxx b/vcl/source/window/menufloatingwindow.hxx new file mode 100644 index 000000000..f26fb5037 --- /dev/null +++ b/vcl/source/window/menufloatingwindow.hxx @@ -0,0 +1,128 @@ +/* -*- 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 "menuwindow.hxx" + +#include <vcl/toolkit/floatwin.hxx> +#include <vcl/menu.hxx> + +#define EXTRASPACEY 2 +#define GUTTERBORDER 8 + +/** Class that implements the actual window of the floating menu. +*/ +class MenuFloatingWindow : public FloatingWindow, public MenuWindow +{ + friend void Menu::ImplFillLayoutData() const; + friend void Menu::dispose(); + +private: + VclPtr<Menu> pMenu; + VclPtr<PopupMenu> pActivePopup; + Timer aHighlightChangedTimer; + Timer aSubmenuCloseTimer; + Timer aScrollTimer; + VclPtr<vcl::Window> xSaveFocusId; + sal_uInt16 nHighlightedItem; // highlighted/selected Item + sal_uInt16 nMBDownPos; + sal_uInt16 nScrollerHeight; + sal_uInt16 nFirstEntry; + sal_uInt16 nPosInParent; + + bool bInExecute : 1; + bool bScrollMenu : 1; + bool bScrollUp : 1; + bool bScrollDown : 1; + bool bIgnoreFirstMove : 1; + bool bKeyInput : 1; + + DECL_LINK( PopupEnd, FloatingWindow*, void ); + DECL_LINK( HighlightChanged, Timer*, void ); + DECL_LINK( SubmenuClose, Timer *, void ); + DECL_LINK( AutoScroll, Timer *, void ); + DECL_LINK( ShowHideListener, VclWindowEvent&, void ); + + virtual void StateChanged( StateChangedType nType ) override; + virtual void DataChanged( const DataChangedEvent& rDCEvt ) override; + + void InitMenuClipRegion(vcl::RenderContext& rRenderContext); + + void Start(); + void End(); + +protected: + vcl::Region ImplCalcClipRegion() const; + void ImplDrawScroller(vcl::RenderContext& rRenderContext, bool bUp); + using Window::ImplScroll; + void ImplScroll( const Point& rMousePos ); + void ImplScroll( bool bUp ); + void ImplCursorUpDown( bool bUp, bool bHomeEnd = false ); + void ImplHighlightItem( const MouseEvent& rMEvt, bool bMBDown ); + tools::Long ImplGetStartY() const; + tools::Rectangle ImplGetItemRect( sal_uInt16 nPos ) const; + void RenderHighlightItem( vcl::RenderContext& rRenderContext, sal_uInt16 nPos ); + tools::Long GetInitialItemY( tools::Long *pOptStartY = nullptr ) const; + void InvalidateItem( sal_uInt16 nPos ); + +public: + MenuFloatingWindow(Menu* pMenu, vcl::Window* pParent, WinBits nStyle); + virtual ~MenuFloatingWindow() override; + + virtual void dispose() override; + void doShutdown(); + + virtual void MouseMove(const MouseEvent& rMEvt) override; + virtual void MouseButtonDown(const MouseEvent& rMEvt) override; + virtual void MouseButtonUp(const MouseEvent& rMEvt) override; + virtual void KeyInput(const KeyEvent& rKEvent) override; + virtual void Command(const CommandEvent& rCEvt) override; + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override; + virtual void RequestHelp( const HelpEvent& rHEvt ) override; + virtual void Resize() override; + + virtual void ApplySettings(vcl::RenderContext& rRenderContext) override; + + void SetFocusId( const VclPtr<vcl::Window>& xId ) { xSaveFocusId = xId; } + const VclPtr<vcl::Window>& GetFocusId() const { return xSaveFocusId; } + + void EnableScrollMenu( bool b ); + bool IsScrollMenu() const { return bScrollMenu; } + sal_uInt16 GetScrollerHeight() const { return nScrollerHeight; } + + void Execute(); + void StopExecute(); + void EndExecute(); + void EndExecute( sal_uInt16 nSelectId ); + + PopupMenu* GetActivePopup() const { return pActivePopup; } + void KillActivePopup( PopupMenu* pThisOnly = nullptr ); + + void ChangeHighlightItem(sal_uInt16 n, bool bStartPopupTimer); + sal_uInt16 GetHighlightedItem() const { return nHighlightedItem; } + + void SetPosInParent( sal_uInt16 nPos ) { nPosInParent = nPos; } + + bool MenuInHierarchyHasFocus() const; + + virtual css::uno::Reference<css::accessibility::XAccessible> CreateAccessible() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/menuitemlist.cxx b/vcl/source/window/menuitemlist.cxx new file mode 100644 index 000000000..173a6204e --- /dev/null +++ b/vcl/source/window/menuitemlist.cxx @@ -0,0 +1,314 @@ +/* -*- 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 "menuitemlist.hxx" + +#include <salframe.hxx> +#include <salinst.hxx> +#include <salmenu.hxx> +#include <svdata.hxx> +#include <vcl/i18nhelp.hxx> +#include <vcl/settings.hxx> +#include <vcl/vcllayout.hxx> +#include <vcl/window.hxx> + +using namespace css; +using namespace vcl; + +MenuItemData::~MenuItemData() +{ + if (aUserValueReleaseFunc) + aUserValueReleaseFunc(nUserValue); + pSalMenuItem.reset(); + pSubMenu.disposeAndClear(); +} + +SalLayoutGlyphs* MenuItemData::GetTextGlyphs(const OutputDevice* pOutputDevice) +{ + if (aTextGlyphs.IsValid()) + // Use pre-calculated result. + return &aTextGlyphs; + + OUString aNonMnemonicString = OutputDevice::GetNonMnemonicString(aText); + std::unique_ptr<SalLayout> pLayout + = pOutputDevice->ImplLayout(aNonMnemonicString, 0, aNonMnemonicString.getLength(), + Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly); + if (!pLayout) + return nullptr; + + // Remember the calculation result. + aTextGlyphs = pLayout->GetGlyphs(); + + return &aTextGlyphs; +} + +MenuItemList::~MenuItemList() +{ +} + +MenuItemData* MenuItemList::Insert( + sal_uInt16 nId, + MenuItemType eType, + MenuItemBits nBits, + const OUString& rStr, + Menu* pMenu, + size_t nPos, + const OString &rIdent +) +{ + MenuItemData* pData = new MenuItemData( rStr ); + pData->nId = nId; + pData->sIdent = rIdent; + pData->eType = eType; + pData->nBits = nBits; + pData->pSubMenu = nullptr; + pData->nUserValue = nullptr; + pData->bChecked = false; + pData->bEnabled = true; + pData->bVisible = true; + pData->bIsTemporary = false; + + SalItemParams aSalMIData; + aSalMIData.nId = nId; + aSalMIData.eType = eType; + aSalMIData.nBits = nBits; + aSalMIData.pMenu = pMenu; + aSalMIData.aText = rStr; + + // Native-support: returns NULL if not supported + pData->pSalMenuItem = ImplGetSVData()->mpDefInst->CreateMenuItem( aSalMIData ); + + if( nPos < maItemList.size() ) { + maItemList.insert( maItemList.begin() + nPos, std::unique_ptr<MenuItemData>(pData) ); + } else { + maItemList.emplace_back( pData ); + } + return pData; +} + +void MenuItemList::InsertSeparator(const OString &rIdent, size_t nPos) +{ + MenuItemData* pData = new MenuItemData; + pData->nId = 0; + pData->sIdent = rIdent; + pData->eType = MenuItemType::SEPARATOR; + pData->nBits = MenuItemBits::NONE; + pData->pSubMenu = nullptr; + pData->nUserValue = nullptr; + pData->bChecked = false; + pData->bEnabled = true; + pData->bVisible = true; + pData->bIsTemporary = false; + + SalItemParams aSalMIData; + aSalMIData.nId = 0; + aSalMIData.eType = MenuItemType::SEPARATOR; + aSalMIData.nBits = MenuItemBits::NONE; + aSalMIData.pMenu = nullptr; + aSalMIData.aText.clear(); + aSalMIData.aImage = Image(); + + // Native-support: returns NULL if not supported + pData->pSalMenuItem = ImplGetSVData()->mpDefInst->CreateMenuItem( aSalMIData ); + + if( nPos < maItemList.size() ) { + maItemList.insert( maItemList.begin() + nPos, std::unique_ptr<MenuItemData>(pData) ); + } else { + maItemList.emplace_back( pData ); + } +} + +void MenuItemList::Remove( size_t nPos ) +{ + if( nPos < maItemList.size() ) + { + maItemList.erase( maItemList.begin() + nPos ); + } +} + +void MenuItemList::Clear() +{ + maItemList.clear(); +} + +MenuItemData* MenuItemList::GetData( sal_uInt16 nSVId, size_t& rPos ) const +{ + for( size_t i = 0, n = maItemList.size(); i < n; ++i ) + { + if ( maItemList[ i ]->nId == nSVId ) + { + rPos = i; + return maItemList[ i ].get(); + } + } + return nullptr; +} + +MenuItemData* MenuItemList::SearchItem( + sal_Unicode cSelectChar, + KeyCode aKeyCode, + size_t& rPos, + size_t& nDuplicates, + size_t nCurrentPos +) const +{ + const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper(); + + size_t nListCount = maItemList.size(); + + // try character code first + nDuplicates = GetItemCount( cSelectChar ); // return number of duplicates + if( nDuplicates ) + { + MenuItemData* pFirstMatch = nullptr; + size_t nFirstPos(0); + for ( rPos = 0; rPos < nListCount; rPos++) + { + MenuItemData* pData = maItemList[ rPos ].get(); + if ( pData->bEnabled && rI18nHelper.MatchMnemonic( pData->aText, cSelectChar ) ) + { + if (nDuplicates == 1) + return pData; + if (rPos > nCurrentPos) + return pData; // select next entry with the same mnemonic + if (!pFirstMatch) // stash the first match for use if nothing follows nCurrentPos + { + pFirstMatch = pData; + nFirstPos = rPos; + } + } + } + if (pFirstMatch) + { + rPos = nFirstPos; + return pFirstMatch; + } + } + + // nothing found, try keycode instead + nDuplicates = GetItemCount( aKeyCode ); // return number of duplicates + + if( nDuplicates ) + { + char ascii = 0; + if( aKeyCode.GetCode() >= KEY_A && aKeyCode.GetCode() <= KEY_Z ) + ascii = sal::static_int_cast<char>('A' + (aKeyCode.GetCode() - KEY_A)); + + MenuItemData* pFirstMatch = nullptr; + size_t nFirstPos(0); + for ( rPos = 0; rPos < nListCount; rPos++) + { + MenuItemData* pData = maItemList[ rPos ].get(); + if ( pData->bEnabled ) + { + sal_Int32 n = pData->aText.indexOf('~'); + if ( n != -1 ) + { + KeyCode nKeyCode; + sal_Unicode nUnicode = pData->aText[n+1]; + vcl::Window* pDefWindow = ImplGetDefaultWindow(); + if( ( pDefWindow + && pDefWindow->ImplGetFrame()->MapUnicodeToKeyCode( nUnicode, + Application::GetSettings().GetUILanguageTag().getLanguageType(), nKeyCode ) + && aKeyCode.GetCode() == nKeyCode.GetCode() + ) + || ( ascii + && rI18nHelper.MatchMnemonic( pData->aText, ascii ) + ) + ) + { + if (nDuplicates == 1) + return pData; + if (rPos > nCurrentPos) + return pData; // select next entry with the same mnemonic + if (!pFirstMatch) // stash the first match for use if nothing follows nCurrentPos + { + pFirstMatch = pData; + nFirstPos = rPos; + } + } + } + } + } + if (pFirstMatch) + { + rPos = nFirstPos; + return pFirstMatch; + } + } + + return nullptr; +} + +size_t MenuItemList::GetItemCount( sal_Unicode cSelectChar ) const +{ + // returns number of entries with same mnemonic + const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper(); + + size_t nItems = 0; + for ( size_t nPos = maItemList.size(); nPos; ) + { + MenuItemData* pData = maItemList[ --nPos ].get(); + if ( pData->bEnabled && rI18nHelper.MatchMnemonic( pData->aText, cSelectChar ) ) + nItems++; + } + + return nItems; +} + +size_t MenuItemList::GetItemCount( KeyCode aKeyCode ) const +{ + // returns number of entries with same mnemonic + // uses key codes instead of character codes + const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper(); + char ascii = 0; + if( aKeyCode.GetCode() >= KEY_A && aKeyCode.GetCode() <= KEY_Z ) + ascii = sal::static_int_cast<char>('A' + (aKeyCode.GetCode() - KEY_A)); + + size_t nItems = 0; + for ( size_t nPos = maItemList.size(); nPos; ) + { + MenuItemData* pData = maItemList[ --nPos ].get(); + if ( pData->bEnabled ) + { + sal_Int32 n = pData->aText.indexOf('~'); + if (n != -1) + { + KeyCode nKeyCode; + // if MapUnicodeToKeyCode fails or is unsupported we try the pure ascii mapping of the keycodes + // so we have working shortcuts when ascii mnemonics are used + vcl::Window* pDefWindow = ImplGetDefaultWindow(); + if( ( pDefWindow + && pDefWindow->ImplGetFrame()->MapUnicodeToKeyCode( pData->aText[n+1], + Application::GetSettings().GetUILanguageTag().getLanguageType(), nKeyCode ) + && aKeyCode.GetCode() == nKeyCode.GetCode() + ) + || ( ascii + && rI18nHelper.MatchMnemonic( pData->aText, ascii ) + ) + ) + nItems++; + } + } + } + + return nItems; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/menuitemlist.hxx b/vcl/source/window/menuitemlist.hxx new file mode 100644 index 000000000..25af6fc6d --- /dev/null +++ b/vcl/source/window/menuitemlist.hxx @@ -0,0 +1,150 @@ +/* -*- 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/vclenum.hxx> +#include <vcl/glyphitem.hxx> +#include <vcl/image.hxx> +#include <vcl/keycod.hxx> +#include <vcl/menu.hxx> +#include <salmenu.hxx> + +#include <memory> +#include <vector> + +class SalMenuItem; + +struct MenuItemData +{ + sal_uInt16 nId; // SV Id + MenuItemType eType; // MenuItem-Type + MenuItemBits nBits; // MenuItem-Bits + VclPtr<Menu> pSubMenu; // Pointer to SubMenu + OUString aText; // Menu-Text + SalLayoutGlyphs aTextGlyphs; ///< Text layout of aText. + OUString aHelpText; // Help-String + OUString aTipHelpText; // TipHelp-String (eg, expanded filenames) + OUString aCommandStr; // CommandString + OUString aHelpCommandStr; // Help command string (to reference external help) + OString sIdent; + OString aHelpId; // Help-Id + void* nUserValue; // User value + MenuUserDataReleaseFunction aUserValueReleaseFunc; // called when MenuItemData is destroyed + Image aImage; // Image + vcl::KeyCode aAccelKey; // Accelerator-Key + bool bChecked; // Checked + bool bEnabled; // Enabled + bool bVisible; // Visible (note: this flag will not override MenuFlags::HideDisabledEntries when true) + bool bIsTemporary; // Temporary inserted ('No selection possible') + bool bHiddenOnGUI; + Size aSz; // only temporarily valid + OUString aAccessibleName; // accessible name + OUString aAccessibleDescription; // accessible description + + std::unique_ptr<SalMenuItem> pSalMenuItem; // access to native menu + + MenuItemData() + : nId(0) + , eType(MenuItemType::DONTKNOW) + , nBits(MenuItemBits::NONE) + , pSubMenu(nullptr) + , nUserValue(nullptr) + , aUserValueReleaseFunc(nullptr) + , bChecked(false) + , bEnabled(false) + , bVisible(false) + , bIsTemporary(false) + , bHiddenOnGUI(false) + { + } + MenuItemData( const OUString& rStr ) + : nId(0) + , eType(MenuItemType::DONTKNOW) + , nBits(MenuItemBits::NONE) + , pSubMenu(nullptr) + , aText(rStr) + , nUserValue(nullptr) + , aUserValueReleaseFunc(nullptr) + , aImage() + , bChecked(false) + , bEnabled(false) + , bVisible(false) + , bIsTemporary(false) + , bHiddenOnGUI(false) + { + } + ~MenuItemData(); + + /// Computes aText's text layout (glyphs), cached in aTextGlyphs. + SalLayoutGlyphs* GetTextGlyphs(const OutputDevice* pOutputDevice); + + bool HasCheck() const + { + return bChecked || ( nBits & ( MenuItemBits::RADIOCHECK | MenuItemBits::CHECKABLE | MenuItemBits::AUTOCHECK ) ); + } +}; + +class MenuItemList +{ +private: + ::std::vector< std::unique_ptr<MenuItemData> > maItemList; + +public: + MenuItemList() {} + ~MenuItemList(); + + MenuItemData* Insert( + sal_uInt16 nId, + MenuItemType eType, + MenuItemBits nBits, + const OUString& rStr, + Menu* pMenu, + size_t nPos, + const OString &rIdent + ); + void InsertSeparator(const OString &rIdent, size_t nPos); + void Remove( size_t nPos ); + void Clear(); + + MenuItemData* GetData( sal_uInt16 nSVId, size_t& rPos ) const; + MenuItemData* GetData( sal_uInt16 nSVId ) const + { + size_t nTemp; + return GetData( nSVId, nTemp ); + } + MenuItemData* GetDataFromPos( size_t nPos ) const + { + return ( nPos < maItemList.size() ) ? maItemList[ nPos ].get() : nullptr; + } + + MenuItemData* SearchItem( + sal_Unicode cSelectChar, + vcl::KeyCode aKeyCode, + size_t& rPos, + size_t& nDuplicates, + size_t nCurrentPos + ) const; + size_t GetItemCount( sal_Unicode cSelectChar ) const; + size_t GetItemCount( vcl::KeyCode aKeyCode ) const; + size_t size() const + { + return maItemList.size(); + } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/menuwindow.cxx b/vcl/source/window/menuwindow.cxx new file mode 100644 index 000000000..a3412e077 --- /dev/null +++ b/vcl/source/window/menuwindow.cxx @@ -0,0 +1,111 @@ +/* -*- 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 "menuwindow.hxx" +#include "menuitemlist.hxx" + +#include <vcl/help.hxx> +#include <vcl/menu.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> + +static sal_uLong ImplChangeTipTimeout( sal_uLong nTimeout, vcl::Window *pWindow ) +{ + AllSettings aAllSettings( pWindow->GetSettings() ); + HelpSettings aHelpSettings( aAllSettings.GetHelpSettings() ); + sal_uLong nRet = aHelpSettings.GetTipTimeout(); + aHelpSettings.SetTipTimeout( nTimeout ); + aAllSettings.SetHelpSettings( aHelpSettings ); + pWindow->GetOutDev()->SetSettings( aAllSettings ); + return nRet; +} + +bool MenuWindow::ImplHandleHelpEvent(vcl::Window* pMenuWindow, Menu const * pMenu, sal_uInt16 nHighlightedItem, + const HelpEvent& rHEvt, const tools::Rectangle &rHighlightRect) +{ + if( ! pMenu ) + return false; + + bool bDone = false; + sal_uInt16 nId = 0; + + if ( nHighlightedItem != ITEMPOS_INVALID ) + { + MenuItemData* pItemData = pMenu->GetItemList()->GetDataFromPos( nHighlightedItem ); + if ( pItemData ) + nId = pItemData->nId; + } + + if ( ( rHEvt.GetMode() & HelpEventMode::BALLOON ) && pMenuWindow ) + { + Point aPos; + if( rHEvt.KeyboardActivated() ) + aPos = rHighlightRect.Center(); + else + aPos = rHEvt.GetMousePosPixel(); + + tools::Rectangle aRect( aPos, Size() ); + if (!pMenu->GetHelpText(nId).isEmpty()) + Help::ShowBalloon( pMenuWindow, aPos, aRect, pMenu->GetHelpText( nId ) ); + else + { + // give user a chance to read the full filename + sal_uLong oldTimeout=ImplChangeTipTimeout( 60000, pMenuWindow ); + // call always, even when strlen==0 to correctly remove tip + Help::ShowQuickHelp( pMenuWindow, aRect, pMenu->GetTipHelpText( nId ) ); + ImplChangeTipTimeout( oldTimeout, pMenuWindow ); + } + bDone = true; + } + else if ( ( rHEvt.GetMode() &HelpEventMode::QUICK ) && pMenuWindow ) + { + Point aPos = rHEvt.GetMousePosPixel(); + tools::Rectangle aRect( aPos, Size() ); + // give user a chance to read the full filename + sal_uLong oldTimeout=ImplChangeTipTimeout( 60000, pMenuWindow ); + // call always, even when strlen==0 to correctly remove tip + Help::ShowQuickHelp( pMenuWindow, aRect, pMenu->GetTipHelpText( nId ) ); + ImplChangeTipTimeout( oldTimeout, pMenuWindow ); + bDone = true; + } + else if ( rHEvt.GetMode() & HelpEventMode::CONTEXT ) + { + // is help in the application selected + Help* pHelp = Application::GetHelp(); + if ( pHelp ) + { + // is an id available, then call help with the id, otherwise + // use help-index + OUString aCommand = pMenu->GetItemCommand( nId ); + OString aHelpId( pMenu->GetHelpId( nId ) ); + if( aHelpId.isEmpty() ) + aHelpId = OOO_HELP_INDEX; + + if ( !aCommand.isEmpty() ) + pHelp->Start(aCommand); + else + pHelp->Start(OStringToOUString(aHelpId, RTL_TEXTENCODING_UTF8)); + } + bDone = true; + } + return bDone; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/menuwindow.hxx b/vcl/source/window/menuwindow.hxx new file mode 100644 index 000000000..df0606b88 --- /dev/null +++ b/vcl/source/window/menuwindow.hxx @@ -0,0 +1,56 @@ +/* -*- 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/types.h> +#include <vcl/event.hxx> + +class HelpEvent; +class Image; +class Menu; +class MenuBar; +namespace tools { class Rectangle; } +namespace vcl { class Window; } + +/** Common ancestor for MenuFloatingWindow and MenuBarWindow. + +The menu can be a floating window, or a menu bar. Even though this has +'Window' in the name, it is not derived from the VCL's Window class, as the +MenuFloatingWindow's or MenuBarWindow's already are VCL Windows. + +TODO: move here stuff that was a mentioned previously when there was no +common class for MenuFloatingWindow and MenuBarWindow: + +// a basic class for both (due to pActivePopup, Timer,...) would be nice, +// but a container class should have been created then, as they +// would be derived from different windows +// In most functions we would have to create exceptions for +// menubar, popupmenu, hence we made two classes + +*/ +class MenuWindow +{ +protected: + /// Show the appropriate help tooltip. + static bool ImplHandleHelpEvent(vcl::Window* pMenuWindow, Menu const * pMenu, sal_uInt16 nHighlightedItem, + const HelpEvent& rHEvt, const tools::Rectangle &rHighlightRect); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/mnemonic.cxx b/vcl/source/window/mnemonic.cxx new file mode 100644 index 000000000..6e4b155c2 --- /dev/null +++ b/vcl/source/window/mnemonic.cxx @@ -0,0 +1,348 @@ +/* -*- 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 <string.h> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/mnemonic.hxx> + +#include <vcl/unohelp.hxx> +#include <com/sun/star/i18n/XCharacterClassification.hpp> +#include <i18nlangtag/languagetag.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <rtl/character.hxx> +#include <sal/log.hxx> + +using namespace ::com::sun::star; + +MnemonicGenerator::MnemonicGenerator(sal_Unicode cMnemonic) + : m_cMnemonic(cMnemonic) +{ + memset( maMnemonics, 1, sizeof( maMnemonics ) ); +} + +sal_uInt16 MnemonicGenerator::ImplGetMnemonicIndex( sal_Unicode c ) +{ + static sal_uInt16 const aImplMnemonicRangeTab[MNEMONIC_RANGES*2] = + { + MNEMONIC_RANGE_1_START, MNEMONIC_RANGE_1_END, + MNEMONIC_RANGE_2_START, MNEMONIC_RANGE_2_END, + MNEMONIC_RANGE_3_START, MNEMONIC_RANGE_3_END, + MNEMONIC_RANGE_4_START, MNEMONIC_RANGE_4_END + }; + + sal_uInt16 nMnemonicIndex = 0; + for ( sal_uInt16 i = 0; i < MNEMONIC_RANGES; i++ ) + { + if ( (c >= aImplMnemonicRangeTab[i*2]) && + (c <= aImplMnemonicRangeTab[i*2+1]) ) + return nMnemonicIndex+c-aImplMnemonicRangeTab[i*2]; + + nMnemonicIndex += aImplMnemonicRangeTab[i*2+1]-aImplMnemonicRangeTab[i*2]; + } + + return MNEMONIC_INDEX_NOTFOUND; +} + +sal_Unicode MnemonicGenerator::ImplFindMnemonic( const OUString& rKey ) +{ + sal_Int32 nIndex = 0; + while ( (nIndex = rKey.indexOf( m_cMnemonic, nIndex )) != -1 ) + { + if (nIndex == rKey.getLength() - 1) { + SAL_WARN("vcl", "key \"" << rKey << "\" ends in lone mnemonic prefix"); + break; + } + sal_Unicode cMnemonic = rKey[ nIndex+1 ]; + if ( cMnemonic != m_cMnemonic ) + return cMnemonic; + nIndex += 2; + } + + return 0; +} + +void MnemonicGenerator::RegisterMnemonic( const OUString& rKey ) +{ + uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass(); + + // Don't crash even when we don't have access to i18n service + if ( !xCharClass.is() ) + return; + + OUString aKey = xCharClass->toLower(rKey, 0, rKey.getLength(), css::lang::Locale()); + + // If we find a Mnemonic, set the flag. In other case count the + // characters, because we need this to set most as possible + // Mnemonics + sal_Unicode cMnemonic = ImplFindMnemonic( aKey ); + if ( cMnemonic ) + { + sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( cMnemonic ); + if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND ) + maMnemonics[nMnemonicIndex] = 0; + } + else + { + sal_Int32 nIndex = 0; + sal_Int32 nLen = aKey.getLength(); + while ( nIndex < nLen ) + { + sal_Unicode c = aKey[ nIndex ]; + + sal_uInt16 nMnemonicIndex = ImplGetMnemonicIndex( c ); + if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND ) + { + if ( maMnemonics[nMnemonicIndex] && (maMnemonics[nMnemonicIndex] < 0xFF) ) + maMnemonics[nMnemonicIndex]++; + } + + nIndex++; + } + } +} + +OUString MnemonicGenerator::CreateMnemonic( const OUString& _rKey ) +{ + if ( _rKey.isEmpty() || ImplFindMnemonic( _rKey ) ) + return _rKey; + + uno::Reference < i18n::XCharacterClassification > xCharClass = GetCharClass(); + + // Don't crash even when we don't have access to i18n service + if ( !xCharClass.is() ) + return _rKey; + + OUString aKey = xCharClass->toLower(_rKey, 0, _rKey.getLength(), css::lang::Locale()); + + bool bChanged = false; + sal_Int32 nLen = aKey.getLength(); + + bool bCJK = MsLangId::isCJK(Application::GetSettings().GetUILanguageTag().getLanguageType()); + + // #107889# in CJK versions ALL strings (even those that contain latin characters) + // will get mnemonics in the form: xyz (M) + // thus steps 1) and 2) are skipped for CJK locales + + // #110720#, avoid CJK-style mnemonics for latin-only strings that do not contain useful mnemonic chars + if( bCJK ) + { + bool bLatinOnly = true; + bool bMnemonicIndexFound = false; + sal_Unicode c; + sal_Int32 nIndex; + + for( nIndex=0; nIndex < nLen; nIndex++ ) + { + c = aKey[ nIndex ]; + if ( ((c >= 0x3000) && (c <= 0xD7FF)) || // cjk + ((c >= 0xFF61) && (c <= 0xFFDC)) ) // halfwidth forms + { + bLatinOnly = false; + break; + } + if( ImplGetMnemonicIndex( c ) != MNEMONIC_INDEX_NOTFOUND ) + bMnemonicIndexFound = true; + } + if( bLatinOnly && !bMnemonicIndexFound ) + return _rKey; + } + + OUString rKey(_rKey); + int nCJK = 0; + sal_uInt16 nMnemonicIndex; + sal_Unicode c; + sal_Int32 nIndex = 0; + if( !bCJK ) + { + // 1) first try the first character of a word + do + { + c = aKey[ nIndex ]; + + if ( nCJK != 2 ) + { + if ( ((c >= 0x3000) && (c <= 0xD7FF)) || // cjk + ((c >= 0xFF61) && (c <= 0xFFDC)) ) // halfwidth forms + nCJK = 1; + else if ( ((c >= 0x0030) && (c <= 0x0039)) || // digits + ((c >= 0x0041) && (c <= 0x005A)) || // latin capitals + ((c >= 0x0061) && (c <= 0x007A)) || // latin small + ((c >= 0x0370) && (c <= 0x037F)) || // greek numeral signs + ((c >= 0x0400) && (c <= 0x04FF)) ) // cyrillic + nCJK = 2; + } + + nMnemonicIndex = ImplGetMnemonicIndex( c ); + if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND ) + { + if ( maMnemonics[nMnemonicIndex] ) + { + maMnemonics[nMnemonicIndex] = 0; + rKey = rKey.replaceAt( nIndex, 0, rtl::OUStringChar(m_cMnemonic) ); + bChanged = true; + break; + } + } + + // Search for next word + nIndex++; + while ( nIndex < nLen ) + { + c = aKey[ nIndex ]; + if ( c == ' ' ) + break; + nIndex++; + } + nIndex++; + } + while ( nIndex < nLen ); + + // 2) search for a unique/uncommon character + if ( !bChanged ) + { + sal_uInt16 nBestCount = 0xFFFF; + sal_uInt16 nBestMnemonicIndex = 0; + sal_Int32 nBestIndex = 0; + nIndex = 0; + do + { + c = aKey[ nIndex ]; + nMnemonicIndex = ImplGetMnemonicIndex( c ); + if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND ) + { + if ( maMnemonics[nMnemonicIndex] ) + { + if ( maMnemonics[nMnemonicIndex] < nBestCount ) + { + nBestCount = maMnemonics[nMnemonicIndex]; + nBestIndex = nIndex; + nBestMnemonicIndex = nMnemonicIndex; + if ( nBestCount == 2 ) + break; + } + } + } + + nIndex++; + } + while ( nIndex < nLen ); + + if ( nBestCount != 0xFFFF ) + { + maMnemonics[nBestMnemonicIndex] = 0; + rKey = rKey.replaceAt( nBestIndex, 0, rtl::OUStringChar(m_cMnemonic) ); + bChanged = true; + } + } + } + else + nCJK = 1; + + // 3) Add English Mnemonic for CJK Text + if ( !bChanged && (nCJK == 1) && !rKey.isEmpty() ) + { + // Append Ascii Mnemonic + for ( c = MNEMONIC_RANGE_2_START; c <= MNEMONIC_RANGE_2_END; c++ ) + { + nMnemonicIndex = ImplGetMnemonicIndex(c); + if ( nMnemonicIndex != MNEMONIC_INDEX_NOTFOUND ) + { + if ( maMnemonics[nMnemonicIndex] ) + { + maMnemonics[nMnemonicIndex] = 0; + OUString aStr = OUString::Concat("(") + OUStringChar(m_cMnemonic) + + OUStringChar(sal_Unicode(rtl::toAsciiUpperCase(c))) + + ")"; + nIndex = rKey.getLength(); + if( nIndex >= 2 ) + { + if ( ( rKey[nIndex-2] == '>' && rKey[nIndex-1] == '>' ) || + ( rKey[nIndex-2] == 0xFF1E && rKey[nIndex-1] == 0xFF1E ) ) + nIndex -= 2; + } + if( nIndex >= 3 ) + { + if ( ( rKey[nIndex-3] == '.' && rKey[nIndex-2] == '.' && rKey[nIndex-1] == '.' ) || + ( rKey[nIndex-3] == 0xFF0E && rKey[nIndex-2] == 0xFF0E && rKey[nIndex-1] == 0xFF0E ) ) + nIndex -= 3; + } + if( nIndex >= 1) + { + sal_Unicode cLastChar = rKey[ nIndex-1 ]; + if ( (cLastChar == ':') || (cLastChar == 0xFF1A) || + (cLastChar == '.') || (cLastChar == 0xFF0E) || + (cLastChar == '?') || (cLastChar == 0xFF1F) || + (cLastChar == ' ') ) + nIndex--; + } + rKey = rKey.replaceAt( nIndex, 0, aStr ); + break; + } + } + } + } + + return rKey; +} + +uno::Reference< i18n::XCharacterClassification > const & MnemonicGenerator::GetCharClass() +{ + if ( !mxCharClass.is() ) + mxCharClass = vcl::unohelper::CreateCharacterClassification(); + return mxCharClass; +} + +OUString MnemonicGenerator::EraseAllMnemonicChars( const OUString& rStr ) +{ + OUString aStr = rStr; + sal_Int32 nLen = aStr.getLength(); + sal_Int32 i = 0; + + while ( i < nLen ) + { + if ( aStr[ i ] == '~' ) + { + // check for CJK-style mnemonic + if( i > 0 && (i+2) < nLen ) + { + sal_Unicode c = sal_Unicode(rtl::toAsciiLowerCase(aStr[i+1])); + if( aStr[ i-1 ] == '(' && + aStr[ i+2 ] == ')' && + c >= MNEMONIC_RANGE_2_START && c <= MNEMONIC_RANGE_2_END ) + { + aStr = aStr.replaceAt( i-1, 4, u"" ); + nLen -= 4; + i--; + continue; + } + } + + // remove standard mnemonics + aStr = aStr.replaceAt( i, 1, u"" ); + nLen--; + } + else + i++; + } + + return aStr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/mouse.cxx b/vcl/source/window/mouse.cxx new file mode 100644 index 000000000..de88cae1d --- /dev/null +++ b/vcl/source/window/mouse.cxx @@ -0,0 +1,768 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <config_feature_desktop.h> +#include <config_vclplug.h> + +#include <tools/time.hxx> + +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + +#include <vcl/ITiledRenderable.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <vcl/cursor.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/event.hxx> + +#include <sal/types.h> + +#include <window.h> +#include <svdata.hxx> +#include <salobj.hxx> +#include <salgdi.hxx> +#include <salframe.hxx> +#include <salinst.hxx> + +#include <dndlistenercontainer.hxx> +#include <dndeventdispatcher.hxx> + +#include <com/sun/star/datatransfer/dnd/XDragSource.hpp> +#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <comphelper/processfactory.hxx> + +using namespace ::com::sun::star::uno; + +namespace vcl { + +WindowHitTest Window::ImplHitTest( const Point& rFramePos ) +{ + Point aFramePos( rFramePos ); + if( GetOutDev()->ImplIsAntiparallel() ) + { + const OutputDevice *pOutDev = GetOutDev(); + pOutDev->ReMirror( aFramePos ); + } + if ( !GetOutputRectPixel().Contains( aFramePos ) ) + return WindowHitTest::NONE; + if ( mpWindowImpl->mbWinRegion ) + { + Point aTempPos = aFramePos; + aTempPos.AdjustX( -GetOutDev()->mnOutOffX ); + aTempPos.AdjustY( -GetOutDev()->mnOutOffY ); + if ( !mpWindowImpl->maWinRegion.Contains( aTempPos ) ) + return WindowHitTest::NONE; + } + + WindowHitTest nHitTest = WindowHitTest::Inside; + if ( mpWindowImpl->mbMouseTransparent ) + nHitTest |= WindowHitTest::Transparent; + return nHitTest; +} + +bool Window::ImplTestMousePointerSet() +{ + // as soon as mouse is captured, switch mouse-pointer + if ( IsMouseCaptured() ) + return true; + + // if the mouse is over the window, switch it + tools::Rectangle aClientRect( Point( 0, 0 ), GetOutputSizePixel() ); + return aClientRect.Contains( GetPointerPosPixel() ); +} + +PointerStyle Window::ImplGetMousePointer() const +{ + PointerStyle ePointerStyle; + bool bWait = false; + + if ( IsEnabled() && IsInputEnabled() && ! IsInModalMode() ) + ePointerStyle = GetPointer(); + else + ePointerStyle = PointerStyle::Arrow; + + const vcl::Window* pWindow = this; + do + { + // when the pointer is not visible stop the search, as + // this status should not be overwritten + if ( pWindow->mpWindowImpl->mbNoPtrVisible ) + return PointerStyle::Null; + + if ( !bWait ) + { + if ( pWindow->mpWindowImpl->mnWaitCount ) + { + ePointerStyle = PointerStyle::Wait; + bWait = true; + } + else + { + if ( pWindow->mpWindowImpl->mbChildPtrOverwrite ) + ePointerStyle = pWindow->GetPointer(); + } + } + + if ( pWindow->ImplIsOverlapWindow() ) + break; + + pWindow = pWindow->ImplGetParent(); + } + while ( pWindow ); + + return ePointerStyle; +} + +void Window::ImplCallMouseMove( sal_uInt16 nMouseCode, bool bModChanged ) +{ + if ( !(mpWindowImpl->mpFrameData->mbMouseIn && mpWindowImpl->mpFrameWindow->mpWindowImpl->mbReallyVisible) ) + return; + + sal_uInt64 nTime = tools::Time::GetSystemTicks(); + tools::Long nX = mpWindowImpl->mpFrameData->mnLastMouseX; + tools::Long nY = mpWindowImpl->mpFrameData->mnLastMouseY; + sal_uInt16 nCode = nMouseCode; + MouseEventModifiers nMode = mpWindowImpl->mpFrameData->mnMouseMode; + bool bLeave; + // check for MouseLeave + bLeave = ((nX < 0) || (nY < 0) || + (nX >= mpWindowImpl->mpFrameWindow->GetOutDev()->mnOutWidth) || + (nY >= mpWindowImpl->mpFrameWindow->GetOutDev()->mnOutHeight)) && + !ImplGetSVData()->mpWinData->mpCaptureWin; + nMode |= MouseEventModifiers::SYNTHETIC; + if ( bModChanged ) + nMode |= MouseEventModifiers::MODIFIERCHANGED; + ImplHandleMouseEvent( mpWindowImpl->mpFrameWindow, MouseNotifyEvent::MOUSEMOVE, bLeave, nX, nY, nTime, nCode, nMode ); +} + +void Window::ImplGenerateMouseMove() +{ + if ( mpWindowImpl && mpWindowImpl->mpFrameData && + !mpWindowImpl->mpFrameData->mnMouseMoveId ) + mpWindowImpl->mpFrameData->mnMouseMoveId = Application::PostUserEvent( LINK( mpWindowImpl->mpFrameWindow, Window, ImplGenerateMouseMoveHdl ), nullptr, true ); +} + +IMPL_LINK_NOARG(Window, ImplGenerateMouseMoveHdl, void*, void) +{ + mpWindowImpl->mpFrameData->mnMouseMoveId = nullptr; + vcl::Window* pCaptureWin = ImplGetSVData()->mpWinData->mpCaptureWin; + if( ! pCaptureWin || + (pCaptureWin->mpWindowImpl && pCaptureWin->mpWindowImpl->mpFrame == mpWindowImpl->mpFrame) + ) + { + ImplCallMouseMove( mpWindowImpl->mpFrameData->mnMouseCode ); + } +} + +void Window::ImplInvertFocus( const tools::Rectangle& rRect ) +{ + InvertTracking( rRect, ShowTrackFlags::Small | ShowTrackFlags::TrackWindow ); +} + +static bool IsWindowFocused(const WindowImpl& rWinImpl) +{ + if (rWinImpl.mpSysObj) + return true; + + if (rWinImpl.mpFrameData->mbHasFocus) + return true; + + if (rWinImpl.mbFakeFocusSet) + return true; + + return false; +} + +void Window::ImplGrabFocus( GetFocusFlags nFlags ) +{ + // #143570# no focus for destructing windows + if( !mpWindowImpl || mpWindowImpl->mbInDispose ) + return; + + // some event listeners do really bad stuff + // => prepare for the worst + VclPtr<vcl::Window> xWindow( this ); + + // Currently the client window should always get the focus + // Should the border window at some point be focusable + // we need to change all GrabFocus() instances in VCL, + // e.g. in ToTop() + + if ( mpWindowImpl->mpClientWindow ) + { + // For a lack of design we need a little hack here to + // ensure that dialogs on close pass the focus back to + // the correct window + if ( mpWindowImpl->mpLastFocusWindow && (mpWindowImpl->mpLastFocusWindow.get() != this) && + !(mpWindowImpl->mnDlgCtrlFlags & DialogControlFlags::WantFocus) && + mpWindowImpl->mpLastFocusWindow->IsEnabled() && + mpWindowImpl->mpLastFocusWindow->IsInputEnabled() && + ! mpWindowImpl->mpLastFocusWindow->IsInModalMode() + ) + mpWindowImpl->mpLastFocusWindow->GrabFocus(); + else + mpWindowImpl->mpClientWindow->GrabFocus(); + return; + } + else if ( mpWindowImpl->mbFrame ) + { + // For a lack of design we need a little hack here to + // ensure that dialogs on close pass the focus back to + // the correct window + if ( mpWindowImpl->mpLastFocusWindow && (mpWindowImpl->mpLastFocusWindow.get() != this) && + !(mpWindowImpl->mnDlgCtrlFlags & DialogControlFlags::WantFocus) && + mpWindowImpl->mpLastFocusWindow->IsEnabled() && + mpWindowImpl->mpLastFocusWindow->IsInputEnabled() && + ! mpWindowImpl->mpLastFocusWindow->IsInModalMode() + ) + { + mpWindowImpl->mpLastFocusWindow->GrabFocus(); + return; + } + } + + // If the Window is disabled, then we don't change the focus + if ( !IsEnabled() || !IsInputEnabled() || IsInModalMode() ) + return; + + // we only need to set the focus if it is not already set + // note: if some other frame is waiting for an asynchronous focus event + // we also have to post an asynchronous focus event for this frame + // which is done using ToTop + ImplSVData* pSVData = ImplGetSVData(); + + bool bAsyncFocusWaiting = false; + vcl::Window *pFrame = pSVData->maFrameData.mpFirstFrame; + while( pFrame && pFrame->mpWindowImpl && pFrame->mpWindowImpl->mpFrameData ) + { + if( pFrame != mpWindowImpl->mpFrameWindow.get() && pFrame->mpWindowImpl->mpFrameData->mnFocusId ) + { + bAsyncFocusWaiting = true; + break; + } + pFrame = pFrame->mpWindowImpl->mpFrameData->mpNextFrame; + } + + bool bHasFocus = IsWindowFocused(*mpWindowImpl); + + bool bMustNotGrabFocus = false; + // #100242#, check parent hierarchy if some floater prohibits grab focus + + vcl::Window *pParent = this; + while( pParent ) + { + if ((pParent->GetStyle() & WB_SYSTEMFLOATWIN) && !(pParent->GetStyle() & WB_MOVEABLE)) + { + bMustNotGrabFocus = true; + break; + } + if (!pParent->mpWindowImpl) + break; + pParent = pParent->mpWindowImpl->mpParent; + } + + if ( !(( pSVData->mpWinData->mpFocusWin.get() != this && + !mpWindowImpl->mbInDispose ) || + ( bAsyncFocusWaiting && !bHasFocus && !bMustNotGrabFocus )) ) + return; + + // EndExtTextInput if it is not the same window + if (pSVData->mpWinData->mpExtTextInputWin + && (pSVData->mpWinData->mpExtTextInputWin.get() != this)) + pSVData->mpWinData->mpExtTextInputWin->EndExtTextInput(); + + // mark this windows as the last FocusWindow + vcl::Window* pOverlapWindow = ImplGetFirstOverlapWindow(); + if (pOverlapWindow->mpWindowImpl) + pOverlapWindow->mpWindowImpl->mpLastFocusWindow = this; + mpWindowImpl->mpFrameData->mpFocusWin = this; + + if( !bHasFocus ) + { + // menu windows never get the system focus + // the application will keep the focus + if( bMustNotGrabFocus ) + return; + else + { + // here we already switch focus as ToTop() + // should not give focus to another window + mpWindowImpl->mpFrame->ToTop( SalFrameToTop::GrabFocus | SalFrameToTop::GrabFocusOnly ); + return; + } + } + + VclPtr<vcl::Window> pOldFocusWindow = pSVData->mpWinData->mpFocusWin; + + pSVData->mpWinData->mpFocusWin = this; + + if ( pOldFocusWindow && pOldFocusWindow->mpWindowImpl ) + { + // Cursor hidden + if ( pOldFocusWindow->mpWindowImpl->mpCursor ) + pOldFocusWindow->mpWindowImpl->mpCursor->ImplHide(); + } + + // !!!!! due to old SV-Office Activate/Deactivate handling + // !!!!! first as before + if ( pOldFocusWindow ) + { + // remember Focus + vcl::Window* pOldOverlapWindow = pOldFocusWindow->ImplGetFirstOverlapWindow(); + vcl::Window* pNewOverlapWindow = ImplGetFirstOverlapWindow(); + if ( pOldOverlapWindow != pNewOverlapWindow ) + ImplCallFocusChangeActivate( pNewOverlapWindow, pOldOverlapWindow ); + } + else + { + vcl::Window* pNewOverlapWindow = ImplGetFirstOverlapWindow(); + if ( pNewOverlapWindow && pNewOverlapWindow->mpWindowImpl ) + { + vcl::Window* pNewRealWindow = pNewOverlapWindow->ImplGetWindow(); + pNewOverlapWindow->mpWindowImpl->mbActive = true; + pNewOverlapWindow->Activate(); + if ( pNewRealWindow != pNewOverlapWindow && pNewRealWindow && pNewRealWindow->mpWindowImpl ) + { + pNewRealWindow->mpWindowImpl->mbActive = true; + pNewRealWindow->Activate(); + } + } + } + + // call Get- and LoseFocus + if ( pOldFocusWindow && ! pOldFocusWindow->isDisposed() ) + { + NotifyEvent aNEvt( MouseNotifyEvent::LOSEFOCUS, pOldFocusWindow ); + if ( !ImplCallPreNotify( aNEvt ) ) + pOldFocusWindow->CompatLoseFocus(); + pOldFocusWindow->ImplCallDeactivateListeners( this ); + } + + if (pSVData->mpWinData->mpFocusWin.get() == this) + { + if ( mpWindowImpl->mpSysObj ) + { + mpWindowImpl->mpFrameData->mpFocusWin = this; + if ( !mpWindowImpl->mpFrameData->mbInSysObjFocusHdl ) + mpWindowImpl->mpSysObj->GrabFocus(); + } + + if (pSVData->mpWinData->mpFocusWin.get() == this) + { + if ( mpWindowImpl->mpCursor ) + mpWindowImpl->mpCursor->ImplShow(); + mpWindowImpl->mbInFocusHdl = true; + mpWindowImpl->mnGetFocusFlags = nFlags; + // if we're changing focus due to closing a popup floating window + // notify the new focus window so it can restore the inner focus + // eg, toolboxes can select their recent active item + if( pOldFocusWindow && + ! pOldFocusWindow->isDisposed() && + ( pOldFocusWindow->GetDialogControlFlags() & DialogControlFlags::FloatWinPopupModeEndCancel ) ) + mpWindowImpl->mnGetFocusFlags |= GetFocusFlags::FloatWinPopupModeEndCancel; + NotifyEvent aNEvt( MouseNotifyEvent::GETFOCUS, this ); + if ( !ImplCallPreNotify( aNEvt ) && !xWindow->isDisposed() ) + CompatGetFocus(); + if( !xWindow->isDisposed() ) + ImplCallActivateListeners( (pOldFocusWindow && ! pOldFocusWindow->isDisposed()) ? pOldFocusWindow : nullptr ); + if( !xWindow->isDisposed() ) + { + mpWindowImpl->mnGetFocusFlags = GetFocusFlags::NONE; + mpWindowImpl->mbInFocusHdl = false; + } + } + } + + ImplNewInputContext(); + +} + +void Window::ImplGrabFocusToDocument( GetFocusFlags nFlags ) +{ + vcl::Window *pWin = this; + while( pWin ) + { + if( !pWin->GetParent() ) + { + pWin->mpWindowImpl->mpFrame->GrabFocus(); + pWin->ImplGetFrameWindow()->GetWindow( GetWindowType::Client )->ImplGrabFocus(nFlags); + return; + } + pWin = pWin->GetParent(); + } +} + +void Window::MouseMove( const MouseEvent& rMEvt ) +{ + NotifyEvent aNEvt( MouseNotifyEvent::MOUSEMOVE, this, &rMEvt ); + EventNotify(aNEvt); +} + +void Window::MouseButtonDown( const MouseEvent& rMEvt ) +{ + NotifyEvent aNEvt( MouseNotifyEvent::MOUSEBUTTONDOWN, this, &rMEvt ); + if (!EventNotify(aNEvt)) + mpWindowImpl->mbMouseButtonDown = true; +} + +void Window::MouseButtonUp( const MouseEvent& rMEvt ) +{ + NotifyEvent aNEvt( MouseNotifyEvent::MOUSEBUTTONUP, this, &rMEvt ); + if (!EventNotify(aNEvt)) + mpWindowImpl->mbMouseButtonUp = true; +} + +void Window::SetMouseTransparent( bool bTransparent ) +{ + + if ( mpWindowImpl->mpBorderWindow ) + mpWindowImpl->mpBorderWindow->SetMouseTransparent( bTransparent ); + + if( mpWindowImpl->mpSysObj ) + mpWindowImpl->mpSysObj->SetMouseTransparent( bTransparent ); + + mpWindowImpl->mbMouseTransparent = bTransparent; +} + +void Window::LocalStartDrag() +{ + ImplGetFrameData()->mbDragging = true; +} + +void Window::CaptureMouse() +{ + ImplSVData* pSVData = ImplGetSVData(); + + // possibly stop tracking + if (pSVData->mpWinData->mpTrackWin.get() != this) + { + if (pSVData->mpWinData->mpTrackWin) + pSVData->mpWinData->mpTrackWin->EndTracking(TrackingEventFlags::Cancel); + } + + if (pSVData->mpWinData->mpCaptureWin.get() != this) + { + pSVData->mpWinData->mpCaptureWin = this; + mpWindowImpl->mpFrame->CaptureMouse( true ); + } +} + +void Window::ReleaseMouse() +{ + if (IsMouseCaptured()) + { + ImplSVData* pSVData = ImplGetSVData(); + pSVData->mpWinData->mpCaptureWin = nullptr; + if (mpWindowImpl && mpWindowImpl->mpFrame) + mpWindowImpl->mpFrame->CaptureMouse( false ); + ImplGenerateMouseMove(); + } +} + +bool Window::IsMouseCaptured() const +{ + return (this == ImplGetSVData()->mpWinData->mpCaptureWin); +} + +void Window::SetPointer( PointerStyle nPointer ) +{ + if ( mpWindowImpl->maPointer == nPointer ) + return; + + mpWindowImpl->maPointer = nPointer; + + // possibly immediately move pointer + if ( !mpWindowImpl->mpFrameData->mbInMouseMove && ImplTestMousePointerSet() ) + mpWindowImpl->mpFrame->SetPointer( ImplGetMousePointer() ); + + VclPtr<vcl::Window> pWin = GetParentWithLOKNotifier(); + if (!pWin) + return; + + PointerStyle aPointer = GetPointer(); + // We don't map all possible pointers hence we need a default + OString aPointerString = "default"; + auto aIt = vcl::gaLOKPointerMap.find(aPointer); + if (aIt != vcl::gaLOKPointerMap.end()) + { + aPointerString = aIt->second; + } + + // issue mouse pointer events only for document windows + // Doc windows' immediate parent SfxFrameViewWindow_Impl is the one with + // parent notifier set during initialization + if ((ImplGetFrameData()->mbDragging && + ImplGetFrameData()->mpMouseDownWin == this) || + (GetParent()->ImplGetWindowImpl()->mbLOKParentNotifier && + GetParent()->ImplGetWindowImpl()->mnLOKWindowId == 0)) + { + pWin->GetLOKNotifier()->libreOfficeKitViewCallback(LOK_CALLBACK_MOUSE_POINTER, aPointerString.getStr()); + } +} + +void Window::EnableChildPointerOverwrite( bool bOverwrite ) +{ + + if ( mpWindowImpl->mbChildPtrOverwrite == bOverwrite ) + return; + + mpWindowImpl->mbChildPtrOverwrite = bOverwrite; + + // possibly immediately move pointer + if ( !mpWindowImpl->mpFrameData->mbInMouseMove && ImplTestMousePointerSet() ) + mpWindowImpl->mpFrame->SetPointer( ImplGetMousePointer() ); +} + +void Window::SetPointerPosPixel( const Point& rPos ) +{ + Point aPos = ImplOutputToFrame( rPos ); + const OutputDevice *pOutDev = GetOutDev(); + if( pOutDev->HasMirroredGraphics() ) + { + if( !IsRTLEnabled() ) + { + pOutDev->ReMirror( aPos ); + } + // mirroring is required here, SetPointerPos bypasses SalGraphics + aPos.setX( GetOutDev()->mpGraphics->mirror2( aPos.X(), *GetOutDev() ) ); + } + else if( GetOutDev()->ImplIsAntiparallel() ) + { + pOutDev->ReMirror( aPos ); + } + mpWindowImpl->mpFrame->SetPointerPos( aPos.X(), aPos.Y() ); +} + +void Window::SetLastMousePos(const Point& rPos) +{ + // Do this conversion, so when GetPointerPosPixel() calls + // ImplFrameToOutput(), we get back the original position. + Point aPos = ImplOutputToFrame(rPos); + mpWindowImpl->mpFrameData->mnLastMouseX = aPos.X(); + mpWindowImpl->mpFrameData->mnLastMouseY = aPos.Y(); +} + +Point Window::GetPointerPosPixel() +{ + + Point aPos( mpWindowImpl->mpFrameData->mnLastMouseX, mpWindowImpl->mpFrameData->mnLastMouseY ); + if( GetOutDev()->ImplIsAntiparallel() ) + { + const OutputDevice *pOutDev = GetOutDev(); + pOutDev->ReMirror( aPos ); + } + return ImplFrameToOutput( aPos ); +} + +Point Window::GetLastPointerPosPixel() +{ + + Point aPos( mpWindowImpl->mpFrameData->mnBeforeLastMouseX, mpWindowImpl->mpFrameData->mnBeforeLastMouseY ); + if( GetOutDev()->ImplIsAntiparallel() ) + { + const OutputDevice *pOutDev = GetOutDev(); + pOutDev->ReMirror( aPos ); + } + return ImplFrameToOutput( aPos ); +} + +void Window::ShowPointer( bool bVisible ) +{ + + if ( mpWindowImpl->mbNoPtrVisible != !bVisible ) + { + mpWindowImpl->mbNoPtrVisible = !bVisible; + + // possibly immediately move pointer + if ( !mpWindowImpl->mpFrameData->mbInMouseMove && ImplTestMousePointerSet() ) + mpWindowImpl->mpFrame->SetPointer( ImplGetMousePointer() ); + } +} + +Window::PointerState Window::GetPointerState() +{ + PointerState aState; + aState.mnState = 0; + + if (mpWindowImpl->mpFrame) + { + SalFrame::SalPointerState aSalPointerState = mpWindowImpl->mpFrame->GetPointerState(); + if( GetOutDev()->ImplIsAntiparallel() ) + { + const OutputDevice *pOutDev = GetOutDev(); + pOutDev->ReMirror( aSalPointerState.maPos ); + } + aState.maPos = ImplFrameToOutput( aSalPointerState.maPos ); + aState.mnState = aSalPointerState.mnState; + } + return aState; +} + +bool Window::IsMouseOver() const +{ + return ImplGetWinData()->mbMouseOver; +} + +void Window::EnterWait() +{ + + mpWindowImpl->mnWaitCount++; + + if ( mpWindowImpl->mnWaitCount == 1 ) + { + // possibly immediately move pointer + if ( !mpWindowImpl->mpFrameData->mbInMouseMove && ImplTestMousePointerSet() ) + mpWindowImpl->mpFrame->SetPointer( ImplGetMousePointer() ); + } +} + +void Window::LeaveWait() +{ + if( !mpWindowImpl ) + return; + + if ( mpWindowImpl->mnWaitCount ) + { + mpWindowImpl->mnWaitCount--; + + if ( !mpWindowImpl->mnWaitCount ) + { + // possibly immediately move pointer + if ( !mpWindowImpl->mpFrameData->mbInMouseMove && ImplTestMousePointerSet() ) + mpWindowImpl->mpFrame->SetPointer( ImplGetMousePointer() ); + } + } +} + +bool Window::ImplStopDnd() +{ + bool bRet = false; + if( mpWindowImpl->mpFrameData && mpWindowImpl->mpFrameData->mxDropTargetListener.is() ) + { + bRet = true; + mpWindowImpl->mpFrameData->mxDropTarget.clear(); + mpWindowImpl->mpFrameData->mxDragSource.clear(); + mpWindowImpl->mpFrameData->mxDropTargetListener.clear(); + } + + return bRet; +} + +void Window::ImplStartDnd() +{ + GetDropTarget(); +} + +Reference< css::datatransfer::dnd::XDropTarget > Window::GetDropTarget() +{ + if( !mpWindowImpl ) + return Reference< css::datatransfer::dnd::XDropTarget >(); + + if( ! mpWindowImpl->mxDNDListenerContainer.is() ) + { + sal_Int8 nDefaultActions = 0; + + if( mpWindowImpl->mpFrameData ) + { + if( ! mpWindowImpl->mpFrameData->mxDropTarget.is() ) + { + // initialization is done in GetDragSource + GetDragSource(); + } + + if( mpWindowImpl->mpFrameData->mxDropTarget.is() ) + { + nDefaultActions = mpWindowImpl->mpFrameData->mxDropTarget->getDefaultActions(); + + if( ! mpWindowImpl->mpFrameData->mxDropTargetListener.is() ) + { + mpWindowImpl->mpFrameData->mxDropTargetListener = new DNDEventDispatcher( mpWindowImpl->mpFrameWindow ); + + try + { + mpWindowImpl->mpFrameData->mxDropTarget->addDropTargetListener( mpWindowImpl->mpFrameData->mxDropTargetListener ); + + // register also as drag gesture listener if directly supported by drag source + Reference< css::datatransfer::dnd::XDragGestureRecognizer > xDragGestureRecognizer( + mpWindowImpl->mpFrameData->mxDragSource, UNO_QUERY); + + if( xDragGestureRecognizer.is() ) + { + xDragGestureRecognizer->addDragGestureListener( + Reference< css::datatransfer::dnd::XDragGestureListener > (mpWindowImpl->mpFrameData->mxDropTargetListener, UNO_QUERY)); + } + else + mpWindowImpl->mpFrameData->mbInternalDragGestureRecognizer = true; + + } + catch (const RuntimeException&) + { + // release all instances + mpWindowImpl->mpFrameData->mxDropTarget.clear(); + mpWindowImpl->mpFrameData->mxDragSource.clear(); + } + } + } + + } + + mpWindowImpl->mxDNDListenerContainer = static_cast < css::datatransfer::dnd::XDropTarget * > ( new DNDListenerContainer( nDefaultActions ) ); + } + + // this object is located in the same process, so there will be no runtime exception + return Reference< css::datatransfer::dnd::XDropTarget > ( mpWindowImpl->mxDNDListenerContainer, UNO_QUERY ); +} + +Reference< css::datatransfer::dnd::XDragSource > Window::GetDragSource() +{ +#if HAVE_FEATURE_DESKTOP + const SystemEnvData* pEnvData = GetSystemData(); + if (!mpWindowImpl->mpFrameData || !pEnvData) + return Reference<css::datatransfer::dnd::XDragSource>(); + if (mpWindowImpl->mpFrameData->mxDragSource.is()) + return mpWindowImpl->mpFrameData->mxDragSource; + + try + { + SalInstance* pInst = ImplGetSVData()->mpDefInst; + mpWindowImpl->mpFrameData->mxDragSource.set(pInst->CreateDragSource(pEnvData), UNO_QUERY); + mpWindowImpl->mpFrameData->mxDropTarget.set(pInst->CreateDropTarget(pEnvData), UNO_QUERY); + } + catch (const Exception&) + { + mpWindowImpl->mpFrameData->mxDropTarget.clear(); + mpWindowImpl->mpFrameData->mxDragSource.clear(); + } + return mpWindowImpl->mpFrameData->mxDragSource; +#else + return Reference< css::datatransfer::dnd::XDragSource > (); +#endif +} + +Reference< css::datatransfer::dnd::XDragGestureRecognizer > Window::GetDragGestureRecognizer() +{ + return Reference< css::datatransfer::dnd::XDragGestureRecognizer > ( GetDropTarget(), UNO_QUERY ); +} + +} /* namespace vcl */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/paint.cxx b/vcl/source/window/paint.cxx new file mode 100644 index 000000000..a70f1c0e4 --- /dev/null +++ b/vcl/source/window/paint.cxx @@ -0,0 +1,1809 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> +#include <vcl/gdimtf.hxx> +#include <vcl/window.hxx> +#include <vcl/virdev.hxx> +#include <vcl/cursor.hxx> +#include <vcl/settings.hxx> +#include <vcl/syswin.hxx> + +#include <sal/types.h> +#include <sal/log.hxx> + +#include <window.h> +#include <salgdi.hxx> +#include <salframe.hxx> +#include <svdata.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/profilezone.hxx> +#if HAVE_FEATURE_OPENGL +#include <vcl/opengl/OpenGLHelper.hxx> +#endif + +// PaintBufferGuard + +namespace vcl +{ +PaintBufferGuard::PaintBufferGuard(ImplFrameData* pFrameData, vcl::Window* pWindow) + : mpFrameData(pFrameData), + m_pWindow(pWindow), + mbBackground(false), + mnOutOffX(0), + mnOutOffY(0) +{ + if (!pFrameData->mpBuffer) + return; + + // transfer various settings + // FIXME: this must disappear as we move to RenderContext only, + // the painting must become state-less, so that no actual + // vcl::Window setting affects this + mbBackground = pFrameData->mpBuffer->IsBackground(); + if (pWindow->IsBackground()) + { + maBackground = pFrameData->mpBuffer->GetBackground(); + pFrameData->mpBuffer->SetBackground(pWindow->GetBackground()); + } + //else + //SAL_WARN("vcl.window", "the root of the double-buffering hierarchy should not have a transparent background"); + + vcl::PushFlags nFlags = vcl::PushFlags::NONE; + nFlags |= vcl::PushFlags::CLIPREGION; + nFlags |= vcl::PushFlags::FILLCOLOR; + nFlags |= vcl::PushFlags::FONT; + nFlags |= vcl::PushFlags::LINECOLOR; + nFlags |= vcl::PushFlags::MAPMODE; + maSettings = pFrameData->mpBuffer->GetSettings(); + nFlags |= vcl::PushFlags::REFPOINT; + nFlags |= vcl::PushFlags::TEXTCOLOR; + nFlags |= vcl::PushFlags::TEXTLINECOLOR; + nFlags |= vcl::PushFlags::OVERLINECOLOR; + nFlags |= vcl::PushFlags::TEXTFILLCOLOR; + nFlags |= vcl::PushFlags::TEXTALIGN; + nFlags |= vcl::PushFlags::RASTEROP; + nFlags |= vcl::PushFlags::TEXTLAYOUTMODE; + nFlags |= vcl::PushFlags::TEXTLANGUAGE; + pFrameData->mpBuffer->Push(nFlags); + auto& rDev = *pWindow->GetOutDev(); + pFrameData->mpBuffer->SetClipRegion(rDev.GetClipRegion()); + pFrameData->mpBuffer->SetFillColor(rDev.GetFillColor()); + pFrameData->mpBuffer->SetFont(pWindow->GetFont()); + pFrameData->mpBuffer->SetLineColor(rDev.GetLineColor()); + pFrameData->mpBuffer->SetMapMode(pWindow->GetMapMode()); + pFrameData->mpBuffer->SetRefPoint(rDev.GetRefPoint()); + pFrameData->mpBuffer->SetSettings(pWindow->GetSettings()); + pFrameData->mpBuffer->SetTextColor(pWindow->GetTextColor()); + pFrameData->mpBuffer->SetTextLineColor(pWindow->GetTextLineColor()); + pFrameData->mpBuffer->SetOverlineColor(pWindow->GetOverlineColor()); + pFrameData->mpBuffer->SetTextFillColor(pWindow->GetTextFillColor()); + pFrameData->mpBuffer->SetTextAlign(pWindow->GetTextAlign()); + pFrameData->mpBuffer->SetRasterOp(rDev.GetRasterOp()); + pFrameData->mpBuffer->SetLayoutMode(rDev.GetLayoutMode()); + pFrameData->mpBuffer->SetDigitLanguage(rDev.GetDigitLanguage()); + + mnOutOffX = pFrameData->mpBuffer->GetOutOffXPixel(); + mnOutOffY = pFrameData->mpBuffer->GetOutOffYPixel(); + pFrameData->mpBuffer->SetOutOffXPixel(pWindow->GetOutOffXPixel()); + pFrameData->mpBuffer->SetOutOffYPixel(pWindow->GetOutOffYPixel()); + pFrameData->mpBuffer->EnableRTL(pWindow->IsRTLEnabled()); +} + +PaintBufferGuard::~PaintBufferGuard() COVERITY_NOEXCEPT_FALSE +{ + if (!mpFrameData->mpBuffer) + return; + + if (!m_aPaintRect.IsEmpty()) + { + // copy the buffer content to the actual window + // export VCL_DOUBLEBUFFERING_AVOID_PAINT=1 to see where we are + // painting directly instead of using Invalidate() + // [ie. everything you can see was painted directly to the + // window either above or in eg. an event handler] + if (!getenv("VCL_DOUBLEBUFFERING_AVOID_PAINT")) + { + // Make sure that the +1 value GetSize() adds to the size is in pixels. + Size aPaintRectSize; + if (m_pWindow->GetMapMode().GetMapUnit() == MapUnit::MapPixel) + { + aPaintRectSize = m_aPaintRect.GetSize(); + } + else + { + tools::Rectangle aRectanglePixel = m_pWindow->LogicToPixel(m_aPaintRect); + aPaintRectSize = m_pWindow->PixelToLogic(aRectanglePixel.GetSize()); + } + + m_pWindow->GetOutDev()->DrawOutDev(m_aPaintRect.TopLeft(), aPaintRectSize, m_aPaintRect.TopLeft(), aPaintRectSize, *mpFrameData->mpBuffer); + } + } + + // Restore buffer state. + mpFrameData->mpBuffer->SetOutOffXPixel(mnOutOffX); + mpFrameData->mpBuffer->SetOutOffYPixel(mnOutOffY); + + mpFrameData->mpBuffer->Pop(); + mpFrameData->mpBuffer->SetSettings(maSettings); + if (mbBackground) + mpFrameData->mpBuffer->SetBackground(maBackground); + else + mpFrameData->mpBuffer->SetBackground(); +} + +void PaintBufferGuard::SetPaintRect(const tools::Rectangle& rRectangle) +{ + m_aPaintRect = rRectangle; +} + +vcl::RenderContext* PaintBufferGuard::GetRenderContext() +{ + if (mpFrameData->mpBuffer) + return mpFrameData->mpBuffer; + else + return m_pWindow->GetOutDev(); +} +} + +class PaintHelper +{ +private: + VclPtr<vcl::Window> m_pWindow; + std::unique_ptr<vcl::Region> m_pChildRegion; + tools::Rectangle m_aSelectionRect; + tools::Rectangle m_aPaintRect; + vcl::Region m_aPaintRegion; + ImplPaintFlags m_nPaintFlags; + bool m_bPop : 1; + bool m_bRestoreCursor : 1; + bool m_bStartedBufferedPaint : 1; ///< This PaintHelper started a buffered paint, and should paint it on the screen when being destructed. +public: + PaintHelper(vcl::Window* pWindow, ImplPaintFlags nPaintFlags); + void SetPop() + { + m_bPop = true; + } + void SetPaintRect(const tools::Rectangle& rRect) + { + m_aPaintRect = rRect; + } + void SetSelectionRect(const tools::Rectangle& rRect) + { + m_aSelectionRect = rRect; + } + void SetRestoreCursor(bool bRestoreCursor) + { + m_bRestoreCursor = bRestoreCursor; + } + bool GetRestoreCursor() const + { + return m_bRestoreCursor; + } + ImplPaintFlags GetPaintFlags() const + { + return m_nPaintFlags; + } + vcl::Region& GetPaintRegion() + { + return m_aPaintRegion; + } + void DoPaint(const vcl::Region* pRegion); + + /// Start buffered paint: set it up to have the same settings as m_pWindow. + void StartBufferedPaint(); + + /// Paint the content of the buffer to the current m_pWindow. + void PaintBuffer(); + + ~PaintHelper(); +}; + +PaintHelper::PaintHelper(vcl::Window *pWindow, ImplPaintFlags nPaintFlags) + : m_pWindow(pWindow) + , m_nPaintFlags(nPaintFlags) + , m_bPop(false) + , m_bRestoreCursor(false) + , m_bStartedBufferedPaint(false) +{ +} + +void PaintHelper::StartBufferedPaint() +{ + ImplFrameData* pFrameData = m_pWindow->mpWindowImpl->mpFrameData; + assert(!pFrameData->mbInBufferedPaint); + + pFrameData->mbInBufferedPaint = true; + pFrameData->maBufferedRect = tools::Rectangle(); + m_bStartedBufferedPaint = true; +} + +void PaintHelper::PaintBuffer() +{ + ImplFrameData* pFrameData = m_pWindow->mpWindowImpl->mpFrameData; + assert(pFrameData->mbInBufferedPaint); + assert(m_bStartedBufferedPaint); + + vcl::PaintBufferGuard aGuard(pFrameData, m_pWindow); + aGuard.SetPaintRect(pFrameData->maBufferedRect); +} + +void PaintHelper::DoPaint(const vcl::Region* pRegion) +{ + WindowImpl* pWindowImpl = m_pWindow->ImplGetWindowImpl(); + + vcl::Region& rWinChildClipRegion = m_pWindow->ImplGetWinChildClipRegion(); + ImplFrameData* pFrameData = m_pWindow->mpWindowImpl->mpFrameData; + if (pWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll || pFrameData->mbInBufferedPaint) + { + pWindowImpl->maInvalidateRegion = rWinChildClipRegion; + } + else + { + if (pRegion) + pWindowImpl->maInvalidateRegion.Union( *pRegion ); + + if (pWindowImpl->mpWinData && pWindowImpl->mbTrackVisible) + /* #98602# need to repaint all children within the + * tracking rectangle, so the following invert + * operation takes places without traces of the previous + * one. + */ + pWindowImpl->maInvalidateRegion.Union(*pWindowImpl->mpWinData->mpTrackRect); + + if (pWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAllChildren) + m_pChildRegion.reset( new vcl::Region(pWindowImpl->maInvalidateRegion) ); + pWindowImpl->maInvalidateRegion.Intersect(rWinChildClipRegion); + } + pWindowImpl->mnPaintFlags = ImplPaintFlags::NONE; + if (pWindowImpl->maInvalidateRegion.IsEmpty()) + return; + +#if HAVE_FEATURE_OPENGL + VCL_GL_INFO("PaintHelper::DoPaint on " << + typeid( *m_pWindow ).name() << " '" << m_pWindow->GetText() << "' begin"); +#endif + // double-buffering: setup the buffer if it does not exist + if (!pFrameData->mbInBufferedPaint && m_pWindow->SupportsDoubleBuffering()) + StartBufferedPaint(); + + // double-buffering: if this window does not support double-buffering, + // but we are in the middle of double-buffered paint, we might be + // losing information + if (pFrameData->mbInBufferedPaint && !m_pWindow->SupportsDoubleBuffering()) + SAL_WARN("vcl.window", "non-double buffered window in the double-buffered hierarchy, painting directly: " << typeid(*m_pWindow.get()).name()); + + if (pFrameData->mbInBufferedPaint && m_pWindow->SupportsDoubleBuffering()) + { + // double-buffering + vcl::PaintBufferGuard g(pFrameData, m_pWindow); + m_pWindow->ApplySettings(*pFrameData->mpBuffer); + + m_pWindow->PushPaintHelper(this, *pFrameData->mpBuffer); + m_pWindow->Paint(*pFrameData->mpBuffer, m_aPaintRect); + pFrameData->maBufferedRect.Union(m_aPaintRect); + } + else + { + // direct painting + Wallpaper aBackground = m_pWindow->GetBackground(); + m_pWindow->ApplySettings(*m_pWindow->GetOutDev()); + // Restore bitmap background if it was lost. + if (aBackground.IsBitmap() && !m_pWindow->GetBackground().IsBitmap()) + { + m_pWindow->SetBackground(aBackground); + } + m_pWindow->PushPaintHelper(this, *m_pWindow->GetOutDev()); + m_pWindow->Paint(*m_pWindow->GetOutDev(), m_aPaintRect); + } +#if HAVE_FEATURE_OPENGL + VCL_GL_INFO("PaintHelper::DoPaint end on " << + typeid( *m_pWindow ).name() << " '" << m_pWindow->GetText() << "'"); +#endif +} + +namespace vcl +{ + +void RenderTools::DrawSelectionBackground(vcl::RenderContext& rRenderContext, vcl::Window const & rWindow, + const tools::Rectangle& rRect, sal_uInt16 nHighlight, + bool bChecked, bool bDrawBorder, bool bDrawExtBorderOnly, + Color* pSelectionTextColor, tools::Long nCornerRadius, Color const * pPaintColor) +{ + if (rRect.IsEmpty()) + return; + + bool bRoundEdges = nCornerRadius > 0; + + const StyleSettings& rStyles = rRenderContext.GetSettings().GetStyleSettings(); + + // colors used for item highlighting + Color aSelectionBorderColor(pPaintColor ? *pPaintColor : rStyles.GetHighlightColor()); + Color aSelectionFillColor(aSelectionBorderColor); + + bool bDark = rStyles.GetFaceColor().IsDark(); + bool bBright = ( rStyles.GetFaceColor() == COL_WHITE ); + + int c1 = aSelectionBorderColor.GetLuminance(); + int c2 = rWindow.GetBackgroundColor().GetLuminance(); + + if (!bDark && !bBright && std::abs(c2 - c1) < (pPaintColor ? 40 : 75)) + { + // contrast too low + sal_uInt16 h, s, b; + aSelectionFillColor.RGBtoHSB( h, s, b ); + if( b > 50 ) b -= 40; + else b += 40; + aSelectionFillColor = Color::HSBtoRGB( h, s, b ); + aSelectionBorderColor = aSelectionFillColor; + } + + if (bRoundEdges) + { + if (aSelectionBorderColor.IsDark()) + aSelectionBorderColor.IncreaseLuminance(128); + else + aSelectionBorderColor.DecreaseLuminance(128); + } + + tools::Rectangle aRect(rRect); + if (bDrawExtBorderOnly) + { + aRect.AdjustLeft( -1 ); + aRect.AdjustTop( -1 ); + aRect.AdjustRight(1 ); + aRect.AdjustBottom(1 ); + } + rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR); + + if (bDrawBorder) + rRenderContext.SetLineColor(bDark ? COL_WHITE : (bBright ? COL_BLACK : aSelectionBorderColor)); + else + rRenderContext.SetLineColor(); + + sal_uInt16 nPercent = 0; + if (!nHighlight) + { + if (bDark) + aSelectionFillColor = COL_BLACK; + else + nPercent = 80; // just checked (light) + } + else + { + if (bChecked && nHighlight == 2) + { + if (bDark) + aSelectionFillColor = COL_LIGHTGRAY; + else if (bBright) + { + aSelectionFillColor = COL_BLACK; + rRenderContext.SetLineColor(COL_BLACK); + nPercent = 0; + } + else + nPercent = bRoundEdges ? 40 : 20; // selected, pressed or checked ( very dark ) + } + else if (bChecked || nHighlight == 1) + { + if (bDark) + aSelectionFillColor = COL_GRAY; + else if (bBright) + { + aSelectionFillColor = COL_BLACK; + rRenderContext.SetLineColor(COL_BLACK); + nPercent = 0; + } + else + nPercent = bRoundEdges ? 60 : 35; // selected, pressed or checked ( very dark ) + } + else + { + if (bDark) + aSelectionFillColor = COL_LIGHTGRAY; + else if (bBright) + { + aSelectionFillColor = COL_BLACK; + rRenderContext.SetLineColor(COL_BLACK); + if (nHighlight == 3) + nPercent = 80; + else + nPercent = 0; + } + else + nPercent = 70; // selected ( dark ) + } + } + + if (bDark && bDrawExtBorderOnly) + { + rRenderContext.SetFillColor(); + if (pSelectionTextColor) + *pSelectionTextColor = rStyles.GetHighlightTextColor(); + } + else + { + rRenderContext.SetFillColor(aSelectionFillColor); + if (pSelectionTextColor) + { + Color aTextColor = rWindow.IsControlBackground() ? rWindow.GetControlForeground() : rStyles.GetButtonTextColor(); + Color aHLTextColor = rStyles.GetHighlightTextColor(); + int nTextDiff = std::abs(aSelectionFillColor.GetLuminance() - aTextColor.GetLuminance()); + int nHLDiff = std::abs(aSelectionFillColor.GetLuminance() - aHLTextColor.GetLuminance()); + *pSelectionTextColor = (nHLDiff >= nTextDiff) ? aHLTextColor : aTextColor; + } + } + + if (bDark) + { + rRenderContext.DrawRect(aRect); + } + else + { + if (bRoundEdges) + { + tools::Polygon aPoly(aRect, nCornerRadius, nCornerRadius); + tools::PolyPolygon aPolyPoly(aPoly); + rRenderContext.DrawTransparent(aPolyPoly, nPercent); + } + else + { + tools::Polygon aPoly(aRect); + tools::PolyPolygon aPolyPoly(aPoly); + rRenderContext.DrawTransparent(aPolyPoly, nPercent); + } + } + + rRenderContext.Pop(); // LINECOLOR | FILLCOLOR +} + +void Window::PushPaintHelper(PaintHelper *pHelper, vcl::RenderContext& rRenderContext) +{ + pHelper->SetPop(); + + if ( mpWindowImpl->mpCursor ) + pHelper->SetRestoreCursor(mpWindowImpl->mpCursor->ImplSuspend()); + + GetOutDev()->mbInitClipRegion = true; + mpWindowImpl->mbInPaint = true; + + // restore Paint-Region + vcl::Region &rPaintRegion = pHelper->GetPaintRegion(); + rPaintRegion = mpWindowImpl->maInvalidateRegion; + tools::Rectangle aPaintRect = rPaintRegion.GetBoundRect(); + + // RTL: re-mirror paint rect and region at this window + if (GetOutDev()->ImplIsAntiparallel()) + { + rRenderContext.ReMirror(aPaintRect); + rRenderContext.ReMirror(rPaintRegion); + } + aPaintRect = GetOutDev()->ImplDevicePixelToLogic(aPaintRect); + mpWindowImpl->mpPaintRegion = &rPaintRegion; + mpWindowImpl->maInvalidateRegion.SetEmpty(); + + if ((pHelper->GetPaintFlags() & ImplPaintFlags::Erase) && rRenderContext.IsBackground()) + { + if (rRenderContext.IsClipRegion()) + { + vcl::Region aOldRegion = rRenderContext.GetClipRegion(); + rRenderContext.SetClipRegion(); + Erase(rRenderContext); + rRenderContext.SetClipRegion(aOldRegion); + } + else + Erase(rRenderContext); + } + + // #98943# trigger drawing of toolbox selection after all children are painted + if (mpWindowImpl->mbDrawSelectionBackground) + pHelper->SetSelectionRect(aPaintRect); + pHelper->SetPaintRect(aPaintRect); +} + +void Window::PopPaintHelper(PaintHelper const *pHelper) +{ + if (mpWindowImpl->mpWinData) + { + if (mpWindowImpl->mbFocusVisible) + ImplInvertFocus(*mpWindowImpl->mpWinData->mpFocusRect); + } + mpWindowImpl->mbInPaint = false; + GetOutDev()->mbInitClipRegion = true; + mpWindowImpl->mpPaintRegion = nullptr; + if (mpWindowImpl->mpCursor) + mpWindowImpl->mpCursor->ImplResume(pHelper->GetRestoreCursor()); +} + +} /* namespace vcl */ + +PaintHelper::~PaintHelper() +{ + WindowImpl* pWindowImpl = m_pWindow->ImplGetWindowImpl(); + if (m_bPop) + { + m_pWindow->PopPaintHelper(this); + } + + ImplFrameData* pFrameData = m_pWindow->mpWindowImpl->mpFrameData; + if ( m_nPaintFlags & (ImplPaintFlags::PaintAllChildren | ImplPaintFlags::PaintChildren) ) + { + // Paint from the bottom child window and frontward. + vcl::Window* pTempWindow = pWindowImpl->mpLastChild; + while (pTempWindow) + { + if (pTempWindow->mpWindowImpl->mbVisible) + pTempWindow->ImplCallPaint(m_pChildRegion.get(), m_nPaintFlags); + pTempWindow = pTempWindow->mpWindowImpl->mpPrev; + } + } + + if ( pWindowImpl->mpWinData && pWindowImpl->mbTrackVisible && (pWindowImpl->mpWinData->mnTrackFlags & ShowTrackFlags::TrackWindow) ) + /* #98602# need to invert the tracking rect AFTER + * the children have painted + */ + m_pWindow->InvertTracking( *pWindowImpl->mpWinData->mpTrackRect, pWindowImpl->mpWinData->mnTrackFlags ); + + // double-buffering: paint in case we created the buffer, the children are + // already painted inside + if (m_bStartedBufferedPaint && pFrameData->mbInBufferedPaint) + { + PaintBuffer(); + pFrameData->mbInBufferedPaint = false; + pFrameData->maBufferedRect = tools::Rectangle(); + } + + // #98943# draw toolbox selection + if( !m_aSelectionRect.IsEmpty() ) + m_pWindow->DrawSelectionBackground( m_aSelectionRect, 3, false, true ); +} + +namespace vcl { + +void Window::ImplCallPaint(const vcl::Region* pRegion, ImplPaintFlags nPaintFlags) +{ + // call PrePaint. PrePaint may add to the invalidate region as well as + // other parameters used below. + PrePaint(*GetOutDev()); + + mpWindowImpl->mbPaintFrame = false; + + if (nPaintFlags & ImplPaintFlags::PaintAllChildren) + mpWindowImpl->mnPaintFlags |= ImplPaintFlags::Paint | ImplPaintFlags::PaintAllChildren | (nPaintFlags & ImplPaintFlags::PaintAll); + if (nPaintFlags & ImplPaintFlags::PaintChildren) + mpWindowImpl->mnPaintFlags |= ImplPaintFlags::PaintChildren; + if (nPaintFlags & ImplPaintFlags::Erase) + mpWindowImpl->mnPaintFlags |= ImplPaintFlags::Erase; + if (nPaintFlags & ImplPaintFlags::CheckRtl) + mpWindowImpl->mnPaintFlags |= ImplPaintFlags::CheckRtl; + if (!mpWindowImpl->mpFirstChild) + mpWindowImpl->mnPaintFlags &= ~ImplPaintFlags::PaintAllChildren; + + // If tiled rendering is used, windows are only invalidated, never painted to. + if (mpWindowImpl->mbPaintDisabled || comphelper::LibreOfficeKit::isActive()) + { + if (mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll) + Invalidate(InvalidateFlags::NoChildren | InvalidateFlags::NoErase | InvalidateFlags::NoTransparent | InvalidateFlags::NoClipChildren); + else if ( pRegion ) + Invalidate(*pRegion, InvalidateFlags::NoChildren | InvalidateFlags::NoErase | InvalidateFlags::NoTransparent | InvalidateFlags::NoClipChildren); + + // call PostPaint before returning + PostPaint(*GetOutDev()); + + return; + } + + nPaintFlags = mpWindowImpl->mnPaintFlags & ~ImplPaintFlags::Paint; + + PaintHelper aHelper(this, nPaintFlags); + + if (mpWindowImpl->mnPaintFlags & ImplPaintFlags::Paint) + aHelper.DoPaint(pRegion); + else + mpWindowImpl->mnPaintFlags = ImplPaintFlags::NONE; + + // call PostPaint + PostPaint(*GetOutDev()); +} + +void Window::ImplCallOverlapPaint() +{ + if (!mpWindowImpl) + return; + + // emit overlapping windows first + vcl::Window* pTempWindow = mpWindowImpl->mpFirstOverlap; + while ( pTempWindow ) + { + if ( pTempWindow->mpWindowImpl->mbReallyVisible ) + pTempWindow->ImplCallOverlapPaint(); + pTempWindow = pTempWindow->mpWindowImpl->mpNext; + } + + // only then ourself + if ( mpWindowImpl->mnPaintFlags & (ImplPaintFlags::Paint | ImplPaintFlags::PaintChildren) ) + { + // RTL: notify ImplCallPaint to check for re-mirroring + // because we were called from the Sal layer + ImplCallPaint(nullptr, mpWindowImpl->mnPaintFlags /*| ImplPaintFlags::CheckRtl */); + } +} + +IMPL_LINK_NOARG(Window, ImplHandlePaintHdl, Timer *, void) +{ + comphelper::ProfileZone aZone("VCL idle re-paint"); + + // save paint events until layout is done + if (IsSystemWindow() && static_cast<const SystemWindow*>(this)->hasPendingLayout()) + { + mpWindowImpl->mpFrameData->maPaintIdle.Start(); + return; + } + + // save paint events until resizing or initial sizing done + if (mpWindowImpl->mbFrame && + mpWindowImpl->mpFrameData->maResizeIdle.IsActive()) + { + mpWindowImpl->mpFrameData->maPaintIdle.Start(); + } + else if ( mpWindowImpl->mbReallyVisible ) + { + ImplCallOverlapPaint(); + if (comphelper::LibreOfficeKit::isActive() && + mpWindowImpl->mpFrameData->maPaintIdle.IsActive()) + mpWindowImpl->mpFrameData->maPaintIdle.Stop(); + } +} + +IMPL_LINK_NOARG(Window, ImplHandleResizeTimerHdl, Timer *, void) +{ + comphelper::ProfileZone aZone("VCL idle resize"); + + if( mpWindowImpl->mbReallyVisible ) + { + ImplCallResize(); + if( mpWindowImpl->mpFrameData->maPaintIdle.IsActive() ) + { + mpWindowImpl->mpFrameData->maPaintIdle.Stop(); + mpWindowImpl->mpFrameData->maPaintIdle.Invoke( nullptr ); + } + } +} + +void Window::ImplInvalidateFrameRegion( const vcl::Region* pRegion, InvalidateFlags nFlags ) +{ + // set PAINTCHILDREN for all parent windows till the first OverlapWindow + if ( !ImplIsOverlapWindow() ) + { + vcl::Window* pTempWindow = this; + ImplPaintFlags nTranspPaint = IsPaintTransparent() ? ImplPaintFlags::Paint : ImplPaintFlags::NONE; + do + { + pTempWindow = pTempWindow->ImplGetParent(); + if ( pTempWindow->mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintChildren ) + break; + pTempWindow->mpWindowImpl->mnPaintFlags |= ImplPaintFlags::PaintChildren | nTranspPaint; + if( ! pTempWindow->IsPaintTransparent() ) + nTranspPaint = ImplPaintFlags::NONE; + } + while ( !pTempWindow->ImplIsOverlapWindow() ); + } + + // set Paint-Flags + mpWindowImpl->mnPaintFlags |= ImplPaintFlags::Paint; + if ( nFlags & InvalidateFlags::Children ) + mpWindowImpl->mnPaintFlags |= ImplPaintFlags::PaintAllChildren; + if ( !(nFlags & InvalidateFlags::NoErase) ) + mpWindowImpl->mnPaintFlags |= ImplPaintFlags::Erase; + + if ( !pRegion ) + mpWindowImpl->mnPaintFlags |= ImplPaintFlags::PaintAll; + else if ( !(mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll) ) + { + // if not everything has to be redrawn, add the region to it + mpWindowImpl->maInvalidateRegion.Union( *pRegion ); + } + + // Handle transparent windows correctly: invalidate must be done on the first opaque parent + if( ((IsPaintTransparent() && !(nFlags & InvalidateFlags::NoTransparent)) || (nFlags & InvalidateFlags::Transparent) ) + && ImplGetParent() ) + { + vcl::Window *pParent = ImplGetParent(); + while( pParent && pParent->IsPaintTransparent() ) + pParent = pParent->ImplGetParent(); + if( pParent ) + { + vcl::Region *pChildRegion; + if ( mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll ) + // invalidate the whole child window region in the parent + pChildRegion = &ImplGetWinChildClipRegion(); + else + // invalidate the same region in the parent that has to be repainted in the child + pChildRegion = &mpWindowImpl->maInvalidateRegion; + + nFlags |= InvalidateFlags::Children; // paint should also be done on all children + nFlags &= ~InvalidateFlags::NoErase; // parent should paint and erase to create proper background + pParent->ImplInvalidateFrameRegion( pChildRegion, nFlags ); + } + } + + if ( !mpWindowImpl->mpFrameData->maPaintIdle.IsActive() ) + mpWindowImpl->mpFrameData->maPaintIdle.Start(); +} + +void Window::ImplInvalidateOverlapFrameRegion( const vcl::Region& rRegion ) +{ + vcl::Region aRegion = rRegion; + + ImplClipBoundaries( aRegion, true, true ); + if ( !aRegion.IsEmpty() ) + ImplInvalidateFrameRegion( &aRegion, InvalidateFlags::Children ); + + // now we invalidate the overlapping windows + vcl::Window* pTempWindow = mpWindowImpl->mpFirstOverlap; + while ( pTempWindow ) + { + if ( pTempWindow->IsVisible() ) + pTempWindow->ImplInvalidateOverlapFrameRegion( rRegion ); + + pTempWindow = pTempWindow->mpWindowImpl->mpNext; + } +} + +void Window::ImplInvalidateParentFrameRegion( const vcl::Region& rRegion ) +{ + if ( mpWindowImpl->mbOverlapWin ) + mpWindowImpl->mpFrameWindow->ImplInvalidateOverlapFrameRegion( rRegion ); + else + { + if( ImplGetParent() ) + ImplGetParent()->ImplInvalidateFrameRegion( &rRegion, InvalidateFlags::Children ); + } +} + +void Window::ImplInvalidate( const vcl::Region* pRegion, InvalidateFlags nFlags ) +{ + // check what has to be redrawn + bool bInvalidateAll = !pRegion; + + // take Transparent-Invalidate into account + vcl::Window* pOpaqueWindow = this; + if ( (mpWindowImpl->mbPaintTransparent && !(nFlags & InvalidateFlags::NoTransparent)) || (nFlags & InvalidateFlags::Transparent) ) + { + vcl::Window* pTempWindow = pOpaqueWindow->ImplGetParent(); + while ( pTempWindow ) + { + if ( !pTempWindow->IsPaintTransparent() ) + { + pOpaqueWindow = pTempWindow; + nFlags |= InvalidateFlags::Children; + bInvalidateAll = false; + break; + } + + if ( pTempWindow->ImplIsOverlapWindow() ) + break; + + pTempWindow = pTempWindow->ImplGetParent(); + } + } + + // assemble region + InvalidateFlags nOrgFlags = nFlags; + if ( !(nFlags & (InvalidateFlags::Children | InvalidateFlags::NoChildren)) ) + { + if ( GetStyle() & WB_CLIPCHILDREN ) + nFlags |= InvalidateFlags::NoChildren; + else + nFlags |= InvalidateFlags::Children; + } + if ( (nFlags & InvalidateFlags::NoChildren) && mpWindowImpl->mpFirstChild ) + bInvalidateAll = false; + if ( bInvalidateAll ) + ImplInvalidateFrameRegion( nullptr, nFlags ); + else + { + vcl::Region aRegion( GetOutputRectPixel() ); + if ( pRegion ) + { + // RTL: remirror region before intersecting it + if ( GetOutDev()->ImplIsAntiparallel() ) + { + const OutputDevice *pOutDev = GetOutDev(); + + vcl::Region aRgn( *pRegion ); + pOutDev->ReMirror( aRgn ); + aRegion.Intersect( aRgn ); + } + else + aRegion.Intersect( *pRegion ); + } + ImplClipBoundaries( aRegion, true, true ); + if ( nFlags & InvalidateFlags::NoChildren ) + { + nFlags &= ~InvalidateFlags::Children; + if ( !(nFlags & InvalidateFlags::NoClipChildren) ) + { + if ( nOrgFlags & InvalidateFlags::NoChildren ) + ImplClipAllChildren( aRegion ); + else + { + if ( ImplClipChildren( aRegion ) ) + nFlags |= InvalidateFlags::Children; + } + } + } + if ( !aRegion.IsEmpty() ) + ImplInvalidateFrameRegion( &aRegion, nFlags ); // transparency is handled here, pOpaqueWindow not required + } + + if ( nFlags & InvalidateFlags::Update ) + pOpaqueWindow->PaintImmediately(); // start painting at the opaque parent +} + +void Window::ImplMoveInvalidateRegion( const tools::Rectangle& rRect, + tools::Long nHorzScroll, tools::Long nVertScroll, + bool bChildren ) +{ + if ( (mpWindowImpl->mnPaintFlags & (ImplPaintFlags::Paint | ImplPaintFlags::PaintAll)) == ImplPaintFlags::Paint ) + { + vcl::Region aTempRegion = mpWindowImpl->maInvalidateRegion; + aTempRegion.Intersect( rRect ); + aTempRegion.Move( nHorzScroll, nVertScroll ); + mpWindowImpl->maInvalidateRegion.Union( aTempRegion ); + } + + if ( bChildren && (mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintChildren) ) + { + vcl::Window* pWindow = mpWindowImpl->mpFirstChild; + while ( pWindow ) + { + pWindow->ImplMoveInvalidateRegion( rRect, nHorzScroll, nVertScroll, true ); + pWindow = pWindow->mpWindowImpl->mpNext; + } + } +} + +void Window::ImplMoveAllInvalidateRegions( const tools::Rectangle& rRect, + tools::Long nHorzScroll, tools::Long nVertScroll, + bool bChildren ) +{ + // also shift Paint-Region when paints need processing + ImplMoveInvalidateRegion( rRect, nHorzScroll, nVertScroll, bChildren ); + // Paint-Region should be shifted, as drawn by the parents + if ( ImplIsOverlapWindow() ) + return; + + vcl::Region aPaintAllRegion; + vcl::Window* pPaintAllWindow = this; + do + { + pPaintAllWindow = pPaintAllWindow->ImplGetParent(); + if ( pPaintAllWindow->mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAllChildren ) + { + if ( pPaintAllWindow->mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll ) + { + aPaintAllRegion.SetEmpty(); + break; + } + else + aPaintAllRegion.Union( pPaintAllWindow->mpWindowImpl->maInvalidateRegion ); + } + } + while ( !pPaintAllWindow->ImplIsOverlapWindow() ); + if ( !aPaintAllRegion.IsEmpty() ) + { + aPaintAllRegion.Move( nHorzScroll, nVertScroll ); + InvalidateFlags nPaintFlags = InvalidateFlags::NONE; + if ( bChildren ) + nPaintFlags |= InvalidateFlags::Children; + ImplInvalidateFrameRegion( &aPaintAllRegion, nPaintFlags ); + } +} + +void Window::ImplValidateFrameRegion( const vcl::Region* pRegion, ValidateFlags nFlags ) +{ + if ( !pRegion ) + mpWindowImpl->maInvalidateRegion.SetEmpty(); + else + { + // when all child windows have to be drawn we need to invalidate them before doing so + if ( (mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAllChildren) && mpWindowImpl->mpFirstChild ) + { + vcl::Region aChildRegion = mpWindowImpl->maInvalidateRegion; + if ( mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll ) + { + aChildRegion = GetOutputRectPixel(); + } + vcl::Window* pChild = mpWindowImpl->mpFirstChild; + while ( pChild ) + { + pChild->Invalidate( aChildRegion, InvalidateFlags::Children | InvalidateFlags::NoTransparent ); + pChild = pChild->mpWindowImpl->mpNext; + } + } + if ( mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAll ) + { + mpWindowImpl->maInvalidateRegion = GetOutputRectPixel(); + } + mpWindowImpl->maInvalidateRegion.Exclude( *pRegion ); + } + mpWindowImpl->mnPaintFlags &= ~ImplPaintFlags::PaintAll; + + if ( nFlags & ValidateFlags::Children ) + { + vcl::Window* pChild = mpWindowImpl->mpFirstChild; + while ( pChild ) + { + pChild->ImplValidateFrameRegion( pRegion, nFlags ); + pChild = pChild->mpWindowImpl->mpNext; + } + } +} + +void Window::ImplValidate() +{ + // assemble region + bool bValidateAll = true; + ValidateFlags nFlags = ValidateFlags::NONE; + if ( GetStyle() & WB_CLIPCHILDREN ) + nFlags |= ValidateFlags::NoChildren; + else + nFlags |= ValidateFlags::Children; + if ( (nFlags & ValidateFlags::NoChildren) && mpWindowImpl->mpFirstChild ) + bValidateAll = false; + if ( bValidateAll ) + ImplValidateFrameRegion( nullptr, nFlags ); + else + { + vcl::Region aRegion( GetOutputRectPixel() ); + ImplClipBoundaries( aRegion, true, true ); + if ( nFlags & ValidateFlags::NoChildren ) + { + nFlags &= ~ValidateFlags::Children; + if ( ImplClipChildren( aRegion ) ) + nFlags |= ValidateFlags::Children; + } + if ( !aRegion.IsEmpty() ) + ImplValidateFrameRegion( &aRegion, nFlags ); + } +} + +void Window::ImplUpdateAll() +{ + if ( !mpWindowImpl || !mpWindowImpl->mbReallyVisible ) + return; + + bool bFlush = false; + if ( mpWindowImpl->mpFrameWindow->mpWindowImpl->mbPaintFrame ) + { + Point aPoint( 0, 0 ); + vcl::Region aRegion( tools::Rectangle( aPoint, GetOutputSizePixel() ) ); + ImplInvalidateOverlapFrameRegion( aRegion ); + if ( mpWindowImpl->mbFrame || (mpWindowImpl->mpBorderWindow && mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame) ) + bFlush = true; + } + + // an update changes the OverlapWindow, such that for later paints + // not too much has to be drawn, if ALLCHILDREN etc. is set + vcl::Window* pWindow = ImplGetFirstOverlapWindow(); + pWindow->ImplCallOverlapPaint(); + + if ( bFlush ) + GetOutDev()->Flush(); +} + +void Window::PrePaint(vcl::RenderContext& /*rRenderContext*/) +{ +} + +void Window::PostPaint(vcl::RenderContext& /*rRenderContext*/) +{ +} + +void Window::Paint(vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& rRect) +{ + CallEventListeners(VclEventId::WindowPaint, const_cast<tools::Rectangle *>(&rRect)); +} + +void Window::SetPaintTransparent( bool bTransparent ) +{ + // transparency is not useful for frames as the background would have to be provided by a different frame + if( bTransparent && mpWindowImpl->mbFrame ) + return; + + if ( mpWindowImpl->mpBorderWindow ) + mpWindowImpl->mpBorderWindow->SetPaintTransparent( bTransparent ); + + mpWindowImpl->mbPaintTransparent = bTransparent; +} + +void Window::SetWindowRegionPixel() +{ + + if ( mpWindowImpl->mpBorderWindow ) + mpWindowImpl->mpBorderWindow->SetWindowRegionPixel(); + else if( mpWindowImpl->mbFrame ) + { + mpWindowImpl->maWinRegion = vcl::Region(true); + mpWindowImpl->mbWinRegion = false; + mpWindowImpl->mpFrame->ResetClipRegion(); + } + else + { + if ( mpWindowImpl->mbWinRegion ) + { + mpWindowImpl->maWinRegion = vcl::Region(true); + mpWindowImpl->mbWinRegion = false; + ImplSetClipFlag(); + + if ( IsReallyVisible() ) + { + vcl::Region aRegion( GetOutputRectPixel() ); + ImplInvalidateParentFrameRegion( aRegion ); + } + } + } +} + +void Window::SetWindowRegionPixel( const vcl::Region& rRegion ) +{ + + if ( mpWindowImpl->mpBorderWindow ) + mpWindowImpl->mpBorderWindow->SetWindowRegionPixel( rRegion ); + else if( mpWindowImpl->mbFrame ) + { + if( !rRegion.IsNull() ) + { + mpWindowImpl->maWinRegion = rRegion; + mpWindowImpl->mbWinRegion = ! rRegion.IsEmpty(); + + if( mpWindowImpl->mbWinRegion ) + { + // set/update ClipRegion + RectangleVector aRectangles; + mpWindowImpl->maWinRegion.GetRegionRectangles(aRectangles); + mpWindowImpl->mpFrame->BeginSetClipRegion(aRectangles.size()); + + for (auto const& rectangle : aRectangles) + { + mpWindowImpl->mpFrame->UnionClipRegion( + rectangle.Left(), + rectangle.Top(), + rectangle.GetWidth(), // orig nWidth was ((R - L) + 1), same as GetWidth does + rectangle.GetHeight()); // same for height + } + + mpWindowImpl->mpFrame->EndSetClipRegion(); + } + else + SetWindowRegionPixel(); + } + else + SetWindowRegionPixel(); + } + else + { + if ( rRegion.IsNull() ) + { + if ( mpWindowImpl->mbWinRegion ) + { + mpWindowImpl->maWinRegion = vcl::Region(true); + mpWindowImpl->mbWinRegion = false; + ImplSetClipFlag(); + } + } + else + { + mpWindowImpl->maWinRegion = rRegion; + mpWindowImpl->mbWinRegion = true; + ImplSetClipFlag(); + } + + if ( IsReallyVisible() ) + { + vcl::Region aRegion( GetOutputRectPixel() ); + ImplInvalidateParentFrameRegion( aRegion ); + } + } +} + +vcl::Region Window::GetPaintRegion() const +{ + + if ( mpWindowImpl->mpPaintRegion ) + { + vcl::Region aRegion = *mpWindowImpl->mpPaintRegion; + aRegion.Move( -GetOutDev()->mnOutOffX, -GetOutDev()->mnOutOffY ); + return PixelToLogic( aRegion ); + } + else + { + vcl::Region aPaintRegion(true); + return aPaintRegion; + } +} + +void Window::Invalidate( InvalidateFlags nFlags ) +{ + if ( !comphelper::LibreOfficeKit::isActive() && (!GetOutDev()->IsDeviceOutputNecessary() || !GetOutDev()->mnOutWidth || !GetOutDev()->mnOutHeight) ) + return; + + ImplInvalidate( nullptr, nFlags ); + LogicInvalidate(nullptr); +} + +void Window::Invalidate( const tools::Rectangle& rRect, InvalidateFlags nFlags ) +{ + if ( !comphelper::LibreOfficeKit::isActive() && (!GetOutDev()->IsDeviceOutputNecessary() || !GetOutDev()->mnOutWidth || !GetOutDev()->mnOutHeight) ) + return; + + OutputDevice *pOutDev = GetOutDev(); + tools::Rectangle aRect = pOutDev->ImplLogicToDevicePixel( rRect ); + if ( !aRect.IsEmpty() ) + { + vcl::Region aRegion( aRect ); + ImplInvalidate( &aRegion, nFlags ); + tools::Rectangle aLogicRectangle(rRect); + LogicInvalidate(&aLogicRectangle); + } +} + +void Window::Invalidate( const vcl::Region& rRegion, InvalidateFlags nFlags ) +{ + if ( !comphelper::LibreOfficeKit::isActive() && (!GetOutDev()->IsDeviceOutputNecessary() || !GetOutDev()->mnOutWidth || !GetOutDev()->mnOutHeight) ) + return; + + if ( rRegion.IsNull() ) + { + ImplInvalidate( nullptr, nFlags ); + LogicInvalidate(nullptr); + } + else + { + vcl::Region aRegion = GetOutDev()->ImplPixelToDevicePixel( LogicToPixel( rRegion ) ); + if ( !aRegion.IsEmpty() ) + { + ImplInvalidate( &aRegion, nFlags ); + tools::Rectangle aLogicRectangle = rRegion.GetBoundRect(); + LogicInvalidate(&aLogicRectangle); + } + } +} + +void Window::LogicInvalidate(const tools::Rectangle* pRectangle) +{ + if(pRectangle) + { + tools::Rectangle aRect = GetOutDev()->ImplLogicToDevicePixel( *pRectangle ); + PixelInvalidate(&aRect); + } + else + PixelInvalidate(nullptr); +} + +void Window::PixelInvalidate(const tools::Rectangle* pRectangle) +{ + if (comphelper::LibreOfficeKit::isDialogPainting() || !comphelper::LibreOfficeKit::isActive()) + return; + + Size aSize = GetSizePixel(); + if (aSize.IsEmpty()) + return; + + if (const vcl::ILibreOfficeKitNotifier* pNotifier = GetLOKNotifier()) + { + // In case we are routing the window, notify the client + std::vector<vcl::LOKPayloadItem> aPayload; + tools::Rectangle aRect(Point(0, 0), aSize); + if (pRectangle) + aRect = *pRectangle; + + if (IsRTLEnabled() && GetOutDev() && !GetOutDev()->ImplIsAntiparallel()) + GetOutDev()->ReMirror(aRect); + + aPayload.emplace_back("rectangle", aRect.toString()); + + pNotifier->notifyWindow(GetLOKWindowId(), "invalidate", aPayload); + } + // Added for dialog items. Pass invalidation to the parent window. + else if (VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier()) + { + const tools::Rectangle aRect(Point(GetOutOffXPixel(), GetOutOffYPixel()), GetSizePixel()); + pParent->PixelInvalidate(&aRect); + } +} + +void Window::Validate() +{ + if ( !comphelper::LibreOfficeKit::isActive() && (!GetOutDev()->IsDeviceOutputNecessary() || !GetOutDev()->mnOutWidth || !GetOutDev()->mnOutHeight) ) + return; + + ImplValidate(); +} + +bool Window::HasPaintEvent() const +{ + + if ( !mpWindowImpl->mbReallyVisible ) + return false; + + if ( mpWindowImpl->mpFrameWindow->mpWindowImpl->mbPaintFrame ) + return true; + + if ( mpWindowImpl->mnPaintFlags & ImplPaintFlags::Paint ) + return true; + + if ( !ImplIsOverlapWindow() ) + { + const vcl::Window* pTempWindow = this; + do + { + pTempWindow = pTempWindow->ImplGetParent(); + if ( pTempWindow->mpWindowImpl->mnPaintFlags & (ImplPaintFlags::PaintChildren | ImplPaintFlags::PaintAllChildren) ) + return true; + } + while ( !pTempWindow->ImplIsOverlapWindow() ); + } + + return false; +} + +void Window::PaintImmediately() +{ + if (!mpWindowImpl) + return; + + if ( mpWindowImpl->mpBorderWindow ) + { + mpWindowImpl->mpBorderWindow->PaintImmediately(); + return; + } + + if ( !mpWindowImpl->mbReallyVisible ) + return; + + bool bFlush = false; + if ( mpWindowImpl->mpFrameWindow->mpWindowImpl->mbPaintFrame ) + { + Point aPoint( 0, 0 ); + vcl::Region aRegion( tools::Rectangle( aPoint, GetOutputSizePixel() ) ); + ImplInvalidateOverlapFrameRegion( aRegion ); + if ( mpWindowImpl->mbFrame || (mpWindowImpl->mpBorderWindow && mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame) ) + bFlush = true; + } + + // First we should skip all windows which are Paint-Transparent + vcl::Window* pUpdateWindow = this; + vcl::Window* pWindow = pUpdateWindow; + while ( !pWindow->ImplIsOverlapWindow() ) + { + if ( !pWindow->mpWindowImpl->mbPaintTransparent ) + { + pUpdateWindow = pWindow; + break; + } + pWindow = pWindow->ImplGetParent(); + } + // In order to limit drawing, an update only draws the window which + // has PAINTALLCHILDREN set + pWindow = pUpdateWindow; + do + { + if ( pWindow->mpWindowImpl->mnPaintFlags & ImplPaintFlags::PaintAllChildren ) + pUpdateWindow = pWindow; + if ( pWindow->ImplIsOverlapWindow() ) + break; + pWindow = pWindow->ImplGetParent(); + } + while ( pWindow ); + + // if there is something to paint, trigger a Paint + if ( pUpdateWindow->mpWindowImpl->mnPaintFlags & (ImplPaintFlags::Paint | ImplPaintFlags::PaintChildren) ) + { + VclPtr<vcl::Window> xWindow(this); + + // trigger an update also for system windows on top of us, + // otherwise holes would remain + vcl::Window* pUpdateOverlapWindow = ImplGetFirstOverlapWindow(); + if (pUpdateOverlapWindow->mpWindowImpl) + pUpdateOverlapWindow = pUpdateOverlapWindow->mpWindowImpl->mpFirstOverlap; + else + pUpdateOverlapWindow = nullptr; + while ( pUpdateOverlapWindow ) + { + pUpdateOverlapWindow->PaintImmediately(); + pUpdateOverlapWindow = pUpdateOverlapWindow->mpWindowImpl->mpNext; + } + + pUpdateWindow->ImplCallPaint(nullptr, pUpdateWindow->mpWindowImpl->mnPaintFlags); + + if (comphelper::LibreOfficeKit::isActive() && pUpdateWindow->GetParentDialog()) + pUpdateWindow->LogicInvalidate(nullptr); + + if (xWindow->isDisposed()) + return; + + bFlush = true; + } + + if ( bFlush ) + GetOutDev()->Flush(); +} + +void Window::ImplPaintToDevice( OutputDevice* i_pTargetOutDev, const Point& i_rPos ) +{ + // Special drawing when called through LOKit + // TODO: Move to its own method + if (comphelper::LibreOfficeKit::isActive()) + { + VclPtrInstance<VirtualDevice> pDevice(*i_pTargetOutDev); + pDevice->EnableRTL(IsRTLEnabled()); + + Size aSize(GetOutputSizePixel()); + pDevice->SetOutputSizePixel(aSize); + + vcl::Font aCopyFont = GetFont(); + pDevice->SetFont(aCopyFont); + + pDevice->SetTextColor(GetTextColor()); + if (GetOutDev()->IsLineColor()) + pDevice->SetLineColor(GetOutDev()->GetLineColor()); + else + pDevice->SetLineColor(); + + if (GetOutDev()->IsFillColor()) + pDevice->SetFillColor(GetOutDev()->GetFillColor()); + else + pDevice->SetFillColor(); + + if (IsTextLineColor()) + pDevice->SetTextLineColor(GetTextLineColor()); + else + pDevice->SetTextLineColor(); + + if (IsOverlineColor()) + pDevice->SetOverlineColor(GetOverlineColor()); + else + pDevice->SetOverlineColor(); + + if (IsTextFillColor()) + pDevice->SetTextFillColor(GetTextFillColor()); + else + pDevice->SetTextFillColor(); + + pDevice->SetTextAlign(GetTextAlign()); + pDevice->SetRasterOp(GetOutDev()->GetRasterOp()); + + tools::Rectangle aPaintRect(Point(), GetOutputSizePixel()); + + vcl::Region aClipRegion(GetOutDev()->GetClipRegion()); + pDevice->SetClipRegion(); + aClipRegion.Intersect(aPaintRect); + pDevice->SetClipRegion(aClipRegion); + + if (!IsPaintTransparent() && IsBackground() && ! (GetParentClipMode() & ParentClipMode::NoClip)) + Erase(*pDevice); + + pDevice->SetMapMode(GetMapMode()); + + Paint(*pDevice, tools::Rectangle(Point(), GetOutputSizePixel())); + + i_pTargetOutDev->DrawOutDev(i_rPos, aSize, Point(), pDevice->PixelToLogic(aSize), *pDevice); + + bool bHasMirroredGraphics = pDevice->HasMirroredGraphics(); + + // get rid of virtual device now so they don't pile up during recursive calls + pDevice.disposeAndClear(); + + + for( vcl::Window* pChild = mpWindowImpl->mpFirstChild; pChild; pChild = pChild->mpWindowImpl->mpNext ) + { + if( pChild->mpWindowImpl->mpFrame == mpWindowImpl->mpFrame && pChild->IsVisible() ) + { + tools::Long nDeltaX = pChild->GetOutDev()->mnOutOffX - GetOutDev()->mnOutOffX; + if( bHasMirroredGraphics ) + nDeltaX = GetOutDev()->mnOutWidth - nDeltaX - pChild->GetOutDev()->mnOutWidth; + + tools::Long nDeltaY = pChild->GetOutOffYPixel() - GetOutOffYPixel(); + + Point aPos( i_rPos ); + aPos += Point(nDeltaX, nDeltaY); + + pChild->ImplPaintToDevice( i_pTargetOutDev, aPos ); + } + } + return; + } + + + bool bRVisible = mpWindowImpl->mbReallyVisible; + mpWindowImpl->mbReallyVisible = mpWindowImpl->mbVisible; + bool bDevOutput = GetOutDev()->mbDevOutput; + GetOutDev()->mbDevOutput = true; + + const OutputDevice *pOutDev = GetOutDev(); + tools::Long nOldDPIX = pOutDev->GetDPIX(); + tools::Long nOldDPIY = pOutDev->GetDPIY(); + GetOutDev()->mnDPIX = i_pTargetOutDev->GetDPIX(); + GetOutDev()->mnDPIY = i_pTargetOutDev->GetDPIY(); + bool bOutput = GetOutDev()->IsOutputEnabled(); + GetOutDev()->EnableOutput(); + + SAL_WARN_IF( GetMapMode().GetMapUnit() != MapUnit::MapPixel, "vcl.window", "MapMode must be PIXEL based" ); + if ( GetMapMode().GetMapUnit() != MapUnit::MapPixel ) + return; + + // preserve graphicsstate + GetOutDev()->Push(); + vcl::Region aClipRegion( GetOutDev()->GetClipRegion() ); + GetOutDev()->SetClipRegion(); + + GDIMetaFile* pOldMtf = GetOutDev()->GetConnectMetaFile(); + GDIMetaFile aMtf; + GetOutDev()->SetConnectMetaFile( &aMtf ); + + // put a push action to metafile + GetOutDev()->Push(); + // copy graphics state to metafile + vcl::Font aCopyFont = GetFont(); + if( nOldDPIX != GetOutDev()->mnDPIX || nOldDPIY != GetOutDev()->mnDPIY ) + { + aCopyFont.SetFontHeight( aCopyFont.GetFontHeight() * GetOutDev()->mnDPIY / nOldDPIY ); + aCopyFont.SetAverageFontWidth( aCopyFont.GetAverageFontWidth() * GetOutDev()->mnDPIX / nOldDPIX ); + } + SetFont( aCopyFont ); + SetTextColor( GetTextColor() ); + if( GetOutDev()->IsLineColor() ) + GetOutDev()->SetLineColor( GetOutDev()->GetLineColor() ); + else + GetOutDev()->SetLineColor(); + if( GetOutDev()->IsFillColor() ) + GetOutDev()->SetFillColor( GetOutDev()->GetFillColor() ); + else + GetOutDev()->SetFillColor(); + if( IsTextLineColor() ) + SetTextLineColor( GetTextLineColor() ); + else + SetTextLineColor(); + if( IsOverlineColor() ) + SetOverlineColor( GetOverlineColor() ); + else + SetOverlineColor(); + if( IsTextFillColor() ) + SetTextFillColor( GetTextFillColor() ); + else + SetTextFillColor(); + SetTextAlign( GetTextAlign() ); + GetOutDev()->SetRasterOp( GetOutDev()->GetRasterOp() ); + if( GetOutDev()->IsRefPoint() ) + GetOutDev()->SetRefPoint( GetOutDev()->GetRefPoint() ); + else + GetOutDev()->SetRefPoint(); + GetOutDev()->SetLayoutMode( GetOutDev()->GetLayoutMode() ); + + GetOutDev()->SetDigitLanguage( GetOutDev()->GetDigitLanguage() ); + tools::Rectangle aPaintRect(Point(0, 0), GetOutputSizePixel()); + aClipRegion.Intersect( aPaintRect ); + GetOutDev()->SetClipRegion( aClipRegion ); + + // do the actual paint + + // background + if( ! IsPaintTransparent() && IsBackground() && ! (GetParentClipMode() & ParentClipMode::NoClip ) ) + { + Erase(*GetOutDev()); + } + // foreground + Paint(*GetOutDev(), aPaintRect); + // put a pop action to metafile + GetOutDev()->Pop(); + + GetOutDev()->SetConnectMetaFile( pOldMtf ); + GetOutDev()->EnableOutput( bOutput ); + mpWindowImpl->mbReallyVisible = bRVisible; + + // paint metafile to VDev + VclPtrInstance<VirtualDevice> pMaskedDevice(*i_pTargetOutDev, + DeviceFormat::DEFAULT, + DeviceFormat::DEFAULT); + + pMaskedDevice->SetOutputSizePixel( GetOutputSizePixel() ); + pMaskedDevice->EnableRTL( IsRTLEnabled() ); + aMtf.WindStart(); + aMtf.Play(*pMaskedDevice); + BitmapEx aBmpEx( pMaskedDevice->GetBitmapEx( Point( 0, 0 ), aPaintRect.GetSize() ) ); + i_pTargetOutDev->DrawBitmapEx( i_rPos, aBmpEx ); + // get rid of virtual device now so they don't pile up during recursive calls + pMaskedDevice.disposeAndClear(); + + for( vcl::Window* pChild = mpWindowImpl->mpFirstChild; pChild; pChild = pChild->mpWindowImpl->mpNext ) + { + if( pChild->mpWindowImpl->mpFrame == mpWindowImpl->mpFrame && pChild->IsVisible() ) + { + tools::Long nDeltaX = pChild->GetOutDev()->mnOutOffX - GetOutDev()->mnOutOffX; + + if( pOutDev->HasMirroredGraphics() ) + nDeltaX = GetOutDev()->mnOutWidth - nDeltaX - pChild->GetOutDev()->mnOutWidth; + tools::Long nDeltaY = pChild->GetOutOffYPixel() - GetOutOffYPixel(); + Point aPos( i_rPos ); + Point aDelta( nDeltaX, nDeltaY ); + aPos += aDelta; + pChild->ImplPaintToDevice( i_pTargetOutDev, aPos ); + } + } + + // restore graphics state + GetOutDev()->Pop(); + + GetOutDev()->EnableOutput( bOutput ); + mpWindowImpl->mbReallyVisible = bRVisible; + GetOutDev()->mbDevOutput = bDevOutput; + GetOutDev()->mnDPIX = nOldDPIX; + GetOutDev()->mnDPIY = nOldDPIY; +} + +void Window::PaintToDevice(OutputDevice* pDev, const Point& rPos) +{ + if( !mpWindowImpl ) + return; + + SAL_WARN_IF( pDev->HasMirroredGraphics(), "vcl.window", "PaintToDevice to mirroring graphics" ); + SAL_WARN_IF( pDev->IsRTLEnabled(), "vcl.window", "PaintToDevice to mirroring device" ); + + vcl::Window* pRealParent = nullptr; + if( ! mpWindowImpl->mbVisible ) + { + vcl::Window* pTempParent = ImplGetDefaultWindow(); + pTempParent->EnableChildTransparentMode(); + pRealParent = GetParent(); + SetParent( pTempParent ); + // trigger correct visibility flags for children + Show(); + Hide(); + } + + bool bVisible = mpWindowImpl->mbVisible; + mpWindowImpl->mbVisible = true; + + if( mpWindowImpl->mpBorderWindow ) + mpWindowImpl->mpBorderWindow->ImplPaintToDevice( pDev, rPos ); + else + ImplPaintToDevice( pDev, rPos ); + + mpWindowImpl->mbVisible = bVisible; + + if( pRealParent ) + SetParent( pRealParent ); +} + +void Window::Erase(vcl::RenderContext& rRenderContext) +{ + if (!GetOutDev()->IsDeviceOutputNecessary() || GetOutDev()->ImplIsRecordLayout()) + return; + + bool bNativeOK = false; + + ControlPart aCtrlPart = ImplGetWindowImpl()->mnNativeBackground; + + if (aCtrlPart == ControlPart::Entire && IsControlBackground()) + { + // nothing to do here; background is drawn in corresponding drawNativeControl implementation + bNativeOK = true; + } + else if (aCtrlPart != ControlPart::NONE && ! IsControlBackground()) + { + tools::Rectangle aCtrlRegion(Point(), GetOutputSizePixel()); + ControlState nState = ControlState::NONE; + + if (IsEnabled()) + nState |= ControlState::ENABLED; + + bNativeOK = rRenderContext.DrawNativeControl(ControlType::WindowBackground, aCtrlPart, aCtrlRegion, + nState, ImplControlValue(), OUString()); + } + + if (GetOutDev()->mbBackground && !bNativeOK) + { + RasterOp eRasterOp = GetOutDev()->GetRasterOp(); + if (eRasterOp != RasterOp::OverPaint) + GetOutDev()->SetRasterOp(RasterOp::OverPaint); + rRenderContext.DrawWallpaper(0, 0, GetOutDev()->mnOutWidth, GetOutDev()->mnOutHeight, GetOutDev()->maBackground); + if (eRasterOp != RasterOp::OverPaint) + rRenderContext.SetRasterOp(eRasterOp); + } + + if (GetOutDev()->mpAlphaVDev) + GetOutDev()->mpAlphaVDev->Erase(); +} + +void Window::ImplScroll( const tools::Rectangle& rRect, + tools::Long nHorzScroll, tools::Long nVertScroll, ScrollFlags nFlags ) +{ + if ( !GetOutDev()->IsDeviceOutputNecessary() ) + return; + + nHorzScroll = GetOutDev()->ImplLogicWidthToDevicePixel( nHorzScroll ); + nVertScroll = GetOutDev()->ImplLogicHeightToDevicePixel( nVertScroll ); + + if ( !nHorzScroll && !nVertScroll ) + return; + + // There will be no CopyArea() call below, so invalidate the whole visible + // area, not only the smaller one that was just scrolled in. + // Do this when we have a double buffer anyway, or (tdf#152094) the device has a map mode enabled which + // makes the conversion to pixel inaccurate + const bool bCopyExistingAreaAndElideInvalidate = !SupportsDoubleBuffering() && !GetOutDev()->IsMapModeEnabled(); + + if ( mpWindowImpl->mpCursor ) + mpWindowImpl->mpCursor->ImplSuspend(); + + ScrollFlags nOrgFlags = nFlags; + if ( !(nFlags & (ScrollFlags::Children | ScrollFlags::NoChildren)) ) + { + if ( GetStyle() & WB_CLIPCHILDREN ) + nFlags |= ScrollFlags::NoChildren; + else + nFlags |= ScrollFlags::Children; + } + + vcl::Region aInvalidateRegion; + bool bScrollChildren(nFlags & ScrollFlags::Children); + + if ( !mpWindowImpl->mpFirstChild ) + bScrollChildren = false; + + OutputDevice *pOutDev = GetOutDev(); + + // RTL: check if this window requires special action + bool bReMirror = GetOutDev()->ImplIsAntiparallel(); + + tools::Rectangle aRectMirror( rRect ); + if( bReMirror ) + { + // make sure the invalidate region of this window is + // computed in the same coordinate space as the one from the overlap windows + pOutDev->ReMirror( aRectMirror ); + } + + // adapt paint areas + ImplMoveAllInvalidateRegions( aRectMirror, nHorzScroll, nVertScroll, bScrollChildren ); + + ImplCalcOverlapRegion( aRectMirror, aInvalidateRegion, !bScrollChildren, false ); + + // if the scrolling on the device is performed in the opposite direction + // then move the overlaps in that direction to compute the invalidate region + // on the correct side, i.e., revert nHorzScroll + if (!aInvalidateRegion.IsEmpty()) + { + aInvalidateRegion.Move(bReMirror ? -nHorzScroll : nHorzScroll, nVertScroll); + } + + tools::Rectangle aDestRect(aRectMirror); + aDestRect.Move(bReMirror ? -nHorzScroll : nHorzScroll, nVertScroll); + vcl::Region aWinInvalidateRegion(aRectMirror); + if (bCopyExistingAreaAndElideInvalidate) + aWinInvalidateRegion.Exclude(aDestRect); + + aInvalidateRegion.Union(aWinInvalidateRegion); + + vcl::Region aRegion( GetOutputRectPixel() ); + if ( nFlags & ScrollFlags::Clip ) + aRegion.Intersect( rRect ); + if ( mpWindowImpl->mbWinRegion ) + aRegion.Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) ); + + aRegion.Exclude( aInvalidateRegion ); + + ImplClipBoundaries( aRegion, false, true ); + if ( !bScrollChildren ) + { + if ( nOrgFlags & ScrollFlags::NoChildren ) + ImplClipAllChildren( aRegion ); + else + ImplClipChildren( aRegion ); + } + if ( GetOutDev()->mbClipRegion && (nFlags & ScrollFlags::UseClipRegion) ) + aRegion.Intersect( GetOutDev()->maRegion ); + if ( !aRegion.IsEmpty() ) + { + if ( mpWindowImpl->mpWinData ) + { + if ( mpWindowImpl->mbFocusVisible ) + ImplInvertFocus( *mpWindowImpl->mpWinData->mpFocusRect ); + if ( mpWindowImpl->mbTrackVisible && (mpWindowImpl->mpWinData->mnTrackFlags & ShowTrackFlags::TrackWindow) ) + InvertTracking( *mpWindowImpl->mpWinData->mpTrackRect, mpWindowImpl->mpWinData->mnTrackFlags ); + } +#ifndef IOS + // This seems completely unnecessary with tiled rendering, and + // causes the "AquaSalGraphics::copyArea() for non-layered + // graphics" message. Presumably we should bypass this on all + // platforms when dealing with a "window" that uses tiled + // rendering at the moment. Unclear how to figure that out, + // though. Also unclear whether we actually could just not + // create a "frame window", whatever that exactly is, in the + // tiled rendering case, or at least for platforms where tiles + // rendering is all there is. + + SalGraphics* pGraphics = ImplGetFrameGraphics(); + // The invalidation area contains the area what would be copied here, + // so avoid copying in case of double buffering. + if (pGraphics && bCopyExistingAreaAndElideInvalidate) + { + if( bReMirror ) + { + pOutDev->ReMirror( aRegion ); + } + + pOutDev->SelectClipRegion( aRegion, pGraphics ); + pGraphics->CopyArea( rRect.Left()+nHorzScroll, rRect.Top()+nVertScroll, + rRect.Left(), rRect.Top(), + rRect.GetWidth(), rRect.GetHeight(), + *GetOutDev() ); + } +#endif + if ( mpWindowImpl->mpWinData ) + { + if ( mpWindowImpl->mbFocusVisible ) + ImplInvertFocus( *mpWindowImpl->mpWinData->mpFocusRect ); + if ( mpWindowImpl->mbTrackVisible && (mpWindowImpl->mpWinData->mnTrackFlags & ShowTrackFlags::TrackWindow) ) + InvertTracking( *mpWindowImpl->mpWinData->mpTrackRect, mpWindowImpl->mpWinData->mnTrackFlags ); + } + } + + if ( !aInvalidateRegion.IsEmpty() ) + { + // RTL: the invalidate region for this windows is already computed in frame coordinates + // so it has to be re-mirrored before calling the Paint-handler + mpWindowImpl->mnPaintFlags |= ImplPaintFlags::CheckRtl; + + if ( !bScrollChildren ) + { + if ( nOrgFlags & ScrollFlags::NoChildren ) + ImplClipAllChildren( aInvalidateRegion ); + else + ImplClipChildren( aInvalidateRegion ); + } + ImplInvalidateFrameRegion( &aInvalidateRegion, InvalidateFlags::Children ); + } + + if ( bScrollChildren ) + { + vcl::Window* pWindow = mpWindowImpl->mpFirstChild; + while ( pWindow ) + { + Point aPos = pWindow->GetPosPixel(); + aPos += Point( nHorzScroll, nVertScroll ); + pWindow->SetPosPixel( aPos ); + + pWindow = pWindow->mpWindowImpl->mpNext; + } + } + + if ( nFlags & ScrollFlags::Update ) + PaintImmediately(); + + if ( mpWindowImpl->mpCursor ) + mpWindowImpl->mpCursor->ImplResume(); +} + +} /* namespace vcl */ + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/printdlg.cxx b/vcl/source/window/printdlg.cxx new file mode 100644 index 000000000..c50bd3ae3 --- /dev/null +++ b/vcl/source/window/printdlg.cxx @@ -0,0 +1,2267 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * 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/safeint.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <rtl/ustrbuf.hxx> +#include <unotools/localedatawrapper.hxx> +#include <officecfg/Office/Common.hxx> + +#include <vcl/QueueInfo.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/decoview.hxx> +#include <vcl/help.hxx> +#include <vcl/naturalsort.hxx> +#include <vcl/print.hxx> +#include <vcl/printer/Options.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <vcl/wall.hxx> +#include <vcl/weldutils.hxx> +#include <vcl/windowstate.hxx> + +#include <bitmaps.hlst> +#include <configsettings.hxx> +#include <printdlg.hxx> +#include <strings.hrc> +#include <svdata.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> + +using namespace vcl; +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; +using namespace com::sun::star::beans; + +enum +{ + ORIENTATION_AUTOMATIC, + ORIENTATION_PORTRAIT, + ORIENTATION_LANDSCAPE +}; + +namespace { + bool lcl_ListBoxCompare( const OUString& rStr1, const OUString& rStr2 ) + { + return vcl::NaturalSortCompare( rStr1, rStr2 ) < 0; + } +} + +PrintDialog::PrintPreviewWindow::PrintPreviewWindow(PrintDialog* pDialog) + : mpDialog(pDialog) + , maOrigSize( 10, 10 ) + , mnDPIX(Application::GetDefaultDevice()->GetDPIX()) + , mnDPIY(Application::GetDefaultDevice()->GetDPIY()) + , mbGreyscale( false ) +{ +} + +PrintDialog::PrintPreviewWindow::~PrintPreviewWindow() +{ +} + +void PrintDialog::PrintPreviewWindow::Resize() +{ + Size aNewSize(GetOutputSizePixel()); + tools::Long nTextHeight = GetDrawingArea()->get_text_height(); + // leave small space for decoration + aNewSize.AdjustWidth( -(nTextHeight + 2) ); + aNewSize.AdjustHeight( -(nTextHeight + 2) ); + Size aScaledSize; + double fScale = 1.0; + + // #i106435# catch corner case of Size(0,0) + Size aOrigSize( maOrigSize ); + if( aOrigSize.Width() < 1 ) + aOrigSize.setWidth( aNewSize.Width() ); + if( aOrigSize.Height() < 1 ) + aOrigSize.setHeight( aNewSize.Height() ); + if( aOrigSize.Width() > aOrigSize.Height() ) + { + aScaledSize = Size( aNewSize.Width(), aNewSize.Width() * aOrigSize.Height() / aOrigSize.Width() ); + if( aScaledSize.Height() > aNewSize.Height() ) + fScale = double(aNewSize.Height())/double(aScaledSize.Height()); + } + else + { + aScaledSize = Size( aNewSize.Height() * aOrigSize.Width() / aOrigSize.Height(), aNewSize.Height() ); + if( aScaledSize.Width() > aNewSize.Width() ) + fScale = double(aNewSize.Width())/double(aScaledSize.Width()); + } + aScaledSize.setWidth( tools::Long(aScaledSize.Width()*fScale) ); + aScaledSize.setHeight( tools::Long(aScaledSize.Height()*fScale) ); + + maPreviewSize = aScaledSize; + + // check and evtl. recreate preview bitmap + preparePreviewBitmap(); +} + +void PrintDialog::PrintPreviewWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + rRenderContext.Push(); + weld::SetPointFont(rRenderContext, rRenderContext.GetSettings().GetStyleSettings().GetLabelFont()); + + rRenderContext.SetBackground(Wallpaper(Application::GetSettings().GetStyleSettings().GetDialogColor())); + rRenderContext.Erase(); + + auto nTextHeight = rRenderContext.GetTextHeight(); + Size aSize(GetOutputSizePixel()); + Point aOffset((aSize.Width() - maPreviewSize.Width() + nTextHeight) / 2, + (aSize.Height() - maPreviewSize.Height() + nTextHeight) / 2); + + // horizontal line + { + auto nWidth = rRenderContext.GetTextWidth(maHorzText); + + auto nStart = aOffset.X() + (maPreviewSize.Width() - nWidth) / 2; + rRenderContext.DrawText(Point(nStart, aOffset.Y() - nTextHeight), maHorzText, 0, maHorzText.getLength()); + + DecorationView aDecoView(&rRenderContext); + auto nTop = aOffset.Y() - (nTextHeight / 2); + aDecoView.DrawSeparator(Point(aOffset.X(), nTop), Point(nStart - 2, nTop), false); + aDecoView.DrawSeparator(Point(nStart + nWidth + 2, nTop), Point(aOffset.X() + maPreviewSize.Width(), nTop), false); + } + + // vertical line + { + rRenderContext.Push(PushFlags::FONT); + vcl::Font aFont(rRenderContext.GetFont()); + aFont.SetOrientation(900_deg10); + rRenderContext.SetFont(aFont); + + auto nLeft = aOffset.X() - nTextHeight; + + auto nWidth = rRenderContext.GetTextWidth(maVertText); + auto nStart = aOffset.Y() + (maPreviewSize.Height() + nWidth) / 2; + + rRenderContext.DrawText(Point(nLeft, nStart), maVertText, 0, maVertText.getLength()); + + DecorationView aDecoView(&rRenderContext); + nLeft = aOffset.X() - (nTextHeight / 2); + aDecoView.DrawSeparator(Point(nLeft, aOffset.Y()), Point(nLeft, nStart - nWidth - 2), true); + aDecoView.DrawSeparator(Point(nLeft, nStart + 2), Point(nLeft, aOffset.Y() + maPreviewSize.Height()), true); + + rRenderContext.Pop(); + } + + if (!maReplacementString.isEmpty()) + { + // replacement is active + tools::Rectangle aTextRect(aOffset + Point(2, 2), Size(maPreviewSize.Width() - 4, maPreviewSize.Height() - 4)); + rRenderContext.DrawText(aTextRect, maReplacementString, + DrawTextFlags::Center | DrawTextFlags::VCenter | + DrawTextFlags::WordBreak | DrawTextFlags::MultiLine); + } + else + { + BitmapEx aPreviewBitmap(maPreviewBitmap); + + // This explicit force-to-scale allows us to get the + // mentioned best quality here. Unfortunately this is + // currently not sure when using just ::DrawBitmap with + // a defined size or ::DrawOutDev + aPreviewBitmap.Scale(maPreviewSize, BmpScaleFlag::BestQuality); + rRenderContext.DrawBitmapEx(aOffset, aPreviewBitmap); + } + + tools::Rectangle aFrameRect(aOffset + Point(-1, -1), Size(maPreviewSize.Width() + 2, maPreviewSize.Height() + 2)); + DecorationView aDecorationView(&rRenderContext); + aDecorationView.DrawFrame(aFrameRect, DrawFrameStyle::Group); + + rRenderContext.Pop(); +} + +bool PrintDialog::PrintPreviewWindow::Command( const CommandEvent& rEvt ) +{ + if( rEvt.GetCommand() == CommandEventId::Wheel ) + { + const CommandWheelData* pWheelData = rEvt.GetWheelData(); + if(pWheelData->GetDelta() > 0) + mpDialog->previewForward(); + else if (pWheelData->GetDelta() < 0) + mpDialog->previewBackward(); + return true; + } + return CustomWidgetController::Command(rEvt); +} + +void PrintDialog::PrintPreviewWindow::setPreview( const GDIMetaFile& i_rNewPreview, + const Size& i_rOrigSize, + std::u16string_view i_rPaperName, + const OUString& i_rReplacement, + sal_Int32 i_nDPIX, + sal_Int32 i_nDPIY, + bool i_bGreyscale + ) +{ + maMtf = i_rNewPreview; + mnDPIX = i_nDPIX; + mnDPIY = i_nDPIY; + maOrigSize = i_rOrigSize; + maReplacementString = i_rReplacement; + mbGreyscale = i_bGreyscale; + + // use correct measurements + const LocaleDataWrapper& rLocWrap(Application::GetSettings().GetLocaleDataWrapper()); + o3tl::Length eUnit = o3tl::Length::mm; + int nDigits = 0; + if( rLocWrap.getMeasurementSystemEnum() == MeasurementSystem::US ) + { + eUnit = o3tl::Length::in100; + nDigits = 2; + } + Size aLogicPaperSize(o3tl::convert(i_rOrigSize, o3tl::Length::mm100, eUnit)); + OUString aNumText( rLocWrap.getNum( aLogicPaperSize.Width(), nDigits ) ); + OUStringBuffer aBuf; + aBuf.append( aNumText + " " ); + aBuf.appendAscii( eUnit == o3tl::Length::mm ? "mm" : "in" ); + if( !i_rPaperName.empty() ) + { + aBuf.append( " (" ); + aBuf.append( i_rPaperName ); + aBuf.append( ')' ); + } + maHorzText = aBuf.makeStringAndClear(); + + aNumText = rLocWrap.getNum( aLogicPaperSize.Height(), nDigits ); + aBuf.append( aNumText + " " ); + aBuf.appendAscii( eUnit == o3tl::Length::mm ? "mm" : "in" ); + maVertText = aBuf.makeStringAndClear(); + + // We have a new Metafile and evtl. a new page, so we need to reset + // the PreviewBitmap to force new creation + maPreviewBitmap = Bitmap(); + + // sets/calculates e.g. maPreviewSize + // also triggers preparePreviewBitmap() + Resize(); + + Invalidate(); +} + +void PrintDialog::PrintPreviewWindow::preparePreviewBitmap() +{ + if(maPreviewSize.IsEmpty()) + { + // not yet fully initialized, no need to prepare anything + return; + } + + // define an allowed number of pixels, also see + // defaults for primitive renderers and similar. This + // might be centralized and made dependent of 32/64bit + const sal_uInt32 nMaxSquarePixels(500000); + + // check how big (squarePixels) the preview is currently (with + // max value of MaxSquarePixels) + const sal_uInt32 nCurrentSquarePixels( + std::min( + nMaxSquarePixels, + static_cast<sal_uInt32>(maPreviewBitmap.GetSizePixel().getWidth()) + * static_cast<sal_uInt32>(maPreviewBitmap.GetSizePixel().getHeight()))); + + // check how big (squarePixels) the preview needs to be (with + // max value of MaxSquarePixels) + const sal_uInt32 nRequiredSquarePixels( + std::min( + nMaxSquarePixels, + static_cast<sal_uInt32>(maPreviewSize.getWidth()) + * static_cast<sal_uInt32>(maPreviewSize.getHeight()))); + + // check if preview is big enough. Use a scaling value in + // the comparison to not get bigger at the last possible moment + // what may look awkward and pixelated (again). This means + // to use a percentage value - if we have at least + // that value of required pixels, we are good. + static const double fPreventAwkwardFactor(1.35); // 35% + if(nCurrentSquarePixels >= static_cast<sal_uInt32>(nRequiredSquarePixels * fPreventAwkwardFactor)) + { + // at this place we also could add a mechanism to let the preview + // bitmap 'shrink' again if it is currently 'too big' -> bigger + // than required. I think this is not necessary for now. + + // already sufficient, done. + return; + } + + // check if we have enough square pixels e.g for 8x8 pixels + if(nRequiredSquarePixels < 64) + { + // too small preview - let it empty + return; + } + + // Calculate nPlannedSquarePixels which is the required size + // expanded by a percentage (with max value of MaxSquarePixels) + static const double fExtraSpaceFactor(1.65); // 65% + const sal_uInt32 nPlannedSquarePixels( + std::min( + nMaxSquarePixels, + static_cast<sal_uInt32>(maPreviewSize.getWidth() * fExtraSpaceFactor) + * static_cast<sal_uInt32>(maPreviewSize.getHeight() * fExtraSpaceFactor))); + + // calculate back new width and height - it might have been + // truncated by MaxSquarePixels. + // We know that w*h == nPlannedSquarePixels and w/h == ratio + const double fRatio(static_cast<double>(maPreviewSize.getWidth()) / static_cast<double>(maPreviewSize.getHeight())); + const double fNewWidth(sqrt(static_cast<double>(nPlannedSquarePixels) * fRatio)); + const double fNewHeight(sqrt(static_cast<double>(nPlannedSquarePixels) / fRatio)); + const Size aScaledSize(basegfx::fround(fNewWidth), basegfx::fround(fNewHeight)); + + // check if this eventual maximum is already reached + // due to having hit the MaxSquarePixels. Due to using + // an integer AspectRatio, we cannot make a numeric exact + // comparison - we need to compare if we are close + const double fScaledSizeSquare(static_cast<double>(aScaledSize.getWidth() * aScaledSize.getHeight())); + const double fPreviewSizeSquare(static_cast<double>(maPreviewBitmap.GetSizePixel().getWidth() * maPreviewBitmap.GetSizePixel().getHeight())); + + // test as equal up to 0.1% (0.001) + if(fPreviewSizeSquare != 0.0 && fabs((fScaledSizeSquare / fPreviewSizeSquare) - 1.0) < 0.001) + { + // maximum is reached, avoid bigger scaling + return; + } + + // create temporary VDev with requested Size and DPI. + // CAUTION: DPI *is* important here - it DIFFERS from 75x75, usually 600x600 is used + ScopedVclPtrInstance<VirtualDevice> pPrerenderVDev(*Application::GetDefaultDevice()); + pPrerenderVDev->SetOutputSizePixel(aScaledSize, false); + pPrerenderVDev->SetReferenceDevice( mnDPIX, mnDPIY ); + + // calculate needed Scale for Metafile (using Size and DPI from VDev) + Size aLogicSize( pPrerenderVDev->PixelToLogic( pPrerenderVDev->GetOutputSizePixel(), MapMode( MapUnit::Map100thMM ) ) ); + Size aOrigSize( maOrigSize ); + if( aOrigSize.Width() < 1 ) + aOrigSize.setWidth( aLogicSize.Width() ); + if( aOrigSize.Height() < 1 ) + aOrigSize.setHeight( aLogicSize.Height() ); + double fScale = double(aLogicSize.Width())/double(aOrigSize.Width()); + + // tdf#141761 + // The display quality of the Preview is pretty ugly when + // FormControls are used. I made a deep-dive why this happens, + // and in principle the reason is the Mteafile::Scale used + // below. Since Metafile actions are integer, that floating point + // scale leads to rounding errors that make the lines painting + // the FormControls disappear in the surrounding ClipRegions. + // That Scale cannot be avoided since the Metafile contains it's + // own SetMapMode commands which *will* be executed on ::Play, + // so the ::Scale is the only possibility fr Metafile currently: + // Giving a Size as parameter in ::Play will *not* work due to + // the relativeMapMode that gets created will fail on + // ::SetMapMode actions in the Metafile - and FormControls DO + // use ::SetMapMode(MapPixel). + // This can only be solved better in the future using Primitives + // which would allow any scale by embedding to a Transformation, + // but that would be a bigger rework. + // Until then, use this little 'trick' to improve quality. + // It uses the fact to empirically having tested that the quality + // gets really bad for FormControls starting by a scale factor + // smaller than 0.2 - that makes the ClipRegion overlap start. + // So - for now - try not to go below that. + static const double fMinimumScale(0.2); + double fFactor(0.0); + if(fScale < fMinimumScale) + { + fFactor = fMinimumScale / fScale; + fScale = fMinimumScale; + + double fWidth(aScaledSize.getWidth() * fFactor); + double fHeight(aScaledSize.getHeight() * fFactor); + const double fNewNeededPixels(fWidth * fHeight); + + // to not risk using too big bitmaps and running into + // memory problems, still limit to a useful factor is + // necessary, also empirically estimated to + // avoid the quality from collapsing (using a direct + // in-between , ceil'd result) + static const double fMaximumQualitySquare(1396221.0); + + if(fNewNeededPixels > fMaximumQualitySquare) + { + const double fCorrection(fMaximumQualitySquare/fNewNeededPixels); + fWidth *= fCorrection; + fHeight *= fCorrection; + fScale *= fCorrection; + } + + const Size aScaledSize2(basegfx::fround(fWidth), basegfx::fround(fHeight)); + pPrerenderVDev->SetOutputSizePixel(aScaledSize2, false); + aLogicSize = pPrerenderVDev->PixelToLogic( aScaledSize2, MapMode( MapUnit::Map100thMM ) ); + } + + pPrerenderVDev->EnableOutput(); + pPrerenderVDev->SetBackground( Wallpaper(COL_WHITE) ); + pPrerenderVDev->Erase(); + pPrerenderVDev->SetMapMode(MapMode(MapUnit::Map100thMM)); + if( mbGreyscale ) + pPrerenderVDev->SetDrawMode( pPrerenderVDev->GetDrawMode() | + ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText | + DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) ); + + // Copy, Scale and Paint Metafile + GDIMetaFile aMtf( maMtf ); + aMtf.WindStart(); + aMtf.Scale( fScale, fScale ); + aMtf.WindStart(); + aMtf.Play(*pPrerenderVDev, Point(0, 0), aLogicSize); + + pPrerenderVDev->SetMapMode(MapMode(MapUnit::MapPixel)); + maPreviewBitmap = pPrerenderVDev->GetBitmapEx(Point(0, 0), pPrerenderVDev->GetOutputSizePixel()); + + if(0.0 != fFactor) + { + // Correct to needed size, BmpScaleFlag::Interpolate is acceptable, + // but BmpScaleFlag::BestQuality is just better. In case of time + // constraints, change to Interpolate would be possible + maPreviewBitmap.Scale(aScaledSize, BmpScaleFlag::BestQuality); + } +} + +PrintDialog::ShowNupOrderWindow::ShowNupOrderWindow() + : mnOrderMode( NupOrderType::LRTB ) + , mnRows( 1 ) + , mnColumns( 1 ) +{ +} + +void PrintDialog::ShowNupOrderWindow::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + Size aSize(70, 70); + pDrawingArea->set_size_request(aSize.Width(), aSize.Height()); + CustomWidgetController::SetDrawingArea(pDrawingArea); + SetOutputSizePixel(aSize); +} + +void PrintDialog::ShowNupOrderWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*i_rRect*/) +{ + rRenderContext.SetMapMode(MapMode(MapUnit::MapPixel)); + rRenderContext.SetTextColor(rRenderContext.GetSettings().GetStyleSettings().GetFieldTextColor()); + rRenderContext.SetBackground(Wallpaper(Application::GetSettings().GetStyleSettings().GetFieldColor())); + rRenderContext.Erase(); + + int nPages = mnRows * mnColumns; + Font aFont(rRenderContext.GetSettings().GetStyleSettings().GetFieldFont()); + aFont.SetFontSize(Size(0, 24)); + rRenderContext.SetFont(aFont); + Size aSampleTextSize(rRenderContext.GetTextWidth(OUString::number(nPages + 1)), rRenderContext.GetTextHeight()); + Size aOutSize(GetOutputSizePixel()); + Size aSubSize(aOutSize.Width() / mnColumns, aOutSize.Height() / mnRows); + // calculate font size: shrink the sample text so it fits + double fX = double(aSubSize.Width()) / double(aSampleTextSize.Width()); + double fY = double(aSubSize.Height()) / double(aSampleTextSize.Height()); + double fScale = (fX < fY) ? fX : fY; + tools::Long nFontHeight = tools::Long(24.0 * fScale) - 3; + if (nFontHeight < 5) + nFontHeight = 5; + aFont.SetFontSize(Size( 0, nFontHeight)); + rRenderContext.SetFont(aFont); + tools::Long nTextHeight = rRenderContext.GetTextHeight(); + for (int i = 0; i < nPages; i++) + { + OUString aPageText(OUString::number(i + 1)); + int nX = 0, nY = 0; + switch (mnOrderMode) + { + case NupOrderType::LRTB: + nX = (i % mnColumns); + nY = (i / mnColumns); + break; + case NupOrderType::TBLR: + nX = (i / mnRows); + nY = (i % mnRows); + break; + case NupOrderType::RLTB: + nX = mnColumns - 1 - (i % mnColumns); + nY = (i / mnColumns); + break; + case NupOrderType::TBRL: + nX = mnColumns - 1 - (i / mnRows); + nY = (i % mnRows); + break; + } + Size aTextSize(rRenderContext.GetTextWidth(aPageText), nTextHeight); + int nDeltaX = (aSubSize.Width() - aTextSize.Width()) / 2; + int nDeltaY = (aSubSize.Height() - aTextSize.Height()) / 2; + rRenderContext.DrawText(Point(nX * aSubSize.Width() + nDeltaX, + nY * aSubSize.Height() + nDeltaY), aPageText); + } + DecorationView aDecorationView(&rRenderContext); + aDecorationView.DrawFrame(tools::Rectangle(Point(0, 0), aOutSize), DrawFrameStyle::Group); +} + +Size const & PrintDialog::getJobPageSize() +{ + if( maFirstPageSize.IsEmpty() ) + { + maFirstPageSize = maNupPortraitSize; + GDIMetaFile aMtf; + if( maPController->getPageCountProtected() > 0 ) + { + PrinterController::PageSize aPageSize = maPController->getPageFile( 0, aMtf, true ); + maFirstPageSize = aPageSize.aSize; + } + } + return maFirstPageSize; +} + +PrintDialog::PrintDialog(weld::Window* i_pWindow, const std::shared_ptr<PrinterController>& i_rController) + : GenericDialogController(i_pWindow, "vcl/ui/printdialog.ui", "PrintDialog") + , maPController( i_rController ) + , mxTabCtrl(m_xBuilder->weld_notebook("tabcontrol")) + , mxScrolledWindow(m_xBuilder->weld_scrolled_window("scrolledwindow")) + , mxPageLayoutFrame(m_xBuilder->weld_frame("layoutframe")) + , mxPrinters(m_xBuilder->weld_combo_box("printersbox")) + , mxStatusTxt(m_xBuilder->weld_label("status")) + , mxSetupButton(m_xBuilder->weld_button("setup")) + , mxCopyCountField(m_xBuilder->weld_spin_button("copycount")) + , mxCollateBox(m_xBuilder->weld_check_button("collate")) + , mxCollateImage(m_xBuilder->weld_image("collateimage")) + , mxPageRangeEdit(m_xBuilder->weld_entry("pagerange")) + , mxPageRangesRadioButton(m_xBuilder->weld_radio_button("rbRangePages")) + , mxPaperSidesBox(m_xBuilder->weld_combo_box("sidesbox")) + , mxSingleJobsBox(m_xBuilder->weld_check_button("singlejobs")) + , mxReverseOrderBox(m_xBuilder->weld_check_button("reverseorder")) + , mxOKButton(m_xBuilder->weld_button("ok")) + , mxCancelButton(m_xBuilder->weld_button("cancel")) + , mxHelpButton(m_xBuilder->weld_button("help")) + , mxMoreOptionsBtn(m_xBuilder->weld_button("moreoptionsbtn")) + , mxBackwardBtn(m_xBuilder->weld_button("backward")) + , mxForwardBtn(m_xBuilder->weld_button("forward")) + , mxFirstBtn(m_xBuilder->weld_button("btnFirst")) + , mxLastBtn(m_xBuilder->weld_button("btnLast")) + , mxPreviewBox(m_xBuilder->weld_check_button("previewbox")) + , mxNumPagesText(m_xBuilder->weld_label("totalnumpages")) + , mxPreview(new PrintPreviewWindow(this)) + , mxPreviewWindow(new weld::CustomWeld(*m_xBuilder, "preview", *mxPreview)) + , mxPageEdit(m_xBuilder->weld_entry("pageedit")) + , mxPagesBtn(m_xBuilder->weld_radio_button("pagespersheetbtn")) + , mxBrochureBtn(m_xBuilder->weld_radio_button("brochure")) + , mxPagesBoxTitleTxt(m_xBuilder->weld_label("pagespersheettxt")) + , mxNupPagesBox(m_xBuilder->weld_combo_box("pagespersheetbox")) + , mxNupNumPagesTxt(m_xBuilder->weld_label("pagestxt")) + , mxNupColEdt(m_xBuilder->weld_spin_button("pagecols")) + , mxNupTimesTxt(m_xBuilder->weld_label("by")) + , mxNupRowsEdt(m_xBuilder->weld_spin_button("pagerows")) + , mxPageMarginTxt1(m_xBuilder->weld_label("pagemargintxt1")) + , mxPageMarginEdt(m_xBuilder->weld_metric_spin_button("pagemarginsb", FieldUnit::MM)) + , mxPageMarginTxt2(m_xBuilder->weld_label("pagemargintxt2")) + , mxSheetMarginTxt1(m_xBuilder->weld_label("sheetmargintxt1")) + , mxSheetMarginEdt(m_xBuilder->weld_metric_spin_button("sheetmarginsb", FieldUnit::MM)) + , mxSheetMarginTxt2(m_xBuilder->weld_label("sheetmargintxt2")) + , mxPaperSizeBox(m_xBuilder->weld_combo_box("papersizebox")) + , mxOrientationBox(m_xBuilder->weld_combo_box("pageorientationbox")) + , mxNupOrderTxt(m_xBuilder->weld_label("labelorder")) + , mxNupOrderBox(m_xBuilder->weld_combo_box("orderbox")) + , mxNupOrder(new ShowNupOrderWindow) + , mxNupOrderWin(new weld::CustomWeld(*m_xBuilder, "orderpreview", *mxNupOrder)) + , mxBorderCB(m_xBuilder->weld_check_button("bordercb")) + , mxRangeExpander(m_xBuilder->weld_expander("exRangeExpander")) + , mxLayoutExpander(m_xBuilder->weld_expander("exLayoutExpander")) + , mxCustom(m_xBuilder->weld_widget("customcontents")) + , maPrintToFileText( VclResId( SV_PRINT_TOFILE_TXT ) ) + , maDefPrtText( VclResId( SV_PRINT_DEFPRT_TXT ) ) + , maNoPageStr( VclResId( SV_PRINT_NOPAGES ) ) + , maNoPreviewStr( VclResId( SV_PRINT_NOPREVIEW ) ) + , mnCurPage( 0 ) + , mnCachedPages( 0 ) + , mbCollateAlwaysOff(false) + , mbShowLayoutFrame( true ) + , maUpdatePreviewIdle("Print Dialog Update Preview Idle") + , maUpdatePreviewNoCacheIdle("Print Dialog Update Preview (no cache) Idle") +{ + // save printbutton text, gets exchanged occasionally with print to file + maPrintText = mxOKButton->get_label(); + + maPageStr = mxNumPagesText->get_label(); + + Printer::updatePrinters(); + + mxPrinters->append_text(maPrintToFileText); + // fill printer listbox + std::vector< OUString > rQueues( Printer::GetPrinterQueues() ); + std::sort( rQueues.begin(), rQueues.end(), lcl_ListBoxCompare ); + for( const auto& rQueue : rQueues ) + { + mxPrinters->append_text(rQueue); + } + // select current printer + if (mxPrinters->find_text(maPController->getPrinter()->GetName()) != -1) + mxPrinters->set_active_text(maPController->getPrinter()->GetName()); + else + { + // fall back to last printer + SettingsConfigItem* pItem = SettingsConfigItem::get(); + OUString aValue( pItem->getValue( "PrintDialog", + "LastPrinter" ) ); + if (mxPrinters->find_text(aValue) != -1) + { + mxPrinters->set_active_text(aValue); + maPController->setPrinter( VclPtrInstance<Printer>( aValue ) ); + } + else + { + // fall back to default printer + mxPrinters->set_active_text(Printer::GetDefaultPrinterName()); + maPController->setPrinter( VclPtrInstance<Printer>( Printer::GetDefaultPrinterName() ) ); + } + } + + // not printing to file + maPController->resetPrinterOptions( false ); + + // update the text fields for the printer + updatePrinterText(); + + // setup dependencies + checkControlDependencies(); + + // setup paper sides box + setupPaperSidesBox(); + + // set initial focus to "Number of copies" + mxCopyCountField->grab_focus(); + mxCopyCountField->select_region(0, -1); + + // setup sizes for N-Up + Size aNupSize( maPController->getPrinter()->PixelToLogic( + maPController->getPrinter()->GetPaperSizePixel(), MapMode( MapUnit::Map100thMM ) ) ); + if( maPController->getPrinter()->GetOrientation() == Orientation::Landscape ) + { + maNupLandscapeSize = aNupSize; + // coverity[swapped_arguments : FALSE] - this is in the correct order + maNupPortraitSize = Size( aNupSize.Height(), aNupSize.Width() ); + } + else + { + maNupPortraitSize = aNupSize; + // coverity[swapped_arguments : FALSE] - this is in the correct order + maNupLandscapeSize = Size( aNupSize.Height(), aNupSize.Width() ); + } + + maUpdatePreviewIdle.SetPriority(TaskPriority::POST_PAINT); + maUpdatePreviewIdle.SetInvokeHandler(LINK( this, PrintDialog, updatePreviewIdle)); + maUpdatePreviewNoCacheIdle.SetPriority(TaskPriority::POST_PAINT); + maUpdatePreviewNoCacheIdle.SetInvokeHandler(LINK(this, PrintDialog, updatePreviewNoCacheIdle)); + + initFromMultiPageSetup( maPController->getMultipage() ); + + // setup optional UI options set by application + setupOptionalUI(); + + // hide layout frame if unwanted + mxPageLayoutFrame->set_visible(mbShowLayoutFrame); + + // restore settings from last run + readFromSettings(); + + // setup click hdl + mxOKButton->connect_clicked(LINK(this, PrintDialog, ClickHdl)); + mxCancelButton->connect_clicked(LINK(this, PrintDialog, ClickHdl)); + mxHelpButton->connect_clicked(LINK(this, PrintDialog, ClickHdl)); + mxSetupButton->connect_clicked( LINK( this, PrintDialog, ClickHdl ) ); + mxBackwardBtn->connect_clicked(LINK(this, PrintDialog, ClickHdl)); + mxForwardBtn->connect_clicked(LINK(this, PrintDialog, ClickHdl)); + mxFirstBtn->connect_clicked(LINK(this, PrintDialog, ClickHdl)); + mxLastBtn->connect_clicked( LINK( this, PrintDialog, ClickHdl ) ); + + // setup toggle hdl + mxReverseOrderBox->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) ); + mxCollateBox->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) ); + mxSingleJobsBox->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) ); + mxBrochureBtn->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) ); + mxPreviewBox->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) ); + mxBorderCB->connect_toggled( LINK( this, PrintDialog, ToggleHdl ) ); + + // setup select hdl + mxPrinters->connect_changed( LINK( this, PrintDialog, SelectHdl ) ); + mxPaperSidesBox->connect_changed( LINK( this, PrintDialog, SelectHdl ) ); + mxNupPagesBox->connect_changed( LINK( this, PrintDialog, SelectHdl ) ); + mxOrientationBox->connect_changed( LINK( this, PrintDialog, SelectHdl ) ); + mxNupOrderBox->connect_changed( LINK( this, PrintDialog, SelectHdl ) ); + mxPaperSizeBox->connect_changed( LINK( this, PrintDialog, SelectHdl ) ); + + // setup modify hdl + mxPageEdit->connect_activate( LINK( this, PrintDialog, ActivateHdl ) ); + mxPageEdit->connect_focus_out( LINK( this, PrintDialog, FocusOutHdl ) ); + mxCopyCountField->connect_value_changed( LINK( this, PrintDialog, SpinModifyHdl ) ); + mxNupColEdt->connect_value_changed( LINK( this, PrintDialog, SpinModifyHdl ) ); + mxNupRowsEdt->connect_value_changed( LINK( this, PrintDialog, SpinModifyHdl ) ); + mxPageMarginEdt->connect_value_changed( LINK( this, PrintDialog, MetricSpinModifyHdl ) ); + mxSheetMarginEdt->connect_value_changed( LINK( this, PrintDialog, MetricSpinModifyHdl ) ); + + updateNupFromPages(); + + // tdf#129180 Delay setting the default value in the Paper Size list + // set paper sizes listbox + setPaperSizes(); + + mxRangeExpander->set_expanded( + officecfg::Office::Common::Print::Dialog::RangeSectionExpanded::get()); + mxLayoutExpander->set_expanded( + officecfg::Office::Common::Print::Dialog::LayoutSectionExpanded::get()); + + // lock the dialog height, regardless of later expander state + mxScrolledWindow->set_size_request( + mxScrolledWindow->get_preferred_size().Width() + mxScrolledWindow->get_scroll_thickness(), + 450); + + m_xDialog->set_centered_on_parent(true); +} + +PrintDialog::~PrintDialog() +{ + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Print::Dialog::RangeSectionExpanded::set(mxRangeExpander->get_expanded(), batch); + officecfg::Office::Common::Print::Dialog::LayoutSectionExpanded::set(mxLayoutExpander->get_expanded(), batch); + batch->commit(); +} + +void PrintDialog::setupPaperSidesBox() +{ + DuplexMode eDuplex = maPController->getPrinter()->GetDuplexMode(); + + if ( eDuplex == DuplexMode::Unknown || isPrintToFile() ) + { + mxPaperSidesBox->set_active( 0 ); + mxPaperSidesBox->set_sensitive( false ); + } + else + { + mxPaperSidesBox->set_active( static_cast<sal_Int32>(eDuplex) - 1 ); + mxPaperSidesBox->set_sensitive( true ); + } +} + +void PrintDialog::storeToSettings() +{ + SettingsConfigItem* pItem = SettingsConfigItem::get(); + + pItem->setValue( "PrintDialog", + "LastPrinter", + isPrintToFile() ? Printer::GetDefaultPrinterName() + : mxPrinters->get_active_text() ); + + pItem->setValue( "PrintDialog", + "LastPage", + mxTabCtrl->get_tab_label_text(mxTabCtrl->get_current_page_ident())); + + pItem->setValue( "PrintDialog", + "WindowState", + OStringToOUString(m_xDialog->get_window_state(WindowStateMask::All), RTL_TEXTENCODING_UTF8) ); + + pItem->setValue( "PrintDialog", + "CopyCount", + mxCopyCountField->get_text() ); + + pItem->setValue( "PrintDialog", + "Collate", + mxCollateBox->get_active() ? OUString("true") : + OUString("false") ); + + pItem->setValue( "PrintDialog", + "CollateSingleJobs", + mxSingleJobsBox->get_active() ? OUString("true") : + OUString("false") ); + + pItem->setValue( "PrintDialog", + "HasPreview", + hasPreview() ? OUString("true") : + OUString("false") ); + + pItem->Commit(); +} + +void PrintDialog::readFromSettings() +{ + SettingsConfigItem* pItem = SettingsConfigItem::get(); + + // read last selected tab page; if it exists, activate it + OUString aValue = pItem->getValue( "PrintDialog", + "LastPage" ); + sal_uInt16 nCount = mxTabCtrl->get_n_pages(); + for (sal_uInt16 i = 0; i < nCount; ++i) + { + OString sPageId = mxTabCtrl->get_page_ident(i); + if (aValue == mxTabCtrl->get_tab_label_text(sPageId)) + { + mxTabCtrl->set_current_page(sPageId); + break; + } + } + + // persistent window state + aValue = pItem->getValue( "PrintDialog", + "WindowState" ); + if (!aValue.isEmpty()) + m_xDialog->set_window_state(OUStringToOString(aValue, RTL_TEXTENCODING_UTF8)); + + // collate + aValue = pItem->getValue( "PrintDialog", + "CollateBox" ); + if( aValue.equalsIgnoreAsciiCase("alwaysoff") ) + { + mbCollateAlwaysOff = true; + mxCollateBox->set_active( false ); + mxCollateBox->set_sensitive( false ); + } + else + { + mbCollateAlwaysOff = false; + aValue = pItem->getValue( "PrintDialog", + "Collate" ); + mxCollateBox->set_active( aValue.equalsIgnoreAsciiCase("true") ); + } + + // collate single jobs + aValue = pItem->getValue( "PrintDialog", + "CollateSingleJobs" ); + mxSingleJobsBox->set_active(aValue.equalsIgnoreAsciiCase("true")); + + // preview box + aValue = pItem->getValue( "PrintDialog", + "HasPreview" ); + if ( aValue.equalsIgnoreAsciiCase("false") ) + mxPreviewBox->set_active( false ); + else + mxPreviewBox->set_active( true ); + +} + +void PrintDialog::setPaperSizes() +{ + mxPaperSizeBox->clear(); + + VclPtr<Printer> aPrt( maPController->getPrinter() ); + mePaper = aPrt->GetPaper(); + + if ( isPrintToFile() ) + { + mxPaperSizeBox->set_sensitive( false ); + } + else + { + Size aSizeOfPaper = aPrt->GetSizeOfPaper(); + PaperInfo aPaperInfo(aSizeOfPaper.getWidth(), aSizeOfPaper.getHeight()); + const LocaleDataWrapper& rLocWrap(Application::GetSettings().GetLocaleDataWrapper()); + o3tl::Length eUnit = o3tl::Length::mm; + int nDigits = 0; + if( rLocWrap.getMeasurementSystemEnum() == MeasurementSystem::US ) + { + eUnit = o3tl::Length::in100; + nDigits = 2; + } + for (int nPaper = 0; nPaper < aPrt->GetPaperInfoCount(); nPaper++) + { + PaperInfo aInfo = aPrt->GetPaperInfo( nPaper ); + aInfo.doSloppyFit(true); + Paper ePaper = aInfo.getPaper(); + + Size aSize = aPrt->GetPaperSize( nPaper ); + Size aLogicPaperSize( o3tl::convert(aSize, o3tl::Length::mm100, eUnit) ); + + OUString aWidth( rLocWrap.getNum( aLogicPaperSize.Width(), nDigits ) ); + OUString aHeight( rLocWrap.getNum( aLogicPaperSize.Height(), nDigits ) ); + OUString aUnit = eUnit == o3tl::Length::mm ? OUString("mm") : OUString("in"); + OUString aPaperName; + + // Paper sizes that we don't know of but the system printer driver lists are not "User + // Defined". Displaying them as such is just confusing. + if (ePaper != PAPER_USER) + aPaperName = Printer::GetPaperName( ePaper ) + " "; + + aPaperName += aWidth + aUnit + " x " + aHeight + aUnit; + + mxPaperSizeBox->append_text(aPaperName); + + if ( (ePaper != PAPER_USER && ePaper == mePaper) || + (ePaper == PAPER_USER && aInfo.sloppyEqual(aPaperInfo) ) ) + mxPaperSizeBox->set_active( nPaper ); + } + + mxPaperSizeBox->set_sensitive( true ); + } +} + +void PrintDialog::updatePrinterText() +{ + const OUString aDefPrt( Printer::GetDefaultPrinterName() ); + const QueueInfo* pInfo = Printer::GetQueueInfo( mxPrinters->get_active_text(), true ); + if( pInfo ) + { + // FIXME: status text + OUString aStatus; + if( aDefPrt == pInfo->GetPrinterName() ) + aStatus = maDefPrtText; + mxStatusTxt->set_label( aStatus ); + } + else + { + mxStatusTxt->set_label( OUString() ); + } +} + +void PrintDialog::setPreviewText() +{ + OUString aNewText( maPageStr.replaceFirst( "%n", OUString::number( mnCachedPages ) ) ); + mxNumPagesText->set_label( aNewText ); +} + +IMPL_LINK_NOARG(PrintDialog, updatePreviewIdle, Timer*, void) +{ + preparePreview(true); +} + +IMPL_LINK_NOARG(PrintDialog, updatePreviewNoCacheIdle, Timer*, void) +{ + preparePreview(false); +} + +void PrintDialog::preparePreview( bool i_bMayUseCache ) +{ + VclPtr<Printer> aPrt( maPController->getPrinter() ); + Size aCurPageSize = aPrt->PixelToLogic( aPrt->GetPaperSizePixel(), MapMode( MapUnit::Map100thMM ) ); + // tdf#123076 Get paper size for the preview top label + mePaper = aPrt->GetPaper(); + GDIMetaFile aMtf; + + // page range may have changed depending on options + sal_Int32 nPages = maPController->getFilteredPageCount(); + mnCachedPages = nPages; + + setPreviewText(); + + if ( !hasPreview() ) + { + mxPreview->setPreview( aMtf, aCurPageSize, + Printer::GetPaperName( mePaper ), + maNoPreviewStr, + aPrt->GetDPIX(), aPrt->GetDPIY(), + aPrt->GetPrinterOptions().IsConvertToGreyscales() + ); + + mxForwardBtn->set_sensitive( false ); + mxBackwardBtn->set_sensitive( false ); + mxFirstBtn->set_sensitive( false ); + mxLastBtn->set_sensitive( false ); + + mxPageEdit->set_sensitive( false ); + + return; + } + + if( mnCurPage >= nPages ) + mnCurPage = nPages-1; + if( mnCurPage < 0 ) + mnCurPage = 0; + mxPageEdit->set_text(OUString::number(mnCurPage + 1)); + + if( nPages > 0 ) + { + PrinterController::PageSize aPageSize = + maPController->getFilteredPageFile( mnCurPage, aMtf, i_bMayUseCache ); + aCurPageSize = aPrt->PixelToLogic(aPrt->GetPaperSizePixel(), MapMode(MapUnit::Map100thMM)); + if( ! aPageSize.bFullPaper ) + { + const MapMode aMapMode( MapUnit::Map100thMM ); + Point aOff( aPrt->PixelToLogic( aPrt->GetPageOffsetPixel(), aMapMode ) ); + aMtf.Move( aOff.X(), aOff.Y() ); + } + // tdf#150561: page size may have changed so sync mePaper with it + mePaper = aPrt->GetPaper(); + } + + mxPreview->setPreview( aMtf, aCurPageSize, + Printer::GetPaperName( mePaper ), + nPages > 0 ? OUString() : maNoPageStr, + aPrt->GetDPIX(), aPrt->GetDPIY(), + aPrt->GetPrinterOptions().IsConvertToGreyscales() + ); + + mxForwardBtn->set_sensitive( mnCurPage < nPages-1 ); + mxBackwardBtn->set_sensitive( mnCurPage != 0 ); + mxFirstBtn->set_sensitive( mnCurPage != 0 ); + mxLastBtn->set_sensitive( mnCurPage < nPages-1 ); + mxPageEdit->set_sensitive( nPages > 1 ); +} + +void PrintDialog::updateOrientationBox( const bool bAutomatic ) +{ + if ( !bAutomatic ) + { + Orientation eOrientation = maPController->getPrinter()->GetOrientation(); + mxOrientationBox->set_active( static_cast<sal_Int32>(eOrientation) + 1 ); + } + else if ( hasOrientationChanged() ) + { + mxOrientationBox->set_active( ORIENTATION_AUTOMATIC ); + } +} + +bool PrintDialog::hasOrientationChanged() const +{ + const int nOrientation = mxOrientationBox->get_active(); + const Orientation eOrientation = maPController->getPrinter()->GetOrientation(); + + return (nOrientation == ORIENTATION_LANDSCAPE && eOrientation == Orientation::Portrait) + || (nOrientation == ORIENTATION_PORTRAIT && eOrientation == Orientation::Landscape); +} + +// Always use this function to set paper orientation to make sure everything behaves well +void PrintDialog::setPaperOrientation( Orientation eOrientation, bool fromUser ) +{ + VclPtr<Printer> aPrt( maPController->getPrinter() ); + aPrt->SetOrientation( eOrientation ); + maPController->setOrientationFromUser( eOrientation, fromUser ); +} + +void PrintDialog::checkControlDependencies() +{ + if (mxCopyCountField->get_value() > 1) + { + mxCollateBox->set_sensitive( !mbCollateAlwaysOff ); + mxSingleJobsBox->set_sensitive( mxCollateBox->get_active() ); + } + else + { + mxCollateBox->set_sensitive( false ); + mxSingleJobsBox->set_sensitive( false ); + } + + OUString aImg(mxCollateBox->get_active() ? OUString(SV_PRINT_COLLATE_BMP) : OUString(SV_PRINT_NOCOLLATE_BMP)); + + mxCollateImage->set_from_icon_name(aImg); + + // enable setup button only for printers that can be setup + bool bHaveSetup = maPController->getPrinter()->HasSupport( PrinterSupport::SetupDialog ); + mxSetupButton->set_sensitive(bHaveSetup); +} + +void PrintDialog::checkOptionalControlDependencies() +{ + for( const auto& rEntry : maControlToPropertyMap ) + { + bool bShouldbeEnabled = maPController->isUIOptionEnabled( rEntry.second ); + + if (bShouldbeEnabled && dynamic_cast<weld::RadioButton*>(rEntry.first)) + { + auto r_it = maControlToNumValMap.find( rEntry.first ); + if( r_it != maControlToNumValMap.end() ) + { + bShouldbeEnabled = maPController->isUIChoiceEnabled( rEntry.second, r_it->second ); + } + } + + bool bIsEnabled = rEntry.first->get_sensitive(); + // Enable does not do a change check first, so can be less cheap than expected + if (bShouldbeEnabled != bIsEnabled) + rEntry.first->set_sensitive( bShouldbeEnabled ); + } +} + +void PrintDialog::initFromMultiPageSetup( const vcl::PrinterController::MultiPageSetup& i_rMPS ) +{ + mxNupOrderWin->show(); + mxPagesBtn->set_active(true); + mxBrochureBtn->hide(); + + // setup field units for metric fields + const LocaleDataWrapper& rLocWrap(Application::GetSettings().GetLocaleDataWrapper()); + FieldUnit eUnit = FieldUnit::MM; + sal_uInt16 nDigits = 0; + if( rLocWrap.getMeasurementSystemEnum() == MeasurementSystem::US ) + { + eUnit = FieldUnit::INCH; + nDigits = 2; + } + // set units + mxPageMarginEdt->set_unit( eUnit ); + mxSheetMarginEdt->set_unit( eUnit ); + + // set precision + mxPageMarginEdt->set_digits( nDigits ); + mxSheetMarginEdt->set_digits( nDigits ); + + mxSheetMarginEdt->set_value( mxSheetMarginEdt->normalize( i_rMPS.nLeftMargin ), FieldUnit::MM_100TH ); + mxPageMarginEdt->set_value( mxPageMarginEdt->normalize( i_rMPS.nHorizontalSpacing ), FieldUnit::MM_100TH ); + mxBorderCB->set_active( i_rMPS.bDrawBorder ); + mxNupRowsEdt->set_value( i_rMPS.nRows ); + mxNupColEdt->set_value( i_rMPS.nColumns ); + mxNupOrderBox->set_active( static_cast<sal_Int32>(i_rMPS.nOrder) ); + if( i_rMPS.nRows != 1 || i_rMPS.nColumns != 1 ) + { + mxNupPagesBox->set_active( mxNupPagesBox->get_count()-1 ); + showAdvancedControls( true ); + mxNupOrder->setValues( i_rMPS.nOrder, i_rMPS.nColumns, i_rMPS.nRows ); + } +} + +void PrintDialog::updateNup( bool i_bMayUseCache ) +{ + int nRows = mxNupRowsEdt->get_value(); + int nCols = mxNupColEdt->get_value(); + tools::Long nPageMargin = mxPageMarginEdt->denormalize(mxPageMarginEdt->get_value( FieldUnit::MM_100TH )); + tools::Long nSheetMargin = mxSheetMarginEdt->denormalize(mxSheetMarginEdt->get_value( FieldUnit::MM_100TH )); + + PrinterController::MultiPageSetup aMPS; + aMPS.nRows = nRows; + aMPS.nColumns = nCols; + aMPS.nLeftMargin = + aMPS.nTopMargin = + aMPS.nRightMargin = + aMPS.nBottomMargin = nSheetMargin; + + aMPS.nHorizontalSpacing = + aMPS.nVerticalSpacing = nPageMargin; + + aMPS.bDrawBorder = mxBorderCB->get_active(); + + aMPS.nOrder = static_cast<NupOrderType>(mxNupOrderBox->get_active()); + + int nOrientationMode = mxOrientationBox->get_active(); + if( nOrientationMode == ORIENTATION_LANDSCAPE ) + aMPS.aPaperSize = maNupLandscapeSize; + else if( nOrientationMode == ORIENTATION_PORTRAIT ) + aMPS.aPaperSize = maNupPortraitSize; + else // automatic mode + { + // get size of first real page to see if it is portrait or landscape + // we assume same page sizes for all the pages for this + Size aPageSize = getJobPageSize(); + + Size aMultiSize( aPageSize.Width() * nCols, aPageSize.Height() * nRows ); + if( aMultiSize.Width() > aMultiSize.Height() ) // fits better on landscape + { + aMPS.aPaperSize = maNupLandscapeSize; + setPaperOrientation( Orientation::Landscape, false ); + } + else + { + aMPS.aPaperSize = maNupPortraitSize; + setPaperOrientation( Orientation::Portrait, false ); + } + } + + maPController->setMultipage( aMPS ); + + mxNupOrder->setValues( aMPS.nOrder, nCols, nRows ); + + if (i_bMayUseCache) + maUpdatePreviewIdle.Start(); + else + maUpdatePreviewNoCacheIdle.Start(); +} + +void PrintDialog::updateNupFromPages( bool i_bMayUseCache ) +{ + int nPages = mxNupPagesBox->get_active_id().toInt32(); + int nRows = mxNupRowsEdt->get_value(); + int nCols = mxNupColEdt->get_value(); + tools::Long nPageMargin = mxPageMarginEdt->denormalize(mxPageMarginEdt->get_value( FieldUnit::MM_100TH )); + tools::Long nSheetMargin = mxSheetMarginEdt->denormalize(mxSheetMarginEdt->get_value( FieldUnit::MM_100TH )); + bool bCustom = false; + + if( nPages == 1 ) + { + nRows = nCols = 1; + nSheetMargin = 0; + nPageMargin = 0; + } + else if( nPages == 2 || nPages == 4 || nPages == 6 || nPages == 9 || nPages == 16 ) + { + Size aJobPageSize( getJobPageSize() ); + bool bPortrait = aJobPageSize.Width() < aJobPageSize.Height(); + if( nPages == 2 ) + { + if( bPortrait ) + { + nRows = 1; + nCols = 2; + } + else + { + nRows = 2; + nCols = 1; + } + } + else if( nPages == 4 ) + nRows = nCols = 2; + else if( nPages == 6 ) + { + if( bPortrait ) + { + nRows = 2; + nCols = 3; + } + else + { + nRows = 3; + nCols = 2; + } + } + else if( nPages == 9 ) + nRows = nCols = 3; + else if( nPages == 16 ) + nRows = nCols = 4; + nPageMargin = 0; + nSheetMargin = 0; + } + else + bCustom = true; + + if( nPages > 1 ) + { + // set upper limits for margins based on job page size and rows/columns + Size aSize( getJobPageSize() ); + + // maximum sheet distance: 1/2 sheet + tools::Long nHorzMax = aSize.Width()/2; + tools::Long nVertMax = aSize.Height()/2; + if( nSheetMargin > nHorzMax ) + nSheetMargin = nHorzMax; + if( nSheetMargin > nVertMax ) + nSheetMargin = nVertMax; + + mxSheetMarginEdt->set_max( + mxSheetMarginEdt->normalize( + std::min(nHorzMax, nVertMax) ), FieldUnit::MM_100TH ); + + // maximum page distance + nHorzMax = (aSize.Width() - 2*nSheetMargin); + if( nCols > 1 ) + nHorzMax /= (nCols-1); + nVertMax = (aSize.Height() - 2*nSheetMargin); + if( nRows > 1 ) + nHorzMax /= (nRows-1); + + if( nPageMargin > nHorzMax ) + nPageMargin = nHorzMax; + if( nPageMargin > nVertMax ) + nPageMargin = nVertMax; + + mxPageMarginEdt->set_max( + mxSheetMarginEdt->normalize( + std::min(nHorzMax, nVertMax ) ), FieldUnit::MM_100TH ); + } + + mxNupRowsEdt->set_value( nRows ); + mxNupColEdt->set_value( nCols ); + mxPageMarginEdt->set_value( mxPageMarginEdt->normalize( nPageMargin ), FieldUnit::MM_100TH ); + mxSheetMarginEdt->set_value( mxSheetMarginEdt->normalize( nSheetMargin ), FieldUnit::MM_100TH ); + + showAdvancedControls( bCustom ); + updateNup( i_bMayUseCache ); +} + +void PrintDialog::enableNupControls( bool bEnable ) +{ + mxNupPagesBox->set_sensitive( bEnable ); + mxNupNumPagesTxt->set_sensitive( bEnable ); + mxNupColEdt->set_sensitive( bEnable ); + mxNupTimesTxt->set_sensitive( bEnable ); + mxNupRowsEdt->set_sensitive( bEnable ); + mxPageMarginTxt1->set_sensitive( bEnable ); + mxPageMarginEdt->set_sensitive( bEnable ); + mxPageMarginTxt2->set_sensitive( bEnable ); + mxSheetMarginTxt1->set_sensitive( bEnable ); + mxSheetMarginEdt->set_sensitive( bEnable ); + mxSheetMarginTxt2->set_sensitive( bEnable ); + mxNupOrderTxt->set_sensitive( bEnable ); + mxNupOrderBox->set_sensitive( bEnable ); + mxNupOrderWin->set_sensitive( bEnable ); + mxBorderCB->set_sensitive( bEnable ); +} + +void PrintDialog::showAdvancedControls( bool i_bShow ) +{ + mxNupNumPagesTxt->set_visible( i_bShow ); + mxNupColEdt->set_visible( i_bShow ); + mxNupTimesTxt->set_visible( i_bShow ); + mxNupRowsEdt->set_visible( i_bShow ); + mxPageMarginTxt1->set_visible( i_bShow ); + mxPageMarginEdt->set_visible( i_bShow ); + mxPageMarginTxt2->set_visible( i_bShow ); + mxSheetMarginTxt1->set_visible( i_bShow ); + mxSheetMarginEdt->set_visible( i_bShow ); + mxSheetMarginTxt2->set_visible( i_bShow ); +} + +namespace +{ + void setHelpId( weld::Widget* i_pWindow, const Sequence< OUString >& i_rHelpIds, sal_Int32 i_nIndex ) + { + if( i_nIndex >= 0 && i_nIndex < i_rHelpIds.getLength() ) + i_pWindow->set_help_id( OUStringToOString( i_rHelpIds.getConstArray()[i_nIndex], RTL_TEXTENCODING_UTF8 ) ); + } + + void setHelpText( weld::Widget* i_pWindow, const Sequence< OUString >& i_rHelpTexts, sal_Int32 i_nIndex ) + { + // without a help text set and the correct smartID, + // help texts will be retrieved from the online help system + if( i_nIndex >= 0 && i_nIndex < i_rHelpTexts.getLength() ) + i_pWindow->set_tooltip_text(i_rHelpTexts.getConstArray()[i_nIndex]); + } +} + +void PrintDialog::setupOptionalUI() +{ + const Sequence< PropertyValue >& rOptions( maPController->getUIOptions() ); + for( const auto& rOption : rOptions ) + { + if (rOption.Name == "OptionsUIFile") + { + OUString sOptionsUIFile; + rOption.Value >>= sOptionsUIFile; + mxCustomOptionsUIBuilder = Application::CreateBuilder(mxCustom.get(), sOptionsUIFile); + std::unique_ptr<weld::Container> xWindow = mxCustomOptionsUIBuilder->weld_container("box"); + xWindow->show(); + continue; + } + + Sequence< beans::PropertyValue > aOptProp; + rOption.Value >>= aOptProp; + + // extract ui element + OUString aCtrlType; + OString aID; + OUString aText; + OUString aPropertyName; + Sequence< OUString > aChoices; + Sequence< sal_Bool > aChoicesDisabled; + Sequence< OUString > aHelpTexts; + Sequence< OUString > aIDs; + Sequence< OUString > aHelpIds; + sal_Int64 nMinValue = 0, nMaxValue = 0; + OUString aGroupingHint; + + for( const beans::PropertyValue& rEntry : std::as_const(aOptProp) ) + { + if ( rEntry.Name == "ID" ) + { + rEntry.Value >>= aIDs; + aID = OUStringToOString(aIDs[0], RTL_TEXTENCODING_UTF8); + } + if ( rEntry.Name == "Text" ) + { + rEntry.Value >>= aText; + } + else if ( rEntry.Name == "ControlType" ) + { + rEntry.Value >>= aCtrlType; + } + else if ( rEntry.Name == "Choices" ) + { + rEntry.Value >>= aChoices; + } + else if ( rEntry.Name == "ChoicesDisabled" ) + { + rEntry.Value >>= aChoicesDisabled; + } + else if ( rEntry.Name == "Property" ) + { + PropertyValue aVal; + rEntry.Value >>= aVal; + aPropertyName = aVal.Name; + } + else if ( rEntry.Name == "Enabled" ) + { + } + else if ( rEntry.Name == "GroupingHint" ) + { + rEntry.Value >>= aGroupingHint; + } + else if ( rEntry.Name == "DependsOnName" ) + { + } + else if ( rEntry.Name == "DependsOnEntry" ) + { + } + else if ( rEntry.Name == "AttachToDependency" ) + { + } + else if ( rEntry.Name == "MinValue" ) + { + rEntry.Value >>= nMinValue; + } + else if ( rEntry.Name == "MaxValue" ) + { + rEntry.Value >>= nMaxValue; + } + else if ( rEntry.Name == "HelpText" ) + { + if( ! (rEntry.Value >>= aHelpTexts) ) + { + OUString aHelpText; + if( rEntry.Value >>= aHelpText ) + { + aHelpTexts.realloc( 1 ); + *aHelpTexts.getArray() = aHelpText; + } + } + } + else if ( rEntry.Name == "HelpId" ) + { + if( ! (rEntry.Value >>= aHelpIds ) ) + { + OUString aHelpId; + if( rEntry.Value >>= aHelpId ) + { + aHelpIds.realloc( 1 ); + *aHelpIds.getArray() = aHelpId; + } + } + } + else if ( rEntry.Name == "HintNoLayoutPage" ) + { + bool bHasLayoutFrame = false; + rEntry.Value >>= bHasLayoutFrame; + mbShowLayoutFrame = !bHasLayoutFrame; + } + } + + if (aCtrlType == "Group") + { + aID = "custom"; + + weld::Container* pPage = mxTabCtrl->get_page(aID); + if (!pPage) + continue; + + mxTabCtrl->set_tab_label_text(aID, aText); + + // set help id + if (aHelpIds.hasElements()) + pPage->set_help_id(OUStringToOString(aHelpIds.getConstArray()[0], RTL_TEXTENCODING_UTF8)); + + // set help text + if (aHelpTexts.hasElements()) + pPage->set_tooltip_text(aHelpTexts.getConstArray()[0]); + + pPage->show(); + } + else if (aCtrlType == "Subgroup" && !aID.isEmpty()) + { + std::unique_ptr<weld::Widget> xWidget; + // since 'New Print Dialog Design' fromwhich in calc is not a frame anymore + if (aID == "fromwhich") + { + std::unique_ptr<weld::Label> xLabel = m_xBuilder->weld_label(aID); + xLabel->set_label(aText); + xWidget = std::move(xLabel); + } + else + { + std::unique_ptr<weld::Frame> xFrame = m_xBuilder->weld_frame(aID); + if (!xFrame && mxCustomOptionsUIBuilder) + xFrame = mxCustomOptionsUIBuilder->weld_frame(aID); + if (xFrame) + { + xFrame->set_label(aText); + xWidget = std::move(xFrame); + } + } + + if (!xWidget) + continue; + + // set help id + setHelpId(xWidget.get(), aHelpIds, 0); + // set help text + setHelpText(xWidget.get(), aHelpTexts, 0); + + xWidget->show(); + } + // EVIL + else if( aCtrlType == "Bool" && aGroupingHint == "LayoutPage" && aPropertyName == "PrintProspect" ) + { + mxBrochureBtn->set_label(aText); + mxBrochureBtn->show(); + + bool bVal = false; + PropertyValue* pVal = maPController->getValue( aPropertyName ); + if( pVal ) + pVal->Value >>= bVal; + mxBrochureBtn->set_active( bVal ); + mxBrochureBtn->set_sensitive( maPController->isUIOptionEnabled( aPropertyName ) && pVal != nullptr ); + + maPropertyToWindowMap[aPropertyName].emplace_back(mxBrochureBtn.get()); + maControlToPropertyMap[mxBrochureBtn.get()] = aPropertyName; + + // set help id + setHelpId( mxBrochureBtn.get(), aHelpIds, 0 ); + // set help text + setHelpText( mxBrochureBtn.get(), aHelpTexts, 0 ); + } + else if (aCtrlType == "Bool") + { + // add a check box + std::unique_ptr<weld::CheckButton> xNewBox = m_xBuilder->weld_check_button(aID); + if (!xNewBox && mxCustomOptionsUIBuilder) + xNewBox = mxCustomOptionsUIBuilder->weld_check_button(aID); + if (!xNewBox) + continue; + + xNewBox->set_label( aText ); + xNewBox->show(); + + bool bVal = false; + PropertyValue* pVal = maPController->getValue( aPropertyName ); + if( pVal ) + pVal->Value >>= bVal; + xNewBox->set_active( bVal ); + xNewBox->connect_toggled( LINK( this, PrintDialog, UIOption_CheckHdl ) ); + + maExtraControls.emplace_back(std::move(xNewBox)); + + weld::Widget* pWidget = maExtraControls.back().get(); + + maPropertyToWindowMap[aPropertyName].emplace_back(pWidget); + maControlToPropertyMap[pWidget] = aPropertyName; + + // set help id + setHelpId(pWidget, aHelpIds, 0); + // set help text + setHelpText(pWidget, aHelpTexts, 0); + } + else if (aCtrlType == "Radio") + { + sal_Int32 nCurHelpText = 0; + + // iterate options + sal_Int32 nSelectVal = 0; + PropertyValue* pVal = maPController->getValue( aPropertyName ); + if( pVal && pVal->Value.hasValue() ) + pVal->Value >>= nSelectVal; + for( sal_Int32 m = 0; m < aChoices.getLength(); m++ ) + { + aID = OUStringToOString(aIDs[m], RTL_TEXTENCODING_UTF8); + std::unique_ptr<weld::RadioButton> xBtn = m_xBuilder->weld_radio_button(aID); + if (!xBtn && mxCustomOptionsUIBuilder) + xBtn = mxCustomOptionsUIBuilder->weld_radio_button(aID); + if (!xBtn) + continue; + + xBtn->set_label( aChoices[m] ); + xBtn->set_active( m == nSelectVal ); + xBtn->connect_toggled( LINK( this, PrintDialog, UIOption_RadioHdl ) ); + if( aChoicesDisabled.getLength() > m && aChoicesDisabled[m] ) + xBtn->set_sensitive( false ); + xBtn->show(); + + maExtraControls.emplace_back(std::move(xBtn)); + + weld::Widget* pWidget = maExtraControls.back().get(); + + maPropertyToWindowMap[ aPropertyName ].emplace_back(pWidget); + maControlToPropertyMap[pWidget] = aPropertyName; + maControlToNumValMap[pWidget] = m; + + // set help id + setHelpId( pWidget, aHelpIds, nCurHelpText ); + // set help text + setHelpText( pWidget, aHelpTexts, nCurHelpText ); + nCurHelpText++; + } + } + else if ( aCtrlType == "List" ) + { + std::unique_ptr<weld::ComboBox> xList = m_xBuilder->weld_combo_box(aID); + if (!xList && mxCustomOptionsUIBuilder) + xList = mxCustomOptionsUIBuilder->weld_combo_box(aID); + if (!xList) + continue; + + // iterate options + for( const auto& rChoice : std::as_const(aChoices) ) + xList->append_text(rChoice); + + sal_Int32 nSelectVal = 0; + PropertyValue* pVal = maPController->getValue( aPropertyName ); + if( pVal && pVal->Value.hasValue() ) + pVal->Value >>= nSelectVal; + xList->set_active(nSelectVal); + xList->connect_changed( LINK( this, PrintDialog, UIOption_SelectHdl ) ); + xList->show(); + + maExtraControls.emplace_back(std::move(xList)); + + weld::Widget* pWidget = maExtraControls.back().get(); + + maPropertyToWindowMap[ aPropertyName ].emplace_back(pWidget); + maControlToPropertyMap[pWidget] = aPropertyName; + + // set help id + setHelpId( pWidget, aHelpIds, 0 ); + // set help text + setHelpText( pWidget, aHelpTexts, 0 ); + } + else if ( aCtrlType == "Range" ) + { + std::unique_ptr<weld::SpinButton> xField = m_xBuilder->weld_spin_button(aID); + if (!xField && mxCustomOptionsUIBuilder) + xField = mxCustomOptionsUIBuilder->weld_spin_button(aID); + if (!xField) + continue; + + // set min/max and current value + if(nMinValue != nMaxValue) + xField->set_range(nMinValue, nMaxValue); + + sal_Int64 nCurVal = 0; + PropertyValue* pVal = maPController->getValue( aPropertyName ); + if( pVal && pVal->Value.hasValue() ) + pVal->Value >>= nCurVal; + xField->set_value( nCurVal ); + xField->connect_value_changed( LINK( this, PrintDialog, UIOption_SpinModifyHdl ) ); + xField->show(); + + maExtraControls.emplace_back(std::move(xField)); + + weld::Widget* pWidget = maExtraControls.back().get(); + + maPropertyToWindowMap[ aPropertyName ].emplace_back(pWidget); + maControlToPropertyMap[pWidget] = aPropertyName; + + // set help id + setHelpId( pWidget, aHelpIds, 0 ); + // set help text + setHelpText( pWidget, aHelpTexts, 0 ); + } + else if (aCtrlType == "Edit") + { + std::unique_ptr<weld::Entry> xField = m_xBuilder->weld_entry(aID); + if (!xField && mxCustomOptionsUIBuilder) + xField = mxCustomOptionsUIBuilder->weld_entry(aID); + if (!xField) + continue; + + OUString aCurVal; + PropertyValue* pVal = maPController->getValue( aPropertyName ); + if( pVal && pVal->Value.hasValue() ) + pVal->Value >>= aCurVal; + xField->set_text( aCurVal ); + xField->connect_changed( LINK( this, PrintDialog, UIOption_EntryModifyHdl ) ); + xField->show(); + + maExtraControls.emplace_back(std::move(xField)); + + weld::Widget* pWidget = maExtraControls.back().get(); + + maPropertyToWindowMap[ aPropertyName ].emplace_back(pWidget); + maControlToPropertyMap[pWidget] = aPropertyName; + + // set help id + setHelpId( pWidget, aHelpIds, 0 ); + // set help text + setHelpText( pWidget, aHelpTexts, 0 ); + } + else + { + SAL_WARN( "vcl", "Unsupported UI option: \"" << aCtrlType << '"'); + } + } + + // #i106506# if no brochure button, then the singular Pages radio button + // makes no sense, so replace it by a FixedText label + if (!mxBrochureBtn->get_visible() && mxPagesBtn->get_visible()) + { + mxPagesBoxTitleTxt->set_label(mxPagesBtn->get_label()); + mxPagesBoxTitleTxt->show(); + mxPagesBtn->hide(); + + mxNupPagesBox->set_accessible_relation_labeled_by(mxPagesBoxTitleTxt.get()); + } + + // update enable states + checkOptionalControlDependencies(); + + // print range not shown (currently math only) -> hide spacer line and reverse order + if (!mxPageRangeEdit->get_visible()) + { + mxReverseOrderBox->hide(); + } + + if (!mxCustomOptionsUIBuilder) + mxTabCtrl->remove_page(mxTabCtrl->get_page_ident(1)); +} + +void PrintDialog::makeEnabled( weld::Widget* i_pWindow ) +{ + auto it = maControlToPropertyMap.find( i_pWindow ); + if( it != maControlToPropertyMap.end() ) + { + OUString aDependency( maPController->makeEnabled( it->second ) ); + if( !aDependency.isEmpty() ) + updateWindowFromProperty( aDependency ); + } +} + +void PrintDialog::updateWindowFromProperty( const OUString& i_rProperty ) +{ + beans::PropertyValue* pValue = maPController->getValue( i_rProperty ); + auto it = maPropertyToWindowMap.find( i_rProperty ); + if( !(pValue && it != maPropertyToWindowMap.end()) ) + return; + + const auto& rWindows( it->second ); + if( rWindows.empty() ) + return; + + bool bVal = false; + sal_Int32 nVal = -1; + if( pValue->Value >>= bVal ) + { + // we should have a CheckBox for this one + weld::CheckButton* pBox = dynamic_cast<weld::CheckButton*>(rWindows.front()); + if( pBox ) + { + pBox->set_active( bVal ); + } + else if ( i_rProperty == "PrintProspect" ) + { + // EVIL special case + if( bVal ) + mxBrochureBtn->set_active(true); + else + mxPagesBtn->set_active(true); + } + else + { + SAL_WARN( "vcl", "missing a checkbox" ); + } + } + else if( pValue->Value >>= nVal ) + { + // this could be a ListBox or a RadioButtonGroup + weld::ComboBox* pList = dynamic_cast<weld::ComboBox*>(rWindows.front()); + if( pList ) + { + pList->set_active( static_cast< sal_uInt16 >(nVal) ); + } + else if( nVal >= 0 && o3tl::make_unsigned(nVal) < rWindows.size() ) + { + weld::RadioButton* pBtn = dynamic_cast<weld::RadioButton*>(rWindows[nVal]); + SAL_WARN_IF( !pBtn, "vcl", "unexpected control for property" ); + if( pBtn ) + pBtn->set_active(true); + } + } +} + +bool PrintDialog::isPrintToFile() const +{ + return ( mxPrinters->get_active() == 0 ); +} + +bool PrintDialog::isCollate() const +{ + return mxCopyCountField->get_value() > 1 && mxCollateBox->get_active(); +} + +bool PrintDialog::isSingleJobs() const +{ + return mxSingleJobsBox->get_active(); +} + +bool PrintDialog::hasPreview() const +{ + return mxPreviewBox->get_active(); +} + +PropertyValue* PrintDialog::getValueForWindow( weld::Widget* i_pWindow ) const +{ + PropertyValue* pVal = nullptr; + auto it = maControlToPropertyMap.find( i_pWindow ); + if( it != maControlToPropertyMap.end() ) + { + pVal = maPController->getValue( it->second ); + SAL_WARN_IF( !pVal, "vcl", "property value not found" ); + } + else + { + OSL_FAIL( "changed control not in property map" ); + } + return pVal; +} + +IMPL_LINK(PrintDialog, ToggleHdl, weld::Toggleable&, rButton, void) +{ + if (&rButton == mxPreviewBox.get()) + { + maUpdatePreviewIdle.Start(); + } + else if( &rButton == mxBorderCB.get() ) + { + updateNup(); + } + else if (&rButton == mxSingleJobsBox.get()) + { + maPController->setValue( "SinglePrintJobs", + Any( isSingleJobs() ) ); + checkControlDependencies(); + } + else if( &rButton == mxCollateBox.get() ) + { + maPController->setValue( "Collate", + Any( isCollate() ) ); + checkControlDependencies(); + } + else if( &rButton == mxReverseOrderBox.get() ) + { + bool bChecked = mxReverseOrderBox->get_active(); + maPController->setReversePrint( bChecked ); + maPController->setValue( "PrintReverse", + Any( bChecked ) ); + maUpdatePreviewIdle.Start(); + } + else if (&rButton == mxBrochureBtn.get()) + { + PropertyValue* pVal = getValueForWindow(mxBrochureBtn.get()); + if( pVal ) + { + bool bVal = mxBrochureBtn->get_active(); + pVal->Value <<= bVal; + + checkOptionalControlDependencies(); + + // update preview and page settings + maUpdatePreviewNoCacheIdle.Start(); + } + if (mxBrochureBtn->get_active()) + { + mxOrientationBox->set_sensitive( false ); + mxOrientationBox->set_active( ORIENTATION_LANDSCAPE ); + mxNupPagesBox->set_active( 0 ); + updateNupFromPages(); + showAdvancedControls( false ); + enableNupControls( false ); + } + else + { + mxOrientationBox->set_sensitive( true ); + mxOrientationBox->set_active( ORIENTATION_AUTOMATIC ); + enableNupControls( true ); + updateNupFromPages(); + } + + } +} + +IMPL_LINK(PrintDialog, ClickHdl, weld::Button&, rButton, void) +{ + if (&rButton == mxOKButton.get() || &rButton == mxCancelButton.get()) + { + storeToSettings(); + m_xDialog->response(&rButton == mxOKButton.get() ? RET_OK : RET_CANCEL); + } + else if( &rButton == mxHelpButton.get() ) + { + // start help system + Help* pHelp = Application::GetHelp(); + if( pHelp ) + { + pHelp->Start("vcl/ui/printdialog/PrintDialog", mxOKButton.get()); + } + } + else if( &rButton == mxForwardBtn.get() ) + { + previewForward(); + } + else if( &rButton == mxBackwardBtn.get() ) + { + previewBackward(); + } + else if( &rButton == mxFirstBtn.get() ) + { + previewFirst(); + } + else if( &rButton == mxLastBtn.get() ) + { + previewLast(); + } + else + { + if( &rButton == mxSetupButton.get() ) + { + maPController->setupPrinter(m_xDialog.get()); + + if ( !isPrintToFile() ) + { + VclPtr<Printer> aPrt( maPController->getPrinter() ); + mePaper = aPrt->GetPaper(); + + for (int nPaper = 0; nPaper < aPrt->GetPaperInfoCount(); nPaper++ ) + { + PaperInfo aInfo = aPrt->GetPaperInfo( nPaper ); + aInfo.doSloppyFit(true); + Paper ePaper = aInfo.getPaper(); + + if ( mePaper == ePaper ) + { + mxPaperSizeBox->set_active( nPaper ); + break; + } + } + } + + updateOrientationBox( false ); + setupPaperSidesBox(); + + // tdf#63905 don't use cache: page size may change + maUpdatePreviewNoCacheIdle.Start(); + } + checkControlDependencies(); + } + +} + +IMPL_LINK( PrintDialog, SelectHdl, weld::ComboBox&, rBox, void ) +{ + if (&rBox == mxPrinters.get()) + { + if ( !isPrintToFile() ) + { + OUString aNewPrinter(rBox.get_active_text()); + // set new printer + maPController->setPrinter( VclPtrInstance<Printer>( aNewPrinter ) ); + maPController->resetPrinterOptions( false ); + + updateOrientationBox(); + + // update text fields + mxOKButton->set_label(maPrintText); + updatePrinterText(); + setPaperSizes(); + maUpdatePreviewIdle.Start(); + } + else // print to file + { + // use the default printer or FIXME: the last used one? + maPController->setPrinter( VclPtrInstance<Printer>( Printer::GetDefaultPrinterName() ) ); + mxOKButton->set_label(maPrintToFileText); + maPController->resetPrinterOptions( true ); + + setPaperSizes(); + updateOrientationBox(); + maUpdatePreviewIdle.Start(); + } + + setupPaperSidesBox(); + } + else if ( &rBox == mxPaperSidesBox.get() ) + { + DuplexMode eDuplex = static_cast<DuplexMode>(mxPaperSidesBox->get_active() + 1); + maPController->getPrinter()->SetDuplexMode( eDuplex ); + } + else if( &rBox == mxOrientationBox.get() ) + { + int nOrientation = mxOrientationBox->get_active(); + if ( nOrientation != ORIENTATION_AUTOMATIC ) + setPaperOrientation( static_cast<Orientation>( nOrientation - 1 ), true ); + + updateNup( false ); + } + else if ( &rBox == mxNupOrderBox.get() ) + { + updateNup(); + } + else if( &rBox == mxNupPagesBox.get() ) + { + if( !mxPagesBtn->get_active() ) + mxPagesBtn->set_active(true); + updateNupFromPages( false ); + } + else if ( &rBox == mxPaperSizeBox.get() ) + { + VclPtr<Printer> aPrt( maPController->getPrinter() ); + PaperInfo aInfo = aPrt->GetPaperInfo( rBox.get_active() ); + aInfo.doSloppyFit(true); + mePaper = aInfo.getPaper(); + + if ( mePaper == PAPER_USER ) + aPrt->SetPaperSizeUser( Size( aInfo.getWidth(), aInfo.getHeight() ) ); + else + aPrt->SetPaper( mePaper ); + + maPController->setPaperSizeFromUser( Size( aInfo.getWidth(), aInfo.getHeight() ) ); + + maUpdatePreviewIdle.Start(); + } +} + +IMPL_LINK_NOARG(PrintDialog, MetricSpinModifyHdl, weld::MetricSpinButton&, void) +{ + checkControlDependencies(); + updateNupFromPages(); +} + +IMPL_LINK_NOARG(PrintDialog, FocusOutHdl, weld::Widget&, void) +{ + ActivateHdl(*mxPageEdit); +} + +IMPL_LINK_NOARG(PrintDialog, ActivateHdl, weld::Entry&, bool) +{ + sal_Int32 nPage = mxPageEdit->get_text().toInt32(); + if (nPage < 1) + { + nPage = 1; + mxPageEdit->set_text("1"); + } + else if (nPage > mnCachedPages) + { + nPage = mnCachedPages; + mxPageEdit->set_text(OUString::number(mnCachedPages)); + } + int nNewCurPage = nPage - 1; + if (nNewCurPage != mnCurPage) + { + mnCurPage = nNewCurPage; + maUpdatePreviewIdle.Start(); + } + return true; +} + +IMPL_LINK( PrintDialog, SpinModifyHdl, weld::SpinButton&, rEdit, void ) +{ + checkControlDependencies(); + if (&rEdit == mxNupRowsEdt.get() || &rEdit == mxNupColEdt.get()) + { + updateNupFromPages(); + } + else if( &rEdit == mxCopyCountField.get() ) + { + maPController->setValue( "CopyCount", + Any( sal_Int32(mxCopyCountField->get_value()) ) ); + maPController->setValue( "Collate", + Any( isCollate() ) ); + } +} + +IMPL_LINK( PrintDialog, UIOption_CheckHdl, weld::Toggleable&, i_rBox, void ) +{ + PropertyValue* pVal = getValueForWindow( &i_rBox ); + if( pVal ) + { + makeEnabled( &i_rBox ); + + bool bVal = i_rBox.get_active(); + pVal->Value <<= bVal; + + checkOptionalControlDependencies(); + + // update preview and page settings + maUpdatePreviewNoCacheIdle.Start(); + } +} + +IMPL_LINK( PrintDialog, UIOption_RadioHdl, weld::Toggleable&, i_rBtn, void ) +{ + // this handler gets called for all radiobuttons that get unchecked, too + // however we only want one notification for the new value (that is for + // the button that gets checked) + if( !i_rBtn.get_active() ) + return; + + PropertyValue* pVal = getValueForWindow( &i_rBtn ); + auto it = maControlToNumValMap.find( &i_rBtn ); + if( !(pVal && it != maControlToNumValMap.end()) ) + return; + + makeEnabled( &i_rBtn ); + + sal_Int32 nVal = it->second; + pVal->Value <<= nVal; + + updateOrientationBox(); + + checkOptionalControlDependencies(); + + // tdf#41205 give focus to the page range edit if the corresponding radio button was selected + if (pVal->Name == "PrintContent" && mxPageRangesRadioButton->get_active()) + mxPageRangeEdit->grab_focus(); + + // update preview and page settings + maUpdatePreviewNoCacheIdle.Start(); +} + +IMPL_LINK( PrintDialog, UIOption_SelectHdl, weld::ComboBox&, i_rBox, void ) +{ + PropertyValue* pVal = getValueForWindow( &i_rBox ); + if( !pVal ) + return; + + makeEnabled( &i_rBox ); + + sal_Int32 nVal( i_rBox.get_active() ); + pVal->Value <<= nVal; + + //If we are in impress we start in print slides mode and get a + //maFirstPageSize for slides which are usually landscape mode, if we + //change to notes which are usually in portrait mode, and then visit + //n-up print, we will assume notes are in landscape unless we throw + //away maFirstPageSize when we change page content type + if (pVal->Name == "PageContentType") + maFirstPageSize = Size(); + + checkOptionalControlDependencies(); + + // update preview and page settings + maUpdatePreviewNoCacheIdle.Start(); +} + +IMPL_LINK( PrintDialog, UIOption_SpinModifyHdl, weld::SpinButton&, i_rBox, void ) +{ + PropertyValue* pVal = getValueForWindow( &i_rBox ); + if( pVal ) + { + makeEnabled( &i_rBox ); + + sal_Int64 nVal = i_rBox.get_value(); + pVal->Value <<= nVal; + + checkOptionalControlDependencies(); + + // update preview and page settings + maUpdatePreviewNoCacheIdle.Start(); + } +} + +IMPL_LINK( PrintDialog, UIOption_EntryModifyHdl, weld::Entry&, i_rBox, void ) +{ + PropertyValue* pVal = getValueForWindow( &i_rBox ); + if( pVal ) + { + makeEnabled( &i_rBox ); + + OUString aVal( i_rBox.get_text() ); + pVal->Value <<= aVal; + + checkOptionalControlDependencies(); + + // update preview and page settings + maUpdatePreviewNoCacheIdle.Start(); + } +} + +void PrintDialog::previewForward() +{ + sal_Int32 nValue = mxPageEdit->get_text().toInt32() + 1; + if (nValue <= mnCachedPages) + { + mxPageEdit->set_text(OUString::number(nValue)); + ActivateHdl(*mxPageEdit); + } +} + +void PrintDialog::previewBackward() +{ + sal_Int32 nValue = mxPageEdit->get_text().toInt32() - 1; + if (nValue >= 1) + { + mxPageEdit->set_text(OUString::number(nValue)); + ActivateHdl(*mxPageEdit); + } +} + +void PrintDialog::previewFirst() +{ + mxPageEdit->set_text("1"); + ActivateHdl(*mxPageEdit); +} + +void PrintDialog::previewLast() +{ + mxPageEdit->set_text(OUString::number(mnCachedPages)); + ActivateHdl(*mxPageEdit); +} + + +static OUString getNewLabel(const OUString& aLabel, int i_nCurr, int i_nMax) +{ + OUString aNewText( aLabel.replaceFirst( "%p", OUString::number( i_nCurr ) ) ); + aNewText = aNewText.replaceFirst( "%n", OUString::number( i_nMax ) ); + + return aNewText; +} + +// PrintProgressDialog +PrintProgressDialog::PrintProgressDialog(weld::Window* i_pParent, int i_nMax) + : GenericDialogController(i_pParent, "vcl/ui/printprogressdialog.ui", "PrintProgressDialog") + , mbCanceled(false) + , mnCur(0) + , mnMax(i_nMax) + , mxText(m_xBuilder->weld_label("label")) + , mxProgress(m_xBuilder->weld_progress_bar("progressbar")) + , mxButton(m_xBuilder->weld_button("cancel")) +{ + if( mnMax < 1 ) + mnMax = 1; + + maStr = mxText->get_label(); + + //just multiply largest value by 10 and take the width of that string as + //the max size we will want + mxText->set_label(getNewLabel(maStr, mnMax * 10, mnMax * 10)); + mxText->set_size_request(mxText->get_preferred_size().Width(), -1); + + //Pick a useful max width + mxProgress->set_size_request(mxProgress->get_approximate_digit_width() * 25, -1); + + mxButton->connect_clicked( LINK( this, PrintProgressDialog, ClickHdl ) ); + + // after this patch f7157f04fab298423e2c4f6a7e5f8e361164b15f, we have seen the calc Max string (sometimes) look above + // now init to the right start values + mxText->set_label(getNewLabel(maStr, mnCur, mnMax)); +} + +PrintProgressDialog::~PrintProgressDialog() +{ +} + +IMPL_LINK_NOARG(PrintProgressDialog, ClickHdl, weld::Button&, void) +{ + mbCanceled = true; +} + +void PrintProgressDialog::setProgress( int i_nCurrent ) +{ + mnCur = i_nCurrent; + + if( mnMax < 1 ) + mnMax = 1; + + mxText->set_label(getNewLabel(maStr, mnCur, mnMax)); + + // here view the dialog, with the right label + mxProgress->set_percentage(mnCur*100/mnMax); +} + +void PrintProgressDialog::tick() +{ + if( mnCur < mnMax ) + setProgress( ++mnCur ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/scrwnd.cxx b/vcl/source/window/scrwnd.cxx new file mode 100644 index 000000000..6376ea0be --- /dev/null +++ b/vcl/source/window/scrwnd.cxx @@ -0,0 +1,379 @@ +/* -*- 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 <limits.h> + +#include <o3tl/float_int_conversion.hxx> +#include <tools/time.hxx> + +#include <bitmaps.hlst> +#include <svdata.hxx> +#include <scrwnd.hxx> + +#include <vcl/timer.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/ptrstyle.hxx> +#include <sal/log.hxx> + +#include <math.h> + +#define WHEEL_WIDTH 25 +#define WHEEL_RADIUS ((WHEEL_WIDTH) >> 1 ) +#define MAX_TIME 300 +#define MIN_TIME 20 +#define DEF_TIMEOUT 50 + +ImplWheelWindow::ImplWheelWindow( vcl::Window* pParent ) : + FloatingWindow ( pParent, 0 ), + mnRepaintTime ( 1 ), + mnTimeout ( DEF_TIMEOUT ), + mnWheelMode ( WheelMode::NONE ), + mnActDist ( 0 ), + mnActDeltaX ( 0 ), + mnActDeltaY ( 0 ) +{ + // we need a parent + SAL_WARN_IF( !pParent, "vcl", "ImplWheelWindow::ImplWheelWindow(): Parent not set!" ); + + const Size aSize( pParent->GetOutputSizePixel() ); + const StartAutoScrollFlags nFlags = ImplGetSVData()->mpWinData->mnAutoScrollFlags; + const bool bHorz( nFlags & StartAutoScrollFlags::Horz ); + const bool bVert( nFlags & StartAutoScrollFlags::Vert ); + + // calculate maximum speed distance + mnMaxWidth = static_cast<sal_uLong>( 0.4 * hypot( static_cast<double>(aSize.Width()), aSize.Height() ) ); + + // create wheel window + SetTitleType( FloatWinTitleType::NONE ); + ImplCreateImageList(); + BitmapEx aBmp(SV_RESID_BITMAP_SCROLLMSK); + ImplSetRegion(aBmp.GetBitmap()); + + // set wheel mode + if( bHorz && bVert ) + ImplSetWheelMode( WheelMode::VH ); + else if( bHorz ) + ImplSetWheelMode( WheelMode::H ); + else + ImplSetWheelMode( WheelMode::V ); + + // init timer + mpTimer.reset(new Timer("WheelWindowTimer")); + mpTimer->SetInvokeHandler( LINK( this, ImplWheelWindow, ImplScrollHdl ) ); + mpTimer->SetTimeout( mnTimeout ); + mpTimer->Start(); + + CaptureMouse(); +} + +ImplWheelWindow::~ImplWheelWindow() +{ + disposeOnce(); +} + +void ImplWheelWindow::dispose() +{ + ImplStop(); + mpTimer.reset(); + FloatingWindow::dispose(); +} + +void ImplWheelWindow::ImplStop() +{ + ReleaseMouse(); + mpTimer->Stop(); + Show(false); +} + +void ImplWheelWindow::ImplSetRegion( const Bitmap& rRegionBmp ) +{ + Point aPos( GetPointerPosPixel() ); + const Size aSize( rRegionBmp.GetSizePixel() ); + const tools::Rectangle aRect( Point(), aSize ); + + maCenter = maLastMousePos = aPos; + aPos.AdjustX( -(aSize.Width() >> 1) ); + aPos.AdjustY( -(aSize.Height() >> 1) ); + + SetPosSizePixel( aPos, aSize ); + SetWindowRegionPixel( rRegionBmp.CreateRegion( COL_BLACK, aRect ) ); +} + +void ImplWheelWindow::ImplCreateImageList() +{ + maImgList.emplace_back(StockImage::Yes, SV_RESID_BITMAP_SCROLLVH); + maImgList.emplace_back(StockImage::Yes, SV_RESID_BITMAP_SCROLLV); + maImgList.emplace_back(StockImage::Yes, SV_RESID_BITMAP_SCROLLH); + maImgList.emplace_back(StockImage::Yes, SV_RESID_BITMAP_WHEELVH); + maImgList.emplace_back(StockImage::Yes, SV_RESID_BITMAP_WHEELV); + maImgList.emplace_back(StockImage::Yes, SV_RESID_BITMAP_WHEELH); +} + +void ImplWheelWindow::ImplSetWheelMode( WheelMode nWheelMode ) +{ + if( nWheelMode == mnWheelMode ) + return; + + mnWheelMode = nWheelMode; + + if( WheelMode::NONE == mnWheelMode ) + { + if( IsVisible() ) + Hide(); + } + else + { + if( !IsVisible() ) + Show(); + + Invalidate(); + } +} + +void ImplWheelWindow::ImplDrawWheel(vcl::RenderContext& rRenderContext) +{ + int nIndex; + + switch (mnWheelMode) + { + case WheelMode::VH: + nIndex = 0; + break; + case WheelMode::V: + nIndex = 1; + break; + case WheelMode::H: + nIndex = 2; + break; + case WheelMode::ScrollVH: + nIndex = 3; + break; + case WheelMode::ScrollV: + nIndex = 4; + break; + case WheelMode::ScrollH: + nIndex = 5; + break; + default: + nIndex = -1; + break; + } + + if (nIndex >= 0) + rRenderContext.DrawImage(Point(), maImgList[nIndex]); +} + +void ImplWheelWindow::ImplRecalcScrollValues() +{ + if( mnActDist < WHEEL_RADIUS ) + { + mnActDeltaX = mnActDeltaY = 0; + mnTimeout = DEF_TIMEOUT; + } + else + { + sal_uInt64 nCurTime; + + // calc current time + if( mnMaxWidth ) + { + const double fExp = ( static_cast<double>(mnActDist) / mnMaxWidth ) * log10( double(MAX_TIME) / MIN_TIME ); + nCurTime = static_cast<sal_uInt64>( MAX_TIME / pow( 10., fExp ) ); + } + else + nCurTime = MAX_TIME; + + if( !nCurTime ) + nCurTime = 1; + + if( mnRepaintTime <= nCurTime ) + mnTimeout = nCurTime - mnRepaintTime; + else + { + sal_uInt64 nMult = mnRepaintTime / nCurTime; + + if( !( mnRepaintTime % nCurTime ) ) + mnTimeout = 0; + else + mnTimeout = ++nMult * nCurTime - mnRepaintTime; + + double fValX = static_cast<double>(mnActDeltaX) * nMult; + double fValY = static_cast<double>(mnActDeltaY) * nMult; + + mnActDeltaX = o3tl::saturating_cast<tools::Long>(fValX); + mnActDeltaY = o3tl::saturating_cast<tools::Long>(fValY); + } + } +} + +PointerStyle ImplWheelWindow::ImplGetMousePointer( tools::Long nDistX, tools::Long nDistY ) const +{ + PointerStyle eStyle; + const StartAutoScrollFlags nFlags = ImplGetSVData()->mpWinData->mnAutoScrollFlags; + const bool bHorz( nFlags & StartAutoScrollFlags::Horz ); + const bool bVert( nFlags & StartAutoScrollFlags::Vert ); + + if( bHorz || bVert ) + { + if( mnActDist < WHEEL_RADIUS ) + { + if( bHorz && bVert ) + eStyle = PointerStyle::AutoScrollNSWE; + else if( bHorz ) + eStyle = PointerStyle::AutoScrollWE; + else + eStyle = PointerStyle::AutoScrollNS; + } + else + { + double fAngle = basegfx::rad2deg(atan2(static_cast<double>(-nDistY), nDistX)); + + if( fAngle < 0.0 ) + fAngle += 360.; + + if( bHorz && bVert ) + { + if( fAngle >= 22.5 && fAngle <= 67.5 ) + eStyle = PointerStyle::AutoScrollNE; + else if( fAngle >= 67.5 && fAngle <= 112.5 ) + eStyle = PointerStyle::AutoScrollN; + else if( fAngle >= 112.5 && fAngle <= 157.5 ) + eStyle = PointerStyle::AutoScrollNW; + else if( fAngle >= 157.5 && fAngle <= 202.5 ) + eStyle = PointerStyle::AutoScrollW; + else if( fAngle >= 202.5 && fAngle <= 247.5 ) + eStyle = PointerStyle::AutoScrollSW; + else if( fAngle >= 247.5 && fAngle <= 292.5 ) + eStyle = PointerStyle::AutoScrollS; + else if( fAngle >= 292.5 && fAngle <= 337.5 ) + eStyle = PointerStyle::AutoScrollSE; + else + eStyle = PointerStyle::AutoScrollE; + } + else if( bHorz ) + { + if( fAngle >= 270. || fAngle <= 90. ) + eStyle = PointerStyle::AutoScrollE; + else + eStyle = PointerStyle::AutoScrollW; + } + else + { + if( fAngle >= 0. && fAngle <= 180. ) + eStyle = PointerStyle::AutoScrollN; + else + eStyle = PointerStyle::AutoScrollS; + } + } + } + else + eStyle = PointerStyle::Arrow; + + return eStyle; +} + +void ImplWheelWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + ImplDrawWheel(rRenderContext); +} + +void ImplWheelWindow::MouseMove( const MouseEvent& rMEvt ) +{ + FloatingWindow::MouseMove( rMEvt ); + + const Point aMousePos( OutputToScreenPixel( rMEvt.GetPosPixel() ) ); + const tools::Long nDistX = aMousePos.X() - maCenter.X(); + const tools::Long nDistY = aMousePos.Y() - maCenter.Y(); + + mnActDist = static_cast<sal_uLong>(hypot( static_cast<double>(nDistX), nDistY )); + + const PointerStyle eActStyle = ImplGetMousePointer( nDistX, nDistY ); + const StartAutoScrollFlags nFlags = ImplGetSVData()->mpWinData->mnAutoScrollFlags; + const bool bHorz( nFlags & StartAutoScrollFlags::Horz ); + const bool bVert( nFlags & StartAutoScrollFlags::Vert ); + const bool bOuter = mnActDist > WHEEL_RADIUS; + + if( bOuter && ( maLastMousePos != aMousePos ) ) + { + switch( eActStyle ) + { + case PointerStyle::AutoScrollN: mnActDeltaX = +0; mnActDeltaY = +1; break; + case PointerStyle::AutoScrollS: mnActDeltaX = +0; mnActDeltaY = -1; break; + case PointerStyle::AutoScrollW: mnActDeltaX = +1; mnActDeltaY = +0; break; + case PointerStyle::AutoScrollE: mnActDeltaX = -1; mnActDeltaY = +0; break; + case PointerStyle::AutoScrollNW: mnActDeltaX = +1; mnActDeltaY = +1; break; + case PointerStyle::AutoScrollNE: mnActDeltaX = -1; mnActDeltaY = +1; break; + case PointerStyle::AutoScrollSW: mnActDeltaX = +1; mnActDeltaY = -1; break; + case PointerStyle::AutoScrollSE: mnActDeltaX = -1; mnActDeltaY = -1; break; + + default: + break; + } + } + + ImplRecalcScrollValues(); + maLastMousePos = aMousePos; + SetPointer( eActStyle ); + + if( bHorz && bVert ) + ImplSetWheelMode( bOuter ? WheelMode::ScrollVH : WheelMode::VH ); + else if( bHorz ) + ImplSetWheelMode( bOuter ? WheelMode::ScrollH : WheelMode::H ); + else + ImplSetWheelMode( bOuter ? WheelMode::ScrollV : WheelMode::V ); +} + +void ImplWheelWindow::MouseButtonUp( const MouseEvent& rMEvt ) +{ + if( mnActDist > WHEEL_RADIUS ) + GetParent()->EndAutoScroll(); + else + FloatingWindow::MouseButtonUp( rMEvt ); +} + +IMPL_LINK_NOARG(ImplWheelWindow, ImplScrollHdl, Timer *, void) +{ + if ( mnActDeltaX || mnActDeltaY ) + { + vcl::Window* pWindow = GetParent(); + const Point aMousePos( pWindow->OutputToScreenPixel( pWindow->GetPointerPosPixel() ) ); + Point aCmdMousePos( pWindow->ImplFrameToOutput( aMousePos ) ); + CommandScrollData aScrollData( mnActDeltaX, mnActDeltaY ); + CommandEvent aCEvt( aCmdMousePos, CommandEventId::AutoScroll, true, &aScrollData ); + NotifyEvent aNCmdEvt( MouseNotifyEvent::COMMAND, pWindow, &aCEvt ); + + if ( !ImplCallPreNotify( aNCmdEvt ) ) + { + const sal_uInt64 nTime = tools::Time::GetSystemTicks(); + VclPtr<ImplWheelWindow> xWin(this); + pWindow->Command( aCEvt ); + if( xWin->isDisposed() ) + return; + mnRepaintTime = std::max( tools::Time::GetSystemTicks() - nTime, sal_uInt64(1) ); + ImplRecalcScrollValues(); + } + } + + if ( mnTimeout != mpTimer->GetTimeout() ) + mpTimer->SetTimeout( mnTimeout ); + mpTimer->Start(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/seleng.cxx b/vcl/source/window/seleng.cxx new file mode 100644 index 000000000..f81ffe6cd --- /dev/null +++ b/vcl/source/window/seleng.cxx @@ -0,0 +1,421 @@ +/* -*- 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/commandevent.hxx> +#include <vcl/window.hxx> +#include <vcl/seleng.hxx> +#include <comphelper/lok.hxx> +#include <sal/log.hxx> + +FunctionSet::~FunctionSet() +{ +} + +inline bool SelectionEngine::ShouldDeselect( bool bModifierKey1 ) const +{ + return eSelMode != SelectionMode::Multiple || !bModifierKey1; +} + +// TODO: throw out FunctionSet::SelectAtPoint + +SelectionEngine::SelectionEngine( vcl::Window* pWindow, FunctionSet* pFuncSet ) : + pWin( pWindow ), + aWTimer( "vcl::SelectionEngine aWTimer" ), + nUpdateInterval( SELENG_AUTOREPEAT_INTERVAL ) +{ + eSelMode = SelectionMode::Single; + pFunctionSet = pFuncSet; + nFlags = SelectionEngineFlags::EXPANDONMOVE; + nLockedMods = 0; + + aWTimer.SetInvokeHandler( LINK( this, SelectionEngine, ImpWatchDog ) ); + aWTimer.SetTimeout( nUpdateInterval ); +} + +SelectionEngine::~SelectionEngine() +{ + aWTimer.Stop(); +} + +IMPL_LINK_NOARG(SelectionEngine, ImpWatchDog, Timer *, void) +{ + if ( !aArea.Contains( aLastMove.GetPosPixel() ) ) + SelMouseMove( aLastMove ); +} + +void SelectionEngine::SetSelectionMode( SelectionMode eMode ) +{ + eSelMode = eMode; +} + +void SelectionEngine::CursorPosChanging( bool bShift, bool bMod1 ) +{ + if ( !pFunctionSet ) + return; + + if ( bShift && eSelMode != SelectionMode::Single ) + { + if ( IsAddMode() ) + { + if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) ) + { + pFunctionSet->CreateAnchor(); + nFlags |= SelectionEngineFlags::HAS_ANCH; + } + } + else + { + if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) ) + { + if( ShouldDeselect( bMod1 ) ) + pFunctionSet->DeselectAll(); + pFunctionSet->CreateAnchor(); + nFlags |= SelectionEngineFlags::HAS_ANCH; + } + } + } + else + { + if ( IsAddMode() ) + { + if ( nFlags & SelectionEngineFlags::HAS_ANCH ) + { + // pFunctionSet->CreateCursor(); + pFunctionSet->DestroyAnchor(); + nFlags &= ~SelectionEngineFlags::HAS_ANCH; + } + } + else + { + if( ShouldDeselect( bMod1 ) ) + pFunctionSet->DeselectAll(); + else + pFunctionSet->DestroyAnchor(); + nFlags &= ~SelectionEngineFlags::HAS_ANCH; + } + } +} + +bool SelectionEngine::SelMouseButtonDown( const MouseEvent& rMEvt ) +{ + nFlags &= ~SelectionEngineFlags::CMDEVT; + if ( !pFunctionSet || rMEvt.GetClicks() > 1 ) + return false; + + sal_uInt16 nModifier = rMEvt.GetModifier() | nLockedMods; + bool nSwap = comphelper::LibreOfficeKit::isActive() && (nModifier & KEY_MOD1) && (nModifier & KEY_MOD2); + + if ( !nSwap && (nModifier & KEY_MOD2) ) + return false; + // in SingleSelection: filter Control-Key, + // so that a D&D can be also started with a Ctrl-Click + if ( nModifier == KEY_MOD1 && eSelMode == SelectionMode::Single ) + nModifier = 0; + + Point aPos = rMEvt.GetPosPixel(); + aLastMove = rMEvt; + + if( !rMEvt.IsRight() ) + { + CaptureMouse(); + nFlags |= SelectionEngineFlags::IN_SEL; + } + else + { + nModifier = 0; + } + + if (nSwap) + { + pFunctionSet->CreateAnchor(); + pFunctionSet->SetCursorAtPoint( aPos ); + return true; + } + + switch ( nModifier ) + { + case 0: // KEY_NO_KEY + { + bool bSelAtPoint = pFunctionSet->IsSelectionAtPoint( aPos ); + nFlags &= ~SelectionEngineFlags::IN_ADD; + if ( (nFlags & SelectionEngineFlags::DRG_ENAB) && bSelAtPoint ) + { + nFlags |= SelectionEngineFlags::WAIT_UPEVT; + nFlags &= ~SelectionEngineFlags::IN_SEL; + ReleaseMouse(); + return true; // wait for STARTDRAG-Command-Event + } + if ( eSelMode != SelectionMode::Single ) + { + if( !IsAddMode() ) + pFunctionSet->DeselectAll(); + else + pFunctionSet->DestroyAnchor(); + nFlags &= ~SelectionEngineFlags::HAS_ANCH; // bHasAnchor = false; + } + pFunctionSet->SetCursorAtPoint( aPos ); + // special case Single-Selection, to enable simple Select+Drag + if (eSelMode == SelectionMode::Single && (nFlags & SelectionEngineFlags::DRG_ENAB)) + nFlags |= SelectionEngineFlags::WAIT_UPEVT; + return true; + } + + case KEY_SHIFT: + if ( eSelMode == SelectionMode::Single ) + { + ReleaseMouse(); + nFlags &= ~SelectionEngineFlags::IN_SEL; + return false; + } + if ( nFlags & SelectionEngineFlags::ADD_ALW ) + nFlags |= SelectionEngineFlags::IN_ADD; + else + nFlags &= ~SelectionEngineFlags::IN_ADD; + + if( !(nFlags & SelectionEngineFlags::HAS_ANCH) ) + { + if ( !(nFlags & SelectionEngineFlags::IN_ADD) ) + pFunctionSet->DeselectAll(); + pFunctionSet->CreateAnchor(); + nFlags |= SelectionEngineFlags::HAS_ANCH; + } + pFunctionSet->SetCursorAtPoint( aPos ); + return true; + + case KEY_MOD1: + // allow Control only for Multi-Select + if ( eSelMode != SelectionMode::Multiple ) + { + nFlags &= ~SelectionEngineFlags::IN_SEL; + ReleaseMouse(); + return true; // skip Mouse-Click + } + if ( nFlags & SelectionEngineFlags::HAS_ANCH ) + { + // pFunctionSet->CreateCursor(); + pFunctionSet->DestroyAnchor(); + nFlags &= ~SelectionEngineFlags::HAS_ANCH; + } + if ( pFunctionSet->IsSelectionAtPoint( aPos ) ) + { + pFunctionSet->DeselectAtPoint( aPos ); + pFunctionSet->SetCursorAtPoint( aPos, true ); + } + else + { + pFunctionSet->SetCursorAtPoint( aPos ); + } + return true; + + case KEY_SHIFT + KEY_MOD1: + if ( eSelMode != SelectionMode::Multiple ) + { + ReleaseMouse(); + nFlags &= ~SelectionEngineFlags::IN_SEL; + return false; + } + nFlags |= SelectionEngineFlags::IN_ADD; //bIsInAddMode = true; + if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) ) + { + pFunctionSet->CreateAnchor(); + nFlags |= SelectionEngineFlags::HAS_ANCH; + } + pFunctionSet->SetCursorAtPoint( aPos ); + return true; + } + + return false; +} + +bool SelectionEngine::SelMouseButtonUp( const MouseEvent& rMEvt ) +{ + aWTimer.Stop(); + if (!pFunctionSet) + { + const SelectionEngineFlags nMask = SelectionEngineFlags::CMDEVT | SelectionEngineFlags::WAIT_UPEVT | SelectionEngineFlags::IN_SEL; + nFlags &= ~nMask; + return false; + } + + if (!rMEvt.IsRight()) + ReleaseMouse(); + +#if defined IOS || defined ANDROID + const bool bDoMessWithSelection = !rMEvt.IsRight(); +#else + constexpr bool bDoMessWithSelection = true; +#endif + + if( (nFlags & SelectionEngineFlags::WAIT_UPEVT) && !(nFlags & SelectionEngineFlags::CMDEVT) && + eSelMode != SelectionMode::Single) + { + // MouseButtonDown in Sel but no CommandEvent yet + // ==> deselect + sal_uInt16 nModifier = aLastMove.GetModifier() | nLockedMods; + if( nModifier == KEY_MOD1 || IsAlwaysAdding() ) + { + if( !(nModifier & KEY_SHIFT) ) + { + pFunctionSet->DestroyAnchor(); + nFlags &= ~SelectionEngineFlags::HAS_ANCH; // uncheck anchor + } + pFunctionSet->DeselectAtPoint( aLastMove.GetPosPixel() ); + nFlags &= ~SelectionEngineFlags::HAS_ANCH; // uncheck anchor + if (bDoMessWithSelection) + pFunctionSet->SetCursorAtPoint( aLastMove.GetPosPixel(), true ); + } + else + { + if (bDoMessWithSelection) + pFunctionSet->DeselectAll(); + nFlags &= ~SelectionEngineFlags::HAS_ANCH; // uncheck anchor + if (bDoMessWithSelection) + pFunctionSet->SetCursorAtPoint( aLastMove.GetPosPixel() ); + } + } + + const SelectionEngineFlags nMask = SelectionEngineFlags::CMDEVT | SelectionEngineFlags::WAIT_UPEVT | SelectionEngineFlags::IN_SEL; + nFlags &= ~nMask; + return true; +} + +void SelectionEngine::ReleaseMouse() +{ + if (!pWin || !pWin->IsMouseCaptured()) + return; + pWin->ReleaseMouse(); +} + +void SelectionEngine::CaptureMouse() +{ + if (!pWin || pWin->IsMouseCaptured()) + return; + pWin->CaptureMouse(); +} + +bool SelectionEngine::SelMouseMove( const MouseEvent& rMEvt ) +{ + + if ( !pFunctionSet || !(nFlags & SelectionEngineFlags::IN_SEL) || + (nFlags & (SelectionEngineFlags::CMDEVT | SelectionEngineFlags::WAIT_UPEVT)) ) + return false; + + if( !(nFlags & SelectionEngineFlags::EXPANDONMOVE) ) + return false; // wait for DragEvent! + + aLastMove = rMEvt; + // if the mouse is outside the area, the frequency of + // SetCursorAtPoint() is only set by the Timer + if( aWTimer.IsActive() && !aArea.Contains( rMEvt.GetPosPixel() )) + return true; + + aWTimer.SetTimeout( nUpdateInterval ); + if (!comphelper::LibreOfficeKit::isActive()) + // Generating fake mouse moves does not work with LOK. + aWTimer.Start(); + if ( eSelMode != SelectionMode::Single ) + { + if ( !(nFlags & SelectionEngineFlags::HAS_ANCH) ) + { + pFunctionSet->CreateAnchor(); + nFlags |= SelectionEngineFlags::HAS_ANCH; + } + } + + pFunctionSet->SetCursorAtPoint( rMEvt.GetPosPixel() ); + + return true; +} + +void SelectionEngine::SetWindow( vcl::Window* pNewWin ) +{ + if( pNewWin != pWin ) + { + if (nFlags & SelectionEngineFlags::IN_SEL) + ReleaseMouse(); + pWin = pNewWin; + if (nFlags & SelectionEngineFlags::IN_SEL) + CaptureMouse(); + } +} + +void SelectionEngine::Reset() +{ + aWTimer.Stop(); + if (nFlags & SelectionEngineFlags::IN_SEL) + ReleaseMouse(); + nFlags &= ~SelectionEngineFlags(SelectionEngineFlags::HAS_ANCH | SelectionEngineFlags::IN_SEL); + nLockedMods = 0; +} + +bool SelectionEngine::Command( const CommandEvent& rCEvt ) +{ + // Timer aWTimer is active during enlarging a selection + if ( !pFunctionSet || aWTimer.IsActive() ) + return false; + aWTimer.Stop(); + if ( rCEvt.GetCommand() != CommandEventId::StartDrag ) + return false; + + nFlags |= SelectionEngineFlags::CMDEVT; + if ( nFlags & SelectionEngineFlags::DRG_ENAB ) + { + SAL_WARN_IF( !rCEvt.IsMouseEvent(), "vcl", "STARTDRAG: Not a MouseEvent" ); + if ( pFunctionSet->IsSelectionAtPoint( rCEvt.GetMousePosPixel() ) ) + { + aLastMove = MouseEvent( rCEvt.GetMousePosPixel(), + aLastMove.GetClicks(), aLastMove.GetMode(), + aLastMove.GetButtons(), aLastMove.GetModifier() ); + pFunctionSet->BeginDrag(); + const SelectionEngineFlags nMask = SelectionEngineFlags::CMDEVT|SelectionEngineFlags::WAIT_UPEVT|SelectionEngineFlags::IN_SEL; + nFlags &= ~nMask; + } + else + nFlags &= ~SelectionEngineFlags::CMDEVT; + } + else + nFlags &= ~SelectionEngineFlags::CMDEVT; + return true; +} + +void SelectionEngine::SetUpdateInterval( sal_uLong nInterval ) +{ + if (nInterval < SELENG_AUTOREPEAT_INTERVAL_MIN) + // Set a lower threshold. On Windows, setting this value too low + // would cause selection to get updated indefinitely. + nInterval = SELENG_AUTOREPEAT_INTERVAL_MIN; + + if (nUpdateInterval == nInterval) + // no update needed. + return; + + if (aWTimer.IsActive()) + { + // reset the timer right away on interval change. + aWTimer.Stop(); + aWTimer.SetTimeout(nInterval); + aWTimer.Start(); + } + else + aWTimer.SetTimeout(nInterval); + + nUpdateInterval = nInterval; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/settings.cxx b/vcl/source/window/settings.cxx new file mode 100644 index 000000000..f9af6982a --- /dev/null +++ b/vcl/source/window/settings.cxx @@ -0,0 +1,266 @@ +/* -*- 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 <i18nlangtag/languagetag.hxx> +#include <i18nlangtag/mslangid.hxx> + +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <vcl/settings.hxx> + +#include <unotools/configmgr.hxx> +#include <unotools/confignode.hxx> + +#include <comphelper/processfactory.hxx> + +#include <salframe.hxx> +#include <brdwin.hxx> + +#include <window.h> + +namespace vcl { + +void WindowOutputDevice::SetSettings( const AllSettings& rSettings ) +{ + SetSettings( rSettings, false ); +} + +void WindowOutputDevice::SetSettings( const AllSettings& rSettings, bool bChild ) +{ + + if ( auto pBorderWindow = mxOwnerWindow->mpWindowImpl->mpBorderWindow.get() ) + { + static_cast<vcl::WindowOutputDevice*>(pBorderWindow->GetOutDev())->SetSettings( rSettings, false ); + if ( (pBorderWindow->GetType() == WindowType::BORDERWINDOW) && + static_cast<ImplBorderWindow*>(pBorderWindow)->mpMenuBarWindow ) + static_cast<vcl::WindowOutputDevice*>(static_cast<ImplBorderWindow*>(pBorderWindow)->mpMenuBarWindow->GetOutDev())->SetSettings( rSettings, true ); + } + + AllSettings aOldSettings(*mxSettings); + OutputDevice::SetSettings( rSettings ); + AllSettingsFlags nChangeFlags = aOldSettings.GetChangeFlags( rSettings ); + + // recalculate AppFont-resolution and DPI-resolution + mxOwnerWindow->ImplInitResolutionSettings(); + + if ( bool(nChangeFlags) ) + { + DataChangedEvent aDCEvt( DataChangedEventType::SETTINGS, &aOldSettings, nChangeFlags ); + mxOwnerWindow->DataChanged( aDCEvt ); + } + + if ( bChild ) + { + vcl::Window* pChild = mxOwnerWindow->mpWindowImpl->mpFirstChild; + while ( pChild ) + { + static_cast<vcl::WindowOutputDevice*>(pChild->GetOutDev())->SetSettings( rSettings, bChild ); + pChild = pChild->mpWindowImpl->mpNext; + } + } +} + +void Window::UpdateSettings( const AllSettings& rSettings, bool bChild ) +{ + + if ( mpWindowImpl->mpBorderWindow ) + { + mpWindowImpl->mpBorderWindow->UpdateSettings( rSettings ); + if ( (mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW) && + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow ) + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow->UpdateSettings( rSettings, true ); + } + + AllSettings aOldSettings(*mpWindowImpl->mxOutDev->mxSettings); + AllSettingsFlags nChangeFlags = mpWindowImpl->mxOutDev->mxSettings->Update( AllSettings::GetWindowUpdate(), rSettings ); + + // recalculate AppFont-resolution and DPI-resolution + ImplInitResolutionSettings(); + + /* #i73785# + * do not overwrite a WheelBehavior with false + * this looks kind of a hack, but WheelBehavior + * is always a local change, not a system property, + * so we can spare all our users the hassle of reacting on + * this in their respective DataChanged. + */ + MouseSettings aSet( mpWindowImpl->mxOutDev->mxSettings->GetMouseSettings() ); + aSet.SetWheelBehavior( aOldSettings.GetMouseSettings().GetWheelBehavior() ); + mpWindowImpl->mxOutDev->mxSettings->SetMouseSettings( aSet ); + + if( (nChangeFlags & AllSettingsFlags::STYLE) && IsBackground() ) + { + Wallpaper aWallpaper = GetBackground(); + if( !aWallpaper.IsBitmap() && !aWallpaper.IsGradient() ) + { + if ( mpWindowImpl->mnStyle & WB_3DLOOK ) + { + if (aOldSettings.GetStyleSettings().GetFaceColor() != rSettings.GetStyleSettings().GetFaceColor()) + SetBackground( Wallpaper( rSettings.GetStyleSettings().GetFaceColor() ) ); + } + else + { + if (aOldSettings.GetStyleSettings().GetWindowColor() != rSettings.GetStyleSettings().GetWindowColor()) + SetBackground( Wallpaper( rSettings.GetStyleSettings().GetWindowColor() ) ); + } + } + } + + if ( bool(nChangeFlags) ) + { + DataChangedEvent aDCEvt( DataChangedEventType::SETTINGS, &aOldSettings, nChangeFlags ); + DataChanged( aDCEvt ); + // notify data change handler + CallEventListeners( VclEventId::WindowDataChanged, &aDCEvt); + } + + if ( bChild ) + { + vcl::Window* pChild = mpWindowImpl->mpFirstChild; + while ( pChild ) + { + pChild->UpdateSettings( rSettings, bChild ); + pChild = pChild->mpWindowImpl->mpNext; + } + } +} + +void Window::ImplUpdateGlobalSettings( AllSettings& rSettings, bool bCallHdl ) const +{ + StyleSettings aTmpSt( rSettings.GetStyleSettings() ); + aTmpSt.SetHighContrastMode( false ); + rSettings.SetStyleSettings( aTmpSt ); + ImplGetFrame()->UpdateSettings( rSettings ); + + StyleSettings aStyleSettings = rSettings.GetStyleSettings(); + + vcl::Font aFont = aStyleSettings.GetMenuFont(); + int defFontheight = aFont.GetFontHeight(); + + // if the UI is korean, chinese or another locale + // where the system font size is known to be often too small to + // generate readable fonts enforce a minimum font size of 9 points + bool bBrokenLangFontHeight = MsLangId::isCJK(Application::GetSettings().GetUILanguageTag().getLanguageType()); + if (bBrokenLangFontHeight) + defFontheight = std::max(9, defFontheight); + + // i22098, toolfont will be scaled differently to avoid bloated rulers and status bars for big fonts + int toolfontheight = defFontheight; + if( toolfontheight > 9 ) + toolfontheight = (defFontheight+8) / 2; + + aFont = aStyleSettings.GetAppFont(); + aFont.SetFontHeight( defFontheight ); + aStyleSettings.SetAppFont( aFont ); + aFont = aStyleSettings.GetTitleFont(); + aFont.SetFontHeight( defFontheight ); + aStyleSettings.SetTitleFont( aFont ); + aFont = aStyleSettings.GetFloatTitleFont(); + aFont.SetFontHeight( defFontheight ); + aStyleSettings.SetFloatTitleFont( aFont ); + // keep menu and help font size from system unless in broken locale size + if( bBrokenLangFontHeight ) + { + aFont = aStyleSettings.GetMenuFont(); + if( aFont.GetFontHeight() < defFontheight ) + { + aFont.SetFontHeight( defFontheight ); + aStyleSettings.SetMenuFont( aFont ); + } + aFont = aStyleSettings.GetHelpFont(); + if( aFont.GetFontHeight() < defFontheight ) + { + aFont.SetFontHeight( defFontheight ); + aStyleSettings.SetHelpFont( aFont ); + } + } + + // use different height for toolfont + aFont = aStyleSettings.GetToolFont(); + aFont.SetFontHeight( toolfontheight ); + aStyleSettings.SetToolFont( aFont ); + + aFont = aStyleSettings.GetLabelFont(); + aFont.SetFontHeight( defFontheight ); + aStyleSettings.SetLabelFont( aFont ); + aFont = aStyleSettings.GetRadioCheckFont(); + aFont.SetFontHeight( defFontheight ); + aStyleSettings.SetRadioCheckFont( aFont ); + aFont = aStyleSettings.GetPushButtonFont(); + aFont.SetFontHeight( defFontheight ); + aStyleSettings.SetPushButtonFont( aFont ); + aFont = aStyleSettings.GetFieldFont(); + aFont.SetFontHeight( defFontheight ); + aStyleSettings.SetFieldFont( aFont ); + aFont = aStyleSettings.GetIconFont(); + aFont.SetFontHeight( defFontheight ); + aStyleSettings.SetIconFont( aFont ); + aFont = aStyleSettings.GetTabFont(); + aFont.SetFontHeight( defFontheight ); + aStyleSettings.SetTabFont( aFont ); + aFont = aStyleSettings.GetGroupFont(); + aFont.SetFontHeight( defFontheight ); + aStyleSettings.SetGroupFont( aFont ); + + rSettings.SetStyleSettings( aStyleSettings ); + + bool bForceHCMode = false; + + // auto detect HC mode; if the system already set it to "yes" + // (see above) then accept that + if (!rSettings.GetStyleSettings().GetHighContrastMode() && !utl::ConfigManager::IsFuzzing()) + { + bool bAutoHCMode = true; + utl::OConfigurationNode aNode = utl::OConfigurationTreeRoot::tryCreateWithComponentContext( + comphelper::getProcessComponentContext(), + "org.openoffice.Office.Common/Accessibility" ); // note: case sensitive ! + if ( aNode.isValid() ) + { + css::uno::Any aValue = aNode.getNodeValue( "AutoDetectSystemHC" ); + bool bTmp = false; + if( aValue >>= bTmp ) + bAutoHCMode = bTmp; + } + if( bAutoHCMode ) + { + if( rSettings.GetStyleSettings().GetFaceColor().IsDark() || + rSettings.GetStyleSettings().GetWindowColor().IsDark() ) + bForceHCMode = true; + } + } + + static const char* pEnvHC = getenv( "SAL_FORCE_HC" ); + if( pEnvHC && *pEnvHC ) + bForceHCMode = true; + + if( bForceHCMode ) + { + aStyleSettings = rSettings.GetStyleSettings(); + aStyleSettings.SetHighContrastMode( true ); + rSettings.SetStyleSettings( aStyleSettings ); + } + + if ( bCallHdl ) + GetpApp()->OverrideSystemSettings( rSettings ); +} + +} /*namespace vcl*/ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/split.cxx b/vcl/source/window/split.cxx new file mode 100644 index 000000000..df631b270 --- /dev/null +++ b/vcl/source/window/split.cxx @@ -0,0 +1,698 @@ +/* -*- 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/poly.hxx> + +#include <vcl/event.hxx> +#include <vcl/split.hxx> +#include <vcl/svapp.hxx> +#include <vcl/syswin.hxx> +#include <vcl/taskpanelist.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/settings.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/lazydelete.hxx> + +#include <window.h> + +namespace +{ + Wallpaper& ImplBlackWall() + { + static vcl::DeleteOnDeinit< Wallpaper > SINGLETON(COL_BLACK); + return *SINGLETON.get(); + } + Wallpaper& ImplWhiteWall() + { + static vcl::DeleteOnDeinit< Wallpaper > SINGLETON(COL_LIGHTGRAY); + return *SINGLETON.get(); + } +} + +// Should only be called from an ImplInit method for initialization or +// after checking bNew is different from the current mbHorzSplit value. +// The public method that does that check is Splitter::SetHorizontal(). +void Splitter::ImplInitHorVer(bool bNew) +{ + mbHorzSplit = bNew; + + PointerStyle ePointerStyle; + const StyleSettings& rSettings = GetSettings().GetStyleSettings(); + + if ( mbHorzSplit ) + { + ePointerStyle = PointerStyle::HSplit; + SetSizePixel( Size( StyleSettings::GetSplitSize(), rSettings.GetScrollBarSize() ) ); + } + else + { + ePointerStyle = PointerStyle::VSplit; + SetSizePixel( Size( rSettings.GetScrollBarSize(), StyleSettings::GetSplitSize() ) ); + } + + SetPointer( ePointerStyle ); +} + +void Splitter::ImplInit( vcl::Window* pParent, WinBits nWinStyle ) +{ + Window::ImplInit( pParent, nWinStyle, nullptr ); + + mpRefWin = pParent; + + ImplInitHorVer(nWinStyle & WB_HSCROLL); + + if( GetSettings().GetStyleSettings().GetFaceColor().IsDark() ) + SetBackground( ImplWhiteWall() ); + else + SetBackground( ImplBlackWall() ); + + TaskPaneList *pTList = GetSystemWindow()->GetTaskPaneList(); + pTList->AddWindow( this ); +} + +void Splitter::ImplSplitMousePos( Point& rPos ) +{ + if ( mbHorzSplit ) + { + if ( rPos.X() > maDragRect.Right()-1 ) + rPos.setX( maDragRect.Right()-1 ); + if ( rPos.X() < maDragRect.Left()+1 ) + rPos.setX( maDragRect.Left()+1 ); + } + else + { + if ( rPos.Y() > maDragRect.Bottom()-1 ) + rPos.setY( maDragRect.Bottom()-1 ); + if ( rPos.Y() < maDragRect.Top()+1 ) + rPos.setY( maDragRect.Top()+1 ); + } +} + +void Splitter::ImplDrawSplitter() +{ + tools::Rectangle aInvRect( maDragRect ); + + if ( mbHorzSplit ) + { + aInvRect.SetLeft( maDragPos.X() - 1 ); + aInvRect.SetRight( maDragPos.X() + 1 ); + } + else + { + aInvRect.SetTop( maDragPos.Y() - 1 ); + aInvRect.SetBottom( maDragPos.Y() + 1 ); + } + + mpRefWin->InvertTracking( mpRefWin->PixelToLogic(aInvRect), ShowTrackFlags::Split ); +} + +Splitter::Splitter( vcl::Window* pParent, WinBits nStyle ) : + Window( WindowType::SPLITTER ), + mpRefWin( nullptr ), + mnSplitPos( 0 ), + mnLastSplitPos( 0 ), + mnStartSplitPos( 0 ), + mbDragFull( false ), + mbKbdSplitting( false ), + mbInKeyEvent( false ), + mnKeyboardStepSize( SPLITTER_DEFAULTSTEPSIZE ) +{ + ImplGetWindowImpl()->mbSplitter = true; + + ImplInit( pParent, nStyle ); + + GetOutDev()->SetLineColor(); + GetOutDev()->SetFillColor(); +} + +Splitter::~Splitter() +{ + disposeOnce(); +} + +void Splitter::dispose() +{ + SystemWindow *pSysWin = GetSystemWindow(); + if(pSysWin) + { + TaskPaneList *pTList = pSysWin->GetTaskPaneList(); + pTList->RemoveWindow(this); + } + mpRefWin.clear(); + Window::dispose(); +} + +void Splitter::SetHorizontal(bool bNew) +{ + if(bNew != mbHorzSplit) + { + ImplInitHorVer(bNew); + } +} + +void Splitter::SetKeyboardStepSize( tools::Long nStepSize ) +{ + mnKeyboardStepSize = nStepSize; +} + +Splitter* Splitter::ImplFindSibling() +{ + // look for another splitter with the same parent but different orientation + vcl::Window *pWin = GetParent()->GetWindow( GetWindowType::FirstChild ); + Splitter *pSplitter = nullptr; + while( pWin ) + { + if( pWin->ImplIsSplitter() ) + { + pSplitter = static_cast<Splitter*>(pWin); + if( pSplitter != this && IsHorizontal() != pSplitter->IsHorizontal() ) + return pSplitter; + } + pWin = pWin->GetWindow( GetWindowType::Next ); + } + return nullptr; +} + +bool Splitter::ImplSplitterActive() +{ + // is splitter in document or at scrollbar handle ? + + bool bActive = true; + const StyleSettings& rSettings = GetSettings().GetStyleSettings(); + tools::Long nA = rSettings.GetScrollBarSize(); + tools::Long nB = StyleSettings::GetSplitSize(); + + Size aSize = GetOutDev()->GetOutputSize(); + if ( mbHorzSplit ) + { + if( aSize.Width() == nB && aSize.Height() == nA ) + bActive = false; + } + else + { + if( aSize.Width() == nA && aSize.Height() == nB ) + bActive = false; + } + return bActive; +} + +void Splitter::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( rMEvt.GetClicks() == 2 ) + { + if ( mnLastSplitPos != mnSplitPos ) + { + StartSplit(); + Point aPos = rMEvt.GetPosPixel(); + if ( mbHorzSplit ) + aPos.setX( mnLastSplitPos ); + else + aPos.setY( mnLastSplitPos ); + ImplSplitMousePos( aPos ); + tools::Long nTemp = mnSplitPos; + if ( mbHorzSplit ) + SetSplitPosPixel( aPos.X() ); + else + SetSplitPosPixel( aPos.Y() ); + mnLastSplitPos = nTemp; + Split(); + EndSplit(); + } + } + else + StartDrag(); +} + +void Splitter::Tracking( const TrackingEvent& rTEvt ) +{ + if ( rTEvt.IsTrackingEnded() ) + { + if ( !mbDragFull ) + ImplDrawSplitter(); + + if ( !rTEvt.IsTrackingCanceled() ) + { + tools::Long nNewPos; + if ( mbHorzSplit ) + nNewPos = maDragPos.X(); + else + nNewPos = maDragPos.Y(); + if ( nNewPos != mnStartSplitPos ) + { + SetSplitPosPixel( nNewPos ); + mnLastSplitPos = 0; + Split(); + } + EndSplit(); + } + else if ( mbDragFull ) + { + SetSplitPosPixel( mnStartSplitPos ); + Split(); + } + mnStartSplitPos = 0; + } + else + { + //Point aNewPos = mpRefWin->ScreenToOutputPixel( OutputToScreenPixel( rTEvt.GetMouseEvent().GetPosPixel() ) ); + Point aNewPos = mpRefWin->NormalizedScreenToOutputPixel( OutputToNormalizedScreenPixel( rTEvt.GetMouseEvent().GetPosPixel() ) ); + ImplSplitMousePos( aNewPos ); + + if ( mbHorzSplit ) + { + if ( aNewPos.X() == maDragPos.X() ) + return; + } + else + { + if ( aNewPos.Y() == maDragPos.Y() ) + return; + } + + if ( mbDragFull ) + { + maDragPos = aNewPos; + tools::Long nNewPos; + if ( mbHorzSplit ) + nNewPos = maDragPos.X(); + else + nNewPos = maDragPos.Y(); + if ( nNewPos != mnSplitPos ) + { + SetSplitPosPixel( nNewPos ); + mnLastSplitPos = 0; + Split(); + } + + GetParent()->PaintImmediately(); + } + else + { + ImplDrawSplitter(); + maDragPos = aNewPos; + ImplDrawSplitter(); + } + } +} + +void Splitter::ImplKbdTracking( vcl::KeyCode aKeyCode ) +{ + sal_uInt16 nCode = aKeyCode.GetCode(); + if ( nCode == KEY_ESCAPE || nCode == KEY_RETURN ) + { + if( !mbKbdSplitting ) + return; + else + mbKbdSplitting = false; + + if ( nCode != KEY_ESCAPE ) + { + tools::Long nNewPos; + if ( mbHorzSplit ) + nNewPos = maDragPos.X(); + else + nNewPos = maDragPos.Y(); + if ( nNewPos != mnStartSplitPos ) + { + SetSplitPosPixel( nNewPos ); + mnLastSplitPos = 0; + Split(); + } + } + else + { + SetSplitPosPixel( mnStartSplitPos ); + Split(); + EndSplit(); + } + mnStartSplitPos = 0; + } + else + { + Point aNewPos; + Size aSize = mpRefWin->GetOutDev()->GetOutputSize(); + Point aPos = GetPosPixel(); + // depending on the position calc allows continuous moves or snaps to row/columns + // continuous mode is active when position is at the origin or end of the splitter + // otherwise snap mode is active + // default here is snap, holding shift sets continuous mode + if( mbHorzSplit ) + aNewPos = Point( ImplSplitterActive() ? aPos.X() : mnSplitPos, aKeyCode.IsShift() ? 0 : aSize.Height()/2); + else + aNewPos = Point( aKeyCode.IsShift() ? 0 : aSize.Width()/2, ImplSplitterActive() ? aPos.Y() : mnSplitPos ); + + Point aOldWindowPos = GetPosPixel(); + + int maxiter = 500; // avoid endless loop + int delta=0; + int delta_step = mbHorzSplit ? aSize.Width()/10 : aSize.Height()/10; + + // use the specified step size if it was set + if( mnKeyboardStepSize != SPLITTER_DEFAULTSTEPSIZE ) + delta_step = mnKeyboardStepSize; + + while( maxiter-- && aOldWindowPos == GetPosPixel() ) + { + // inc/dec position until application performs changes + // thus a single key press really moves the splitter + if( aKeyCode.IsShift() ) + delta++; + else + delta += delta_step; + + switch( nCode ) + { + case KEY_LEFT: + aNewPos.AdjustX( -delta ); + break; + case KEY_RIGHT: + aNewPos.AdjustX(delta ); + break; + case KEY_UP: + aNewPos.AdjustY( -delta ); + break; + case KEY_DOWN: + aNewPos.AdjustY(delta ); + break; + default: + maxiter = 0; // leave loop + break; + } + ImplSplitMousePos( aNewPos ); + + if ( mbHorzSplit ) + { + if ( aNewPos.X() == maDragPos.X() ) + continue; + } + else + { + if ( aNewPos.Y() == maDragPos.Y() ) + continue; + } + + maDragPos = aNewPos; + tools::Long nNewPos; + if ( mbHorzSplit ) + nNewPos = maDragPos.X(); + else + nNewPos = maDragPos.Y(); + if ( nNewPos != mnSplitPos ) + { + SetSplitPosPixel( nNewPos ); + mnLastSplitPos = 0; + Split(); + } + GetParent()->PaintImmediately(); + } + } +} + +void Splitter::StartSplit() +{ + maStartSplitHdl.Call( this ); +} + +void Splitter::Split() +{ + maSplitHdl.Call( this ); +} + +void Splitter::EndSplit() +{ + maEndSplitHdl.Call( this ); +} + +void Splitter::SetDragRectPixel( const tools::Rectangle& rDragRect, vcl::Window* _pRefWin ) +{ + maDragRect = rDragRect; + if ( !_pRefWin ) + mpRefWin = GetParent(); + else + mpRefWin = _pRefWin; +} + +void Splitter::SetSplitPosPixel( tools::Long nNewPos ) +{ + mnSplitPos = nNewPos; +} + +void Splitter::StartDrag() +{ + if ( IsTracking() ) + return; + + StartSplit(); + + // Tracking starten + StartTracking(); + + // Start-Position ermitteln + maDragPos = mpRefWin->GetPointerPosPixel(); + ImplSplitMousePos( maDragPos ); + if ( mbHorzSplit ) + mnStartSplitPos = maDragPos.X(); + else + mnStartSplitPos = maDragPos.Y(); + + mbDragFull = bool(Application::GetSettings().GetStyleSettings().GetDragFullOptions() & DragFullOptions::Split); + if ( !mbDragFull ) + ImplDrawSplitter(); +} + +void Splitter::ImplStartKbdSplitting() +{ + if( mbKbdSplitting ) + return; + + mbKbdSplitting = true; + + StartSplit(); + + // determine start position + // because we have no mouse position we take either the position + // of the splitter window or the last split position + // the other coordinate is just the center of the reference window + Size aSize = mpRefWin->GetOutDev()->GetOutputSize(); + Point aPos = GetPosPixel(); + if( mbHorzSplit ) + maDragPos = Point( ImplSplitterActive() ? aPos.X() : mnSplitPos, aSize.Height()/2 ); + else + maDragPos = Point( aSize.Width()/2, ImplSplitterActive() ? aPos.Y() : mnSplitPos ); + ImplSplitMousePos( maDragPos ); + if ( mbHorzSplit ) + mnStartSplitPos = maDragPos.X(); + else + mnStartSplitPos = maDragPos.Y(); +} + +void Splitter::ImplRestoreSplitter() +{ + // set splitter in the center of the ref window + StartSplit(); + Size aSize = mpRefWin->GetOutDev()->GetOutputSize(); + Point aPos( aSize.Width()/2 , aSize.Height()/2); + if ( mnLastSplitPos != mnSplitPos && mnLastSplitPos > 5 ) + { + // restore last pos if it was a useful position (>5) + if ( mbHorzSplit ) + aPos.setX( mnLastSplitPos ); + else + aPos.setY( mnLastSplitPos ); + } + + ImplSplitMousePos( aPos ); + tools::Long nTemp = mnSplitPos; + if ( mbHorzSplit ) + SetSplitPosPixel( aPos.X() ); + else + SetSplitPosPixel( aPos.Y() ); + mnLastSplitPos = nTemp; + Split(); + EndSplit(); +} + +void Splitter::GetFocus() +{ + if( !ImplSplitterActive() ) + ImplRestoreSplitter(); + + Invalidate(); +} + +void Splitter::LoseFocus() +{ + if( mbKbdSplitting ) + { + vcl::KeyCode aReturnKey( KEY_RETURN ); + ImplKbdTracking( aReturnKey ); + mbKbdSplitting = false; + } + Invalidate(); +} + +void Splitter::KeyInput( const KeyEvent& rKEvt ) +{ + if( mbInKeyEvent ) + return; + + mbInKeyEvent = true; + + Splitter *pSibling = ImplFindSibling(); + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + sal_uInt16 nCode = aKeyCode.GetCode(); + switch ( nCode ) + { + case KEY_UP: + case KEY_DOWN: + if( !mbHorzSplit ) + { + ImplStartKbdSplitting(); + ImplKbdTracking( aKeyCode ); + } + else + { + if( pSibling ) + { + pSibling->GrabFocus(); + pSibling->KeyInput( rKEvt ); + } + } + break; + case KEY_RIGHT: + case KEY_LEFT: + if( mbHorzSplit ) + { + ImplStartKbdSplitting(); + ImplKbdTracking( aKeyCode ); + } + else + { + if( pSibling ) + { + pSibling->GrabFocus(); + pSibling->KeyInput( rKEvt ); + } + } + break; + + case KEY_DELETE: + if( ImplSplitterActive() ) + { + if( mbKbdSplitting ) + { + vcl::KeyCode aKey( KEY_ESCAPE ); + ImplKbdTracking( aKey ); + } + + StartSplit(); + Point aPos; + if ( mbHorzSplit ) + aPos.setX( 0 ); + else + aPos.setY( 0 ); + ImplSplitMousePos( aPos ); + tools::Long nTemp = mnSplitPos; + if ( mbHorzSplit ) + SetSplitPosPixel( aPos.X() ); + else + SetSplitPosPixel( aPos.Y() ); + mnLastSplitPos = nTemp; + Split(); + EndSplit(); + + // Shift-Del deletes both splitters + if( aKeyCode.IsShift() && pSibling ) + pSibling->KeyInput( rKEvt ); + + GrabFocusToDocument(); + } + break; + + case KEY_ESCAPE: + if( mbKbdSplitting ) + ImplKbdTracking( aKeyCode ); + else + GrabFocusToDocument(); + break; + + case KEY_RETURN: + ImplKbdTracking( aKeyCode ); + GrabFocusToDocument(); + break; + default: // let any key input fix the splitter + Window::KeyInput( rKEvt ); + GrabFocusToDocument(); + break; + } + mbInKeyEvent = false; +} + +void Splitter::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + if( rDCEvt.GetType() != DataChangedEventType::SETTINGS ) + return; + + const AllSettings* pOldSettings = rDCEvt.GetOldSettings(); + if(!pOldSettings) + return; + + Color oldFaceColor = pOldSettings->GetStyleSettings().GetFaceColor(); + Color newFaceColor = Application::GetSettings().GetStyleSettings().GetFaceColor(); + if( oldFaceColor.IsDark() != newFaceColor.IsDark() ) + { + if( newFaceColor.IsDark() ) + SetBackground( ImplWhiteWall() ); + else + SetBackground( ImplBlackWall() ); + } +} + +void Splitter::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rPaintRect) +{ + rRenderContext.DrawRect(rPaintRect); + + tools::Polygon aPoly(rPaintRect); + tools::PolyPolygon aPolyPoly(aPoly); + rRenderContext.DrawTransparent(aPolyPoly, 85); + + if (mbKbdSplitting) + { + LineInfo aInfo( LineStyle::Dash ); + //aInfo.SetDashLen( 2 ); + //aInfo.SetDashCount( 1 ); + aInfo.SetDistance( 1 ); + aInfo.SetDotLen( 2 ); + aInfo.SetDotCount( 3 ); + + rRenderContext.DrawPolyLine( aPoly, aInfo ); + } + else + { + rRenderContext.DrawRect(rPaintRect); + } +} + +Size Splitter::GetOptimalSize() const +{ + return LogicToPixel(Size(3, 3), MapMode(MapUnit::MapAppFont)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/splitwin.cxx b/vcl/source/window/splitwin.cxx new file mode 100644 index 000000000..1cb8389fa --- /dev/null +++ b/vcl/source/window/splitwin.cxx @@ -0,0 +1,2719 @@ +/* -*- 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 <string.h> + +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> + +#include <vcl/event.hxx> +#include <vcl/wall.hxx> +#include <vcl/help.hxx> +#include <vcl/splitwin.hxx> +#include <vcl/settings.hxx> +#include <vcl/ptrstyle.hxx> + +#include <svdata.hxx> +#include <strings.hrc> + + +#define SPLITWIN_SPLITSIZEEX 4 +#define SPLITWIN_SPLITSIZEAUTOHIDE 72 +#define SPLITWIN_SPLITSIZEFADE 72 + +#define SPLIT_HORZ (sal_uInt16(0x0001)) +#define SPLIT_VERT (sal_uInt16(0x0002)) +#define SPLIT_WINDOW (sal_uInt16(0x0004)) +#define SPLIT_NOSPLIT (sal_uInt16(0x8000)) + +namespace { + +class ImplSplitItem +{ +public: + ImplSplitItem(); + + tools::Long mnSize; + tools::Long mnPixSize; + tools::Long mnLeft; + tools::Long mnTop; + tools::Long mnWidth; + tools::Long mnHeight; + tools::Long mnSplitPos; + tools::Long mnSplitSize; + tools::Long mnOldSplitPos; + tools::Long mnOldSplitSize; + tools::Long mnOldWidth; + tools::Long mnOldHeight; + std::unique_ptr<ImplSplitSet> mpSet; + VclPtr<vcl::Window> mpWindow; + VclPtr<vcl::Window> mpOrgParent; + sal_uInt16 mnId; + SplitWindowItemFlags mnBits; + bool mbFixed; + bool mbSubSize; + /// Minimal width or height of the item. -1 means no restriction. + tools::Long mnMinSize; + /// Maximal width or height of the item. -1 means no restriction. + tools::Long mnMaxSize; +}; + +} + +class ImplSplitSet +{ +public: + ImplSplitSet(); + + std::vector< ImplSplitItem > mvItems; + tools::Long mnLastSize; + tools::Long mnSplitSize; + sal_uInt16 mnId; + bool mbCalcPix; +}; + +ImplSplitItem::ImplSplitItem() + : mnSize(0) + , mnPixSize(0) + , mnLeft(0) + , mnTop(0) + , mnWidth(0) + , mnHeight(0) + , mnSplitPos(0) + , mnSplitSize(0) + , mnOldSplitPos(0) + , mnOldSplitSize(0) + , mnOldWidth(0) + , mnOldHeight(0) + , mnId(0) + , mnBits(SplitWindowItemFlags::NONE) + , mbFixed(false) + , mbSubSize(false) + , mnMinSize(-1) + , mnMaxSize(-1) +{ +} + +ImplSplitSet::ImplSplitSet() : + mnLastSize( 0 ), + mnSplitSize( SPLITWIN_SPLITSIZE ), + mnId( 0 ), + mbCalcPix( true ) +{ +} + +/** Check whether the given size is inside the valid range defined by + [rItem.mnMinSize,rItem.mnMaxSize]. When it is not inside it then return + the upper or lower bound, respectively. Otherwise return the given size + unmodified. + Note that either mnMinSize and/or mnMaxSize can be -1 in which case the + size has not lower or upper bound. +*/ +namespace { + tools::Long ValidateSize (const tools::Long nSize, const ImplSplitItem & rItem) + { + if (rItem.mnMinSize>=0 && nSize<rItem.mnMinSize) + return rItem.mnMinSize; + else if (rItem.mnMaxSize>0 && nSize>rItem.mnMaxSize) + return rItem.mnMaxSize; + else + return nSize; + } +} + +static void ImplCalcBorder( WindowAlign eAlign, + tools::Long& rLeft, tools::Long& rTop, + tools::Long& rRight, tools::Long& rBottom ) +{ + switch ( eAlign ) + { + case WindowAlign::Top: + rLeft = 2; + rTop = 2; + rRight = 2; + rBottom = 0; + break; + case WindowAlign::Left: + rLeft = 0; + rTop = 2; + rRight = 2; + rBottom = 2; + break; + case WindowAlign::Bottom: + rLeft = 2; + rTop = 0; + rRight = 2; + rBottom = 2; + break; + default: + rLeft = 0; + rTop = 2; + rRight = 2; + rBottom = 2; + break; + } +} + +void SplitWindow::ImplDrawBorder(vcl::RenderContext& rRenderContext) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + tools::Long nDX = mnDX; + tools::Long nDY = mnDY; + + switch (meAlign) + { + case WindowAlign::Bottom: + break; + case WindowAlign::Top: + break; + case WindowAlign::Left: + break; + default: + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + rRenderContext.DrawLine(Point(0, 0), Point( 0, nDY)); + rRenderContext.DrawLine(Point(0, nDY), Point(nDX, nDY)); + } +} + +void SplitWindow::ImplDrawBorderLine(vcl::RenderContext& rRenderContext) +{ + if (!mbFadeOut) + return; + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + tools::Long nDX = mnDX; + tools::Long nDY = mnDY; + + switch (meAlign) + { + case WindowAlign::Left: + rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() ); + rRenderContext.DrawLine( Point( nDX-SPLITWIN_SPLITSIZEEXLN-1, 1 ), Point( nDX-SPLITWIN_SPLITSIZEEXLN-1, nDY-2 ) ); + + rRenderContext.SetLineColor( rStyleSettings.GetLightColor() ); + rRenderContext.DrawLine( Point( nDX-SPLITWIN_SPLITSIZEEXLN, 1 ), Point( nDX-SPLITWIN_SPLITSIZEEXLN, nDY-3 ) ); + break; + case WindowAlign::Right: + break; + case WindowAlign::Top: + rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() ); + rRenderContext.DrawLine( Point( 0, nDY-SPLITWIN_SPLITSIZEEXLN-1 ), Point( nDX-1, nDY-SPLITWIN_SPLITSIZEEXLN-1 ) ); + + rRenderContext.SetLineColor( rStyleSettings.GetLightColor() ); + rRenderContext.DrawLine( Point( 0, nDY-SPLITWIN_SPLITSIZEEXLN ), Point( nDX-1, nDY-SPLITWIN_SPLITSIZEEXLN ) ); + break; + case WindowAlign::Bottom: + rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() ); + rRenderContext.DrawLine( Point( 0, 5 ), Point( nDX-1, 5 ) ); + + rRenderContext.SetLineColor( rStyleSettings.GetLightColor() ); + rRenderContext.DrawLine( Point( 0, SPLITWIN_SPLITSIZEEXLN ), Point( nDX-1, SPLITWIN_SPLITSIZEEXLN ) ); + break; + } +} + +static ImplSplitSet* ImplFindSet( ImplSplitSet* pSet, sal_uInt16 nId ) +{ + if ( pSet->mnId == nId ) + return pSet; + + std::vector< ImplSplitItem >& rItems = pSet->mvItems; + + for ( const auto& rItem : rItems ) + { + if ( rItem.mnId == nId ) + return rItem.mpSet.get(); + } + + for ( auto& rItem : rItems ) + { + if ( rItem.mpSet ) + { + ImplSplitSet* pFindSet = ImplFindSet( rItem.mpSet.get(), nId ); + if ( pFindSet ) + return pFindSet; + } + } + + return nullptr; +} + +static ImplSplitSet* ImplFindItem( ImplSplitSet* pSet, sal_uInt16 nId, sal_uInt16& rPos ) +{ + size_t nItems = pSet->mvItems.size(); + std::vector< ImplSplitItem >& rItems = pSet->mvItems; + + for ( size_t i = 0; i < nItems; i++ ) + { + if ( rItems[i].mnId == nId ) + { + rPos = i; + return pSet; + } + } + + for ( auto& rItem : rItems ) + { + if ( rItem.mpSet ) + { + ImplSplitSet* pFindSet = ImplFindItem( rItem.mpSet.get(), nId, rPos ); + if ( pFindSet ) + return pFindSet; + } + } + + return nullptr; +} + +static sal_uInt16 ImplFindItem( ImplSplitSet* pSet, vcl::Window* pWindow ) +{ + std::vector< ImplSplitItem >& rItems = pSet->mvItems; + + for ( auto& rItem : rItems ) + { + if ( rItem.mpWindow == pWindow ) + return rItem.mnId; + else + { + if ( rItem.mpSet ) + { + sal_uInt16 nId = ImplFindItem( rItem.mpSet.get(), pWindow ); + if ( nId ) + return nId; + } + } + } + + return 0; +} + +static sal_uInt16 ImplFindItem( ImplSplitSet* pSet, const Point& rPos, + bool bRows, bool bDown = true ) +{ + std::vector< ImplSplitItem >& rItems = pSet->mvItems; + + for ( auto& rItem : rItems ) + { + if ( rItem.mnWidth && rItem.mnHeight ) + { + Point aPoint( rItem.mnLeft, rItem.mnTop ); + Size aSize( rItem.mnWidth, rItem.mnHeight ); + tools::Rectangle aRect( aPoint, aSize ); + if ( bRows ) + { + if ( bDown ) + aRect.AdjustBottom(pSet->mnSplitSize ); + else + aRect.AdjustTop( -(pSet->mnSplitSize) ); + } + else + { + if ( bDown ) + aRect.AdjustRight(pSet->mnSplitSize ); + else + aRect.AdjustLeft( -(pSet->mnSplitSize) ); + } + + if ( aRect.Contains( rPos ) ) + { + if ( rItem.mpSet && !rItem.mpSet->mvItems.empty() ) + { + return ImplFindItem( rItem.mpSet.get(), rPos, + !(rItem.mnBits & SplitWindowItemFlags::ColSet) ); + } + else + return rItem.mnId; + } + } + } + + return 0; +} + +static void ImplCalcSet( ImplSplitSet* pSet, + tools::Long nSetLeft, tools::Long nSetTop, + tools::Long nSetWidth, tools::Long nSetHeight, + bool bRows, bool bDown = true ) +{ + if ( pSet->mvItems.empty() ) + return; + + sal_uInt16 nMins; + sal_uInt16 nCalcItems; + size_t nItems = pSet->mvItems.size(); + sal_uInt16 nAbsItems; + tools::Long nCalcSize; + tools::Long nPos; + tools::Long nMaxPos; + std::vector< ImplSplitItem >& rItems = pSet->mvItems; + bool bEmpty; + + // calculate sizes + if ( bRows ) + nCalcSize = nSetHeight; + else + nCalcSize = nSetWidth; + nCalcSize -= (rItems.size()-1)*pSet->mnSplitSize; + if ( pSet->mbCalcPix || (pSet->mnLastSize != nCalcSize) ) + { + tools::Long nPercentFactor = 10; + tools::Long nRelCount = 0; + tools::Long nPercent = 0; + tools::Long nRelPercent = 0; + tools::Long nAbsSize = 0; + tools::Long nCurSize = 0; + for ( const auto& rItem : rItems ) + { + if ( rItem.mnBits & SplitWindowItemFlags::RelativeSize ) + nRelCount += rItem.mnSize; + else if ( rItem.mnBits & SplitWindowItemFlags::PercentSize ) + nPercent += rItem.mnSize; + else + nAbsSize += rItem.mnSize; + } + // map relative values to percentages (percentage here one tenth of a percent) + nPercent *= nPercentFactor; + if ( nRelCount ) + { + tools::Long nRelPercentBase = 1000; + while ( (nRelCount > nRelPercentBase) && (nPercentFactor < 100000) ) + { + nRelPercentBase *= 10; + nPercentFactor *= 10; + } + if ( nPercent < nRelPercentBase ) + { + nRelPercent = (nRelPercentBase-nPercent)/nRelCount; + nPercent += nRelPercent*nRelCount; + } + else + nRelPercent = 0; + } + if ( !nPercent ) + nPercent = 1; + tools::Long nSizeDelta = nCalcSize-nAbsSize; + for ( auto& rItem : rItems ) + { + if ( rItem.mnBits & SplitWindowItemFlags::RelativeSize ) + { + if ( nSizeDelta <= 0 ) + rItem.mnPixSize = 0; + else + rItem.mnPixSize = (nSizeDelta*rItem.mnSize*nRelPercent)/nPercent; + } + else if ( rItem.mnBits & SplitWindowItemFlags::PercentSize ) + { + if ( nSizeDelta <= 0 ) + rItem.mnPixSize = 0; + else + rItem.mnPixSize = (nSizeDelta*rItem.mnSize*nPercentFactor)/nPercent; + } + else + rItem.mnPixSize = rItem.mnSize; + nCurSize += rItem.mnPixSize; + } + + pSet->mbCalcPix = false; + pSet->mnLastSize = nCalcSize; + + // adapt window + nSizeDelta = nCalcSize-nCurSize; + if ( nSizeDelta ) + { + nAbsItems = 0; + tools::Long nSizeWinSize = 0; + + // first resize absolute items relative + for ( const auto& rItem : rItems ) + { + if ( !(rItem.mnBits & (SplitWindowItemFlags::RelativeSize | SplitWindowItemFlags::PercentSize)) ) + { + nAbsItems++; + nSizeWinSize += rItem.mnPixSize; + } + } + // do not compensate rounding errors here + if ( (nAbsItems < o3tl::make_unsigned(std::abs( nSizeDelta ))) && nSizeWinSize ) + { + tools::Long nNewSizeWinSize = 0; + + for ( auto& rItem : rItems ) + { + if ( !(rItem.mnBits & (SplitWindowItemFlags::RelativeSize | SplitWindowItemFlags::PercentSize)) ) + { + rItem.mnPixSize += (nSizeDelta*rItem.mnPixSize)/nSizeWinSize; + nNewSizeWinSize += rItem.mnPixSize; + } + } + + nSizeDelta -= nNewSizeWinSize-nSizeWinSize; + } + + // compensate rounding errors now + sal_uInt16 j = 0; + nMins = 0; + while ( nSizeDelta && (nItems != nMins) ) + { + // determine which items we can calculate + nCalcItems = 0; + while ( !nCalcItems ) + { + for ( auto& rItem : rItems ) + { + rItem.mbSubSize = false; + + if ( j >= 2 ) + rItem.mbSubSize = true; + else + { + if ( (nSizeDelta > 0) || rItem.mnPixSize ) + { + if ( j >= 1 ) + rItem.mbSubSize = true; + else + { + if ( (j == 0) && (rItem.mnBits & (SplitWindowItemFlags::RelativeSize | SplitWindowItemFlags::PercentSize)) ) + rItem.mbSubSize = true; + } + } + } + + if ( rItem.mbSubSize ) + nCalcItems++; + } + + j++; + } + + // subtract size of individual items + tools::Long nErrorSum = nSizeDelta % nCalcItems; + tools::Long nCurSizeDelta = nSizeDelta / nCalcItems; + nMins = 0; + for ( auto& rItem : rItems ) + { + if ( rItem.mbSubSize ) + { + tools::Long* pSize = &(rItem.mnPixSize); + tools::Long nTempErr; + + if ( nErrorSum ) + { + if ( nErrorSum < 0 ) + nTempErr = -1; + else + nTempErr = 1; + } + else + nTempErr = 0; + + if ( (*pSize+nCurSizeDelta+nTempErr) <= 0 ) + { + tools::Long nTemp = *pSize; + if ( nTemp ) + { + *pSize -= nTemp; + nSizeDelta += nTemp; + } + nMins++; + } + else + { + *pSize += nCurSizeDelta; + nSizeDelta -= nCurSizeDelta; + if ( nTempErr && (*pSize || (nTempErr > 0)) ) + { + *pSize += nTempErr; + nSizeDelta -= nTempErr; + nErrorSum -= nTempErr; + } + } + } + } + } + } + } + + // calculate maximum size + if ( bRows ) + { + nPos = nSetTop; + if ( !bDown ) + nMaxPos = nSetTop-nSetHeight; + else + nMaxPos = nSetTop+nSetHeight; + } + else + { + nPos = nSetLeft; + if ( !bDown ) + nMaxPos = nSetLeft-nSetWidth; + else + nMaxPos = nSetLeft+nSetWidth; + } + + // order windows and adapt values + for ( size_t i = 0; i < nItems; i++ ) + { + rItems[i].mnOldSplitPos = rItems[i].mnSplitPos; + rItems[i].mnOldSplitSize = rItems[i].mnSplitSize; + rItems[i].mnOldWidth = rItems[i].mnWidth; + rItems[i].mnOldHeight = rItems[i].mnHeight; + + bEmpty = false; + if ( bDown ) + { + if ( nPos+rItems[i].mnPixSize > nMaxPos ) + bEmpty = true; + } + else + { + nPos -= rItems[i].mnPixSize; + if ( nPos < nMaxPos ) + bEmpty = true; + } + + if ( bEmpty ) + { + rItems[i].mnWidth = 0; + rItems[i].mnHeight = 0; + rItems[i].mnSplitSize = 0; + } + else + { + if ( bRows ) + { + rItems[i].mnLeft = nSetLeft; + rItems[i].mnTop = nPos; + rItems[i].mnWidth = nSetWidth; + rItems[i].mnHeight = rItems[i].mnPixSize; + } + else + { + rItems[i].mnLeft = nPos; + rItems[i].mnTop = nSetTop; + rItems[i].mnWidth = rItems[i].mnPixSize; + rItems[i].mnHeight = nSetHeight; + } + + if ( i > nItems-1 ) + rItems[i].mnSplitSize = 0; + else + { + rItems[i].mnSplitSize = pSet->mnSplitSize; + if ( bDown ) + { + rItems[i].mnSplitPos = nPos+rItems[i].mnPixSize; + if ( rItems[i].mnSplitPos+rItems[i].mnSplitSize > nMaxPos ) + rItems[i].mnSplitSize = nMaxPos-rItems[i].mnSplitPos; + } + else + { + rItems[i].mnSplitPos = nPos-pSet->mnSplitSize; + if ( rItems[i].mnSplitPos < nMaxPos ) + rItems[i].mnSplitSize = rItems[i].mnSplitPos+pSet->mnSplitSize-nMaxPos; + } + } + } + + if ( !bDown ) + nPos -= pSet->mnSplitSize; + else + nPos += rItems[i].mnPixSize+pSet->mnSplitSize; + } + + // calculate Sub-Set's + for ( auto& rItem : rItems ) + { + if ( rItem.mpSet && rItem.mnWidth && rItem.mnHeight ) + { + ImplCalcSet( rItem.mpSet.get(), + rItem.mnLeft, rItem.mnTop, + rItem.mnWidth, rItem.mnHeight, + !(rItem.mnBits & SplitWindowItemFlags::ColSet) ); + } + } + + // set fixed + for ( auto& rItem : rItems ) + { + rItem.mbFixed = false; + if ( rItem.mnBits & SplitWindowItemFlags::Fixed ) + rItem.mbFixed = true; + else + { + // this item is also fixed if Child-Set is available, + // if a child is fixed + if ( rItem.mpSet ) + { + for ( auto const & j: rItem.mpSet->mvItems ) + { + if ( j.mbFixed ) + { + rItem.mbFixed = true; + break; + } + } + } + } + } +} + +void SplitWindow::ImplCalcSet2( SplitWindow* pWindow, ImplSplitSet* pSet, bool bHide, + bool bRows ) +{ + std::vector< ImplSplitItem >& rItems = pSet->mvItems; + + if ( pWindow->IsReallyVisible() && pWindow->IsUpdateMode() && pWindow->mbInvalidate ) + { + for ( const auto& rItem : rItems ) + { + if ( rItem.mnSplitSize ) + { + // invalidate all, if applicable or only a small part + if ( (rItem.mnOldSplitPos != rItem.mnSplitPos) || + (rItem.mnOldSplitSize != rItem.mnSplitSize) || + (rItem.mnOldWidth != rItem.mnWidth) || + (rItem.mnOldHeight != rItem.mnHeight) ) + { + tools::Rectangle aRect; + + // invalidate old rectangle + if ( bRows ) + { + aRect.SetLeft( rItem.mnLeft ); + aRect.SetRight( rItem.mnLeft+rItem.mnOldWidth-1 ); + aRect.SetTop( rItem.mnOldSplitPos ); + aRect.SetBottom( aRect.Top() + rItem.mnOldSplitSize ); + } + else + { + aRect.SetTop( rItem.mnTop ); + aRect.SetBottom( rItem.mnTop+rItem.mnOldHeight-1 ); + aRect.SetLeft( rItem.mnOldSplitPos ); + aRect.SetRight( aRect.Left() + rItem.mnOldSplitSize ); + } + pWindow->Invalidate( aRect ); + // invalidate new rectangle + if ( bRows ) + { + aRect.SetLeft( rItem.mnLeft ); + aRect.SetRight( rItem.mnLeft+rItem.mnWidth-1 ); + aRect.SetTop( rItem.mnSplitPos ); + aRect.SetBottom( aRect.Top() + rItem.mnSplitSize ); + } + else + { + aRect.SetTop( rItem.mnTop ); + aRect.SetBottom( rItem.mnTop+rItem.mnHeight-1 ); + aRect.SetLeft( rItem.mnSplitPos ); + aRect.SetRight( aRect.Left() + rItem.mnSplitSize ); + } + pWindow->Invalidate( aRect ); + + // invalidate complete set, as these areas + // are not cluttered by windows + if ( rItem.mpSet && rItem.mpSet->mvItems.empty() ) + { + aRect.SetLeft( rItem.mnLeft ); + aRect.SetTop( rItem.mnTop ); + aRect.SetRight( rItem.mnLeft+rItem.mnWidth-1 ); + aRect.SetBottom( rItem.mnTop+rItem.mnHeight-1 ); + pWindow->Invalidate( aRect ); + } + } + } + } + } + + // position windows + for ( auto& rItem : rItems ) + { + if ( rItem.mpSet ) + { + bool bTempHide = bHide; + if ( !rItem.mnWidth || !rItem.mnHeight ) + bTempHide = true; + ImplCalcSet2( pWindow, rItem.mpSet.get(), bTempHide, + !(rItem.mnBits & SplitWindowItemFlags::ColSet) ); + } + else + { + if ( rItem.mnWidth && rItem.mnHeight && !bHide ) + { + Point aPos( rItem.mnLeft, rItem.mnTop ); + Size aSize( rItem.mnWidth, rItem.mnHeight ); + rItem.mpWindow->SetPosSizePixel( aPos, aSize ); + } + else + rItem.mpWindow->Hide(); + } + } + + // show windows and reset flag + for ( auto& rItem : rItems ) + { + if ( rItem.mpWindow && rItem.mnWidth && rItem.mnHeight && !bHide ) + rItem.mpWindow->Show(); + } +} + +static void ImplCalcLogSize( std::vector< ImplSplitItem > & rItems, size_t nItems ) +{ + // update original sizes + size_t i; + tools::Long nRelSize = 0; + tools::Long nPerSize = 0; + + for ( i = 0; i < nItems; i++ ) + { + if ( rItems[i].mnBits & SplitWindowItemFlags::RelativeSize ) + nRelSize += rItems[i].mnPixSize; + else if ( rItems[i].mnBits & SplitWindowItemFlags::PercentSize ) + nPerSize += rItems[i].mnPixSize; + } + nPerSize += nRelSize; + for ( i = 0; i < nItems; i++ ) + { + if ( rItems[i].mnBits & SplitWindowItemFlags::RelativeSize ) + { + if ( nRelSize ) + rItems[i].mnSize = (rItems[i].mnPixSize+(nRelSize/2))/nRelSize; + else + rItems[i].mnSize = 1; + } + else if ( rItems[i].mnBits & SplitWindowItemFlags::PercentSize ) + { + if ( nPerSize ) + rItems[i].mnSize = (rItems[i].mnPixSize*100)/nPerSize; + else + rItems[i].mnSize = 1; + } + else + rItems[i].mnSize = rItems[i].mnPixSize; + } +} + +static void ImplDrawSplit(vcl::RenderContext& rRenderContext, ImplSplitSet* pSet, bool bRows, bool bDown) +{ + if (pSet->mvItems.empty()) + return; + + size_t nItems = pSet->mvItems.size(); + tools::Long nPos; + tools::Long nTop; + tools::Long nBottom; + std::vector< ImplSplitItem >& rItems = pSet->mvItems; + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + for (size_t i = 0; i < nItems-1; i++) + { + if (rItems[i].mnSplitSize) + { + nPos = rItems[i].mnSplitPos; + + tools::Long nItemSplitSize = rItems[i].mnSplitSize; + tools::Long nSplitSize = pSet->mnSplitSize; + if (bRows) + { + nTop = rItems[i].mnLeft; + nBottom = rItems[i].mnLeft+rItems[i].mnWidth-1; + + if (bDown || (nItemSplitSize >= nSplitSize)) + { + rRenderContext.SetLineColor(rStyleSettings.GetLightColor()); + rRenderContext.DrawLine(Point(nTop, nPos + 1), Point(nBottom, nPos + 1)); + } + nPos += nSplitSize-2; + if ((!bDown && (nItemSplitSize >= 2)) || + (bDown && (nItemSplitSize >= nSplitSize - 1))) + { + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + rRenderContext.DrawLine(Point(nTop, nPos), Point(nBottom, nPos)); + } + nPos++; + if (!bDown || (nItemSplitSize >= nSplitSize)) + { + rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor()); + rRenderContext.DrawLine(Point(nTop, nPos), Point(nBottom, nPos)); + } + } + else + { + nTop = rItems[i].mnTop; + nBottom = rItems[i].mnTop+pSet->mvItems[i].mnHeight-1; + + if (bDown || (nItemSplitSize >= nSplitSize)) + { + rRenderContext.SetLineColor(rStyleSettings.GetLightColor()); + rRenderContext.DrawLine(Point(nPos + 1, nTop), Point(nPos+1, nBottom)); + } + nPos += pSet->mnSplitSize - 2; + if ((!bDown && (nItemSplitSize >= 2)) || + (bDown && (nItemSplitSize >= nSplitSize - 1))) + { + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + rRenderContext.DrawLine(Point(nPos, nTop), Point(nPos, nBottom)); + } + nPos++; + if (!bDown || (nItemSplitSize >= nSplitSize)) + { + rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor()); + rRenderContext.DrawLine(Point(nPos, nTop), Point(nPos, nBottom)); + } + } + } + } + + for ( auto& rItem : rItems ) + { + if (rItem.mpSet && rItem.mnWidth && rItem.mnHeight) + { + ImplDrawSplit(rRenderContext, rItem.mpSet.get(), !(rItem.mnBits & SplitWindowItemFlags::ColSet), true/*bDown*/); + } + } +} + +sal_uInt16 SplitWindow::ImplTestSplit( ImplSplitSet* pSet, const Point& rPos, + tools::Long& rMouseOff, ImplSplitSet** ppFoundSet, sal_uInt16& rFoundPos, + bool bRows ) +{ + if ( pSet->mvItems.empty() ) + return 0; + + sal_uInt16 nSplitTest; + size_t nItems = pSet->mvItems.size(); + tools::Long nMPos1; + tools::Long nMPos2; + tools::Long nPos; + tools::Long nTop; + tools::Long nBottom; + std::vector< ImplSplitItem >& rItems = pSet->mvItems; + + if ( bRows ) + { + nMPos1 = rPos.X(); + nMPos2 = rPos.Y(); + } + else + { + nMPos1 = rPos.Y(); + nMPos2 = rPos.X(); + } + + for ( size_t i = 0; i < nItems-1; i++ ) + { + if ( rItems[i].mnSplitSize ) + { + if ( bRows ) + { + nTop = rItems[i].mnLeft; + nBottom = rItems[i].mnLeft+rItems[i].mnWidth-1; + } + else + { + nTop = rItems[i].mnTop; + nBottom = rItems[i].mnTop+rItems[i].mnHeight-1; + } + nPos = rItems[i].mnSplitPos; + + if ( (nMPos1 >= nTop) && (nMPos1 <= nBottom) && + (nMPos2 >= nPos) && (nMPos2 <= nPos+rItems[i].mnSplitSize) ) + { + if ( !rItems[i].mbFixed && !rItems[i+1].mbFixed ) + { + rMouseOff = nMPos2-nPos; + *ppFoundSet = pSet; + rFoundPos = i; + if ( bRows ) + return SPLIT_VERT; + else + return SPLIT_HORZ; + } + else + return SPLIT_NOSPLIT; + } + } + } + + for ( auto& rItem : rItems ) + { + if ( rItem.mpSet ) + { + nSplitTest = ImplTestSplit( rItem.mpSet.get(), rPos, + rMouseOff, ppFoundSet, rFoundPos, + !(rItem.mnBits & SplitWindowItemFlags::ColSet) ); + if ( nSplitTest ) + return nSplitTest; + } + } + + return 0; +} + +sal_uInt16 SplitWindow::ImplTestSplit( const SplitWindow* pWindow, const Point& rPos, + tools::Long& rMouseOff, ImplSplitSet** ppFoundSet, sal_uInt16& rFoundPos ) +{ + // Resizable SplitWindow should be treated different + if ( pWindow->mnWinStyle & WB_SIZEABLE ) + { + tools::Long nTPos; + tools::Long nPos; + tools::Long nBorder; + + if ( pWindow->mbHorz ) + { + if ( pWindow->mbBottomRight ) + { + nBorder = pWindow->mnBottomBorder; + nPos = 0; + } + else + { + nBorder = pWindow->mnTopBorder; + nPos = pWindow->mnDY-nBorder; + } + nTPos = rPos.Y(); + } + else + { + if ( pWindow->mbBottomRight ) + { + nBorder = pWindow->mnRightBorder; + nPos = 0; + } + else + { + nBorder = pWindow->mnLeftBorder; + nPos = pWindow->mnDX-nBorder; + } + nTPos = rPos.X(); + } + tools::Long nSplitSize = pWindow->mpMainSet->mnSplitSize-2; + if (pWindow->mbFadeOut) + nSplitSize += SPLITWIN_SPLITSIZEEXLN; + if ( !pWindow->mbBottomRight ) + nPos -= nSplitSize; + if ( (nTPos >= nPos) && (nTPos <= nPos+nSplitSize+nBorder) ) + { + rMouseOff = nTPos-nPos; + *ppFoundSet = pWindow->mpMainSet.get(); + if ( !pWindow->mpMainSet->mvItems.empty() ) + rFoundPos = pWindow->mpMainSet->mvItems.size() - 1; + else + rFoundPos = 0; + if ( pWindow->mbHorz ) + return SPLIT_VERT | SPLIT_WINDOW; + else + return SPLIT_HORZ | SPLIT_WINDOW; + } + } + + return ImplTestSplit( pWindow->mpMainSet.get(), rPos, rMouseOff, ppFoundSet, rFoundPos, + pWindow->mbHorz ); +} + +void SplitWindow::ImplDrawSplitTracking(const Point& rPos) +{ + tools::Rectangle aRect; + + if (mnSplitTest & SPLIT_HORZ) + { + aRect.SetTop( maDragRect.Top() ); + aRect.SetBottom( maDragRect.Bottom() ); + aRect.SetLeft( rPos.X() ); + aRect.SetRight( aRect.Left() + mpSplitSet->mnSplitSize - 1 ); + if (!(mnWinStyle & WB_NOSPLITDRAW)) + aRect.AdjustRight( -1 ); + if ((mnSplitTest & SPLIT_WINDOW) && mbFadeOut) + { + aRect.AdjustLeft(SPLITWIN_SPLITSIZEEXLN ); + aRect.AdjustRight(SPLITWIN_SPLITSIZEEXLN ); + } + } + else + { + aRect.SetLeft( maDragRect.Left() ); + aRect.SetRight( maDragRect.Right() ); + aRect.SetTop( rPos.Y() ); + aRect.SetBottom( aRect.Top() + mpSplitSet->mnSplitSize - 1 ); + if (!(mnWinStyle & WB_NOSPLITDRAW)) + aRect.AdjustBottom( -1 ); + if ((mnSplitTest & SPLIT_WINDOW) && mbFadeOut) + { + aRect.AdjustTop(SPLITWIN_SPLITSIZEEXLN ); + aRect.AdjustBottom(SPLITWIN_SPLITSIZEEXLN ); + } + } + ShowTracking(aRect, ShowTrackFlags::Split); +} + +void SplitWindow::ImplInit( vcl::Window* pParent, WinBits nStyle ) +{ + mpMainSet.reset(new ImplSplitSet()); + mpBaseSet = mpMainSet.get(); + mpSplitSet = nullptr; + mpLastSizes = nullptr; + mnDX = 0; + mnDY = 0; + mnLeftBorder = 0; + mnTopBorder = 0; + mnRightBorder = 0; + mnBottomBorder = 0; + mnMaxSize = 0; + mnMouseOff = 0; + meAlign = WindowAlign::Top; + mnWinStyle = nStyle; + mnSplitTest = 0; + mnSplitPos = 0; + mnMouseModifier = 0; + mnMStartPos = 0; + mnMSplitPos = 0; + mbDragFull = false; + mbHorz = true; + mbBottomRight = false; + mbCalc = false; + mbRecalc = true; + mbInvalidate = true; + mbFadeIn = false; + mbFadeOut = false; + mbFadeInDown = false; + mbFadeOutDown = false; + mbFadeInPressed = false; + mbFadeOutPressed = false; + mbFadeNoButtonMode = false; + + if ( nStyle & WB_NOSPLITDRAW ) + { + mpMainSet->mnSplitSize -= 2; + mbInvalidate = false; + } + + if ( nStyle & WB_BORDER ) + { + ImplCalcBorder( meAlign, mnLeftBorder, mnTopBorder, + mnRightBorder, mnBottomBorder ); + } + else + { + mnLeftBorder = 0; + mnTopBorder = 0; + mnRightBorder = 0; + mnBottomBorder = 0; + } + + DockingWindow::ImplInit( pParent, (nStyle | WB_CLIPCHILDREN) & ~(WB_BORDER | WB_SIZEABLE) ); + + ImplInitSettings(); +} + +void SplitWindow::ImplInitSettings() +{ + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + + Color aColor; + if ( IsControlBackground() ) + aColor = GetControlBackground(); + else if ( Window::GetStyle() & WB_3DLOOK ) + aColor = rStyleSettings.GetFaceColor(); + else + aColor = rStyleSettings.GetWindowColor(); + SetBackground( aColor ); +} + +SplitWindow::SplitWindow( vcl::Window* pParent, WinBits nStyle ) : + DockingWindow( WindowType::SPLITWINDOW, "vcl::SplitWindow maLayoutIdle" ) +{ + ImplInit( pParent, nStyle ); +} + +SplitWindow::~SplitWindow() +{ + disposeOnce(); +} + +void SplitWindow::dispose() +{ + // delete Sets + mpMainSet.reset(); + DockingWindow::dispose(); +} + +void SplitWindow::ImplSetWindowSize( tools::Long nDelta ) +{ + if ( !nDelta ) + return; + + Size aSize = GetSizePixel(); + switch ( meAlign ) + { + case WindowAlign::Top: + aSize.AdjustHeight(nDelta ); + SetSizePixel( aSize ); + break; + case WindowAlign::Bottom: + { + maDragRect.AdjustTop(nDelta ); + Point aPos = GetPosPixel(); + aPos.AdjustY( -nDelta ); + aSize.AdjustHeight(nDelta ); + SetPosSizePixel( aPos, aSize ); + break; + } + case WindowAlign::Left: + aSize.AdjustWidth(nDelta ); + SetSizePixel( aSize ); + break; + case WindowAlign::Right: + default: + { + maDragRect.AdjustLeft(nDelta ); + Point aPos = GetPosPixel(); + aPos.AdjustX( -nDelta ); + aSize.AdjustWidth(nDelta ); + SetPosSizePixel( aPos, aSize ); + break; + } + } + + SplitResize(); +} + +Size SplitWindow::CalcLayoutSizePixel( const Size& aNewSize ) +{ + Size aSize( aNewSize ); + tools::Long nSplitSize = mpMainSet->mnSplitSize-2; + + if (mbFadeOut) + nSplitSize += SPLITWIN_SPLITSIZEEXLN; + + // if the window is sizeable and if it does not contain a relative window, + // the size is determined according to MainSet + if ( mnWinStyle & WB_SIZEABLE ) + { + tools::Long nCalcSize = 0; + std::vector< ImplSplitItem* >::size_type i; + + for ( i = 0; i < mpMainSet->mvItems.size(); i++ ) + { + if ( mpMainSet->mvItems[i].mnBits & (SplitWindowItemFlags::RelativeSize | SplitWindowItemFlags::PercentSize) ) + break; + else + nCalcSize += mpMainSet->mvItems[i].mnSize; + } + + if ( i == mpMainSet->mvItems.size() ) + { + tools::Long nDelta = 0; + tools::Long nCurSize; + + if ( mbHorz ) + nCurSize = aNewSize.Height()-mnTopBorder-mnBottomBorder; + else + nCurSize = aNewSize.Width()-mnLeftBorder-mnRightBorder; + nCurSize -= nSplitSize; + nCurSize -= (mpMainSet->mvItems.size()-1)*mpMainSet->mnSplitSize; + + nDelta = nCalcSize-nCurSize; + if ( !nDelta ) + return aSize; + + switch ( meAlign ) + { + case WindowAlign::Top: + aSize.AdjustHeight(nDelta ); + break; + case WindowAlign::Bottom: + aSize.AdjustHeight(nDelta ); + break; + case WindowAlign::Left: + aSize.AdjustWidth(nDelta ); + break; + case WindowAlign::Right: + default: + aSize.AdjustWidth(nDelta ); + break; + } + } + } + + return aSize; +} + +void SplitWindow::ImplCalcLayout() +{ + if ( !mbCalc || !mbRecalc || mpMainSet->mvItems.empty() ) + return; + + tools::Long nSplitSize = mpMainSet->mnSplitSize-2; + if (mbFadeOut) + nSplitSize += SPLITWIN_SPLITSIZEEXLN; + + // if the window is sizeable and if it does not contain a relative window, + // the size is determined according to MainSet + if ( mnWinStyle & WB_SIZEABLE ) + { + tools::Long nCalcSize = 0; + std::vector<ImplSplitItem *>::size_type i; + + for ( i = 0; i < mpMainSet->mvItems.size(); i++ ) + { + if ( mpMainSet->mvItems[i].mnBits & (SplitWindowItemFlags::RelativeSize | SplitWindowItemFlags::PercentSize) ) + break; + else + nCalcSize += mpMainSet->mvItems[i].mnSize; + } + + if ( i == mpMainSet->mvItems.size() ) + { + tools::Long nCurSize; + if ( mbHorz ) + nCurSize = mnDY-mnTopBorder-mnBottomBorder; + else + nCurSize = mnDX-mnLeftBorder-mnRightBorder; + nCurSize -= nSplitSize; + nCurSize -= (mpMainSet->mvItems.size()-1)*mpMainSet->mnSplitSize; + + mbRecalc = false; + ImplSetWindowSize( nCalcSize-nCurSize ); + mbRecalc = true; + } + } + + if ( (mnDX <= 0) || (mnDY <= 0) ) + return; + + // pre-calculate sizes/position + tools::Long nL; + tools::Long nT; + tools::Long nW; + tools::Long nH; + + if ( mbHorz ) + { + if ( mbBottomRight ) + nT = mnDY-mnBottomBorder; + else + nT = mnTopBorder; + nL = mnLeftBorder; + } + else + { + if ( mbBottomRight ) + nL = mnDX-mnRightBorder; + else + nL = mnLeftBorder; + nT = mnTopBorder; + } + nW = mnDX-mnLeftBorder-mnRightBorder; + nH = mnDY-mnTopBorder-mnBottomBorder; + if ( mnWinStyle & WB_SIZEABLE ) + { + if ( mbHorz ) + nH -= nSplitSize; + else + nW -= nSplitSize; + } + + // calculate sets recursive + ImplCalcSet( mpMainSet.get(), nL, nT, nW, nH, mbHorz, !mbBottomRight ); + ImplCalcSet2( this, mpMainSet.get(), false, mbHorz ); + mbCalc = false; +} + +void SplitWindow::ImplUpdate() +{ + mbCalc = true; + + if ( IsReallyShown() && IsUpdateMode() && mbRecalc ) + { + if ( !mpMainSet->mvItems.empty() ) + ImplCalcLayout(); + else + Invalidate(); + } +} + +void SplitWindow::ImplSplitMousePos( Point& rMousePos ) +{ + if ( mnSplitTest & SPLIT_HORZ ) + { + rMousePos.AdjustX( -mnMouseOff ); + if ( rMousePos.X() < maDragRect.Left() ) + rMousePos.setX( maDragRect.Left() ); + else if ( rMousePos.X()+mpSplitSet->mnSplitSize+1 > maDragRect.Right() ) + rMousePos.setX( maDragRect.Right()-mpSplitSet->mnSplitSize+1 ); + // store in screen coordinates due to FullDrag + mnMSplitPos = OutputToScreenPixel( rMousePos ).X(); + } + else + { + rMousePos.AdjustY( -mnMouseOff ); + if ( rMousePos.Y() < maDragRect.Top() ) + rMousePos.setY( maDragRect.Top() ); + else if ( rMousePos.Y()+mpSplitSet->mnSplitSize+1 > maDragRect.Bottom() ) + rMousePos.setY( maDragRect.Bottom()-mpSplitSet->mnSplitSize+1 ); + mnMSplitPos = OutputToScreenPixel( rMousePos ).Y(); + } +} + +void SplitWindow::ImplGetButtonRect( tools::Rectangle& rRect, bool bTest ) const +{ + tools::Long nSplitSize = mpMainSet->mnSplitSize-1; + if (mbFadeOut || mbFadeIn) + nSplitSize += SPLITWIN_SPLITSIZEEX; + + tools::Long nButtonSize = 0; + if ( mbFadeIn ) + nButtonSize += SPLITWIN_SPLITSIZEFADE+1; + if ( mbFadeOut ) + nButtonSize += SPLITWIN_SPLITSIZEFADE+1; + tools::Long nCenterEx = 0; + if ( mbHorz ) + nCenterEx += ((mnDX-mnLeftBorder-mnRightBorder)-nButtonSize)/2; + else + nCenterEx += ((mnDY-mnTopBorder-mnBottomBorder)-nButtonSize)/2; + tools::Long nEx = 0; + if ( nCenterEx > 0 ) + nEx += nCenterEx; + + switch ( meAlign ) + { + case WindowAlign::Top: + rRect.SetLeft( mnLeftBorder+nEx ); + rRect.SetTop( mnDY-mnBottomBorder-nSplitSize ); + rRect.SetRight( rRect.Left()+SPLITWIN_SPLITSIZEAUTOHIDE ); + rRect.SetBottom( mnDY-mnBottomBorder-1 ); + if ( bTest ) + { + rRect.AdjustTop( -mnTopBorder ); + rRect.AdjustBottom(mnBottomBorder ); + } + break; + case WindowAlign::Bottom: + rRect.SetLeft( mnLeftBorder+nEx ); + rRect.SetTop( mnTopBorder ); + rRect.SetRight( rRect.Left()+SPLITWIN_SPLITSIZEAUTOHIDE ); + rRect.SetBottom( mnTopBorder+nSplitSize-1 ); + if ( bTest ) + { + rRect.AdjustTop( -mnTopBorder ); + rRect.AdjustBottom(mnBottomBorder ); + } + break; + case WindowAlign::Left: + rRect.SetLeft( mnDX-mnRightBorder-nSplitSize ); + rRect.SetTop( mnTopBorder+nEx ); + rRect.SetRight( mnDX-mnRightBorder-1 ); + rRect.SetBottom( rRect.Top()+SPLITWIN_SPLITSIZEAUTOHIDE ); + if ( bTest ) + { + rRect.AdjustLeft( -mnLeftBorder ); + rRect.AdjustRight(mnRightBorder ); + } + break; + case WindowAlign::Right: + rRect.SetLeft( mnLeftBorder ); + rRect.SetTop( mnTopBorder+nEx ); + rRect.SetRight( mnLeftBorder+nSplitSize-1 ); + rRect.SetBottom( rRect.Top()+SPLITWIN_SPLITSIZEAUTOHIDE ); + if ( bTest ) + { + rRect.AdjustLeft( -mnLeftBorder ); + rRect.AdjustRight(mnRightBorder ); + } + break; + } +} + +void SplitWindow::ImplGetFadeInRect( tools::Rectangle& rRect, bool bTest ) const +{ + tools::Rectangle aRect; + + if ( mbFadeIn ) + ImplGetButtonRect( aRect, bTest ); + + rRect = aRect; +} + +void SplitWindow::ImplGetFadeOutRect( tools::Rectangle& rRect ) const +{ + tools::Rectangle aRect; + + if ( mbFadeOut ) + ImplGetButtonRect( aRect, false ); + + rRect = aRect; +} + +void SplitWindow::ImplDrawGrip(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, bool bHorizontal, bool bLeft) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + Color aColor; + + if (rRect.Contains(GetPointerPosPixel())) + { + vcl::RenderTools::DrawSelectionBackground(rRenderContext, *this, rRect, 2, false, false, false); + + aColor = rStyleSettings.GetDarkShadowColor(); + } + else + { + rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor()); + rRenderContext.SetFillColor(rStyleSettings.GetDarkShadowColor()); + + rRenderContext.DrawRect(rRect); + + aColor = rStyleSettings.GetFaceColor(); + } + + AntialiasingFlags nAA = rRenderContext.GetAntialiasing(); + rRenderContext.SetAntialiasing(nAA | AntialiasingFlags::PixelSnapHairline | AntialiasingFlags::Enable); + + tools::Long nWidth = rRect.getWidth(); + tools::Long nWidthHalf = nWidth / 2; + tools::Long nHeight = rRect.getHeight(); + tools::Long nHeightHalf = nHeight / 2; + + tools::Long nLeft = rRect.Left(); + tools::Long nRight = rRect.Right(); + tools::Long nTop = rRect.Top(); + tools::Long nBottom = rRect.Bottom(); + tools::Long nMargin = 1; + + rRenderContext.SetLineColor(aColor); + rRenderContext.SetFillColor(aColor); + + tools::Polygon aPoly(3); + + if (bHorizontal) + { + tools::Long nCenter = nLeft + nWidthHalf; + + if (bLeft) + { + aPoly.SetPoint(Point(nCenter, nTop + nMargin), 0); + aPoly.SetPoint(Point(nCenter - nHeightHalf, nBottom - nMargin), 1); + aPoly.SetPoint(Point(nCenter - nHeightHalf, nBottom - nMargin), 2); + } + else + { + aPoly.SetPoint(Point(nCenter, nBottom - nMargin), 0); + aPoly.SetPoint(Point(nCenter - nHeightHalf, nTop + nMargin), 1); + aPoly.SetPoint(Point(nCenter + nHeightHalf, nTop + nMargin), 2); + } + rRenderContext.DrawPolygon(aPoly); + } + else + { + tools::Long nCenter = nTop + nHeightHalf; + + if (bLeft) + { + aPoly.SetPoint(Point(nLeft + nMargin, nCenter), 0); + aPoly.SetPoint(Point(nRight - nMargin, nCenter - nWidthHalf), 1); + aPoly.SetPoint(Point(nRight - nMargin, nCenter + nWidthHalf), 2); + } + else + { + aPoly.SetPoint(Point(nRight - nMargin, nCenter), 0); + aPoly.SetPoint(Point(nLeft + nMargin, nCenter - nWidthHalf), 1); + aPoly.SetPoint(Point(nLeft + nMargin, nCenter + nWidthHalf), 2); + } + rRenderContext.DrawPolygon(aPoly); + } + + rRenderContext.SetAntialiasing(nAA); +} + +void SplitWindow::ImplDrawFadeIn(vcl::RenderContext& rRenderContext) +{ + if (!mbFadeIn) + return; + + tools::Rectangle aTempRect; + ImplGetFadeInRect(aTempRect); + + bool bLeft = true; + switch (meAlign) + { + case WindowAlign::Top: + case WindowAlign::Left: + bLeft = false; + break; + case WindowAlign::Bottom: + case WindowAlign::Right: + default: + bLeft = true; + break; + } + + ImplDrawGrip(rRenderContext, aTempRect, (meAlign == WindowAlign::Top) || (meAlign == WindowAlign::Bottom), bLeft); +} + +void SplitWindow::ImplDrawFadeOut(vcl::RenderContext& rRenderContext) +{ + if (!mbFadeOut) + return; + + tools::Rectangle aTempRect; + ImplGetFadeOutRect(aTempRect); + + bool bLeft = true; + switch (meAlign) + { + case WindowAlign::Bottom: + case WindowAlign::Right: + bLeft = false; + break; + case WindowAlign::Top: + case WindowAlign::Left: + default: + bLeft = true; + break; + } + + ImplDrawGrip(rRenderContext, aTempRect, (meAlign == WindowAlign::Top) || (meAlign == WindowAlign::Bottom), bLeft); +} + +void SplitWindow::ImplStartSplit( const MouseEvent& rMEvt ) +{ + Point aMousePosPixel = rMEvt.GetPosPixel(); + mnSplitTest = ImplTestSplit( this, aMousePosPixel, mnMouseOff, &mpSplitSet, mnSplitPos ); + + if ( !mnSplitTest || (mnSplitTest & SPLIT_NOSPLIT) ) + return; + + ImplSplitItem* pSplitItem; + tools::Long nCurMaxSize; + bool bPropSmaller; + + mnMouseModifier = rMEvt.GetModifier(); + bPropSmaller = (mnMouseModifier & KEY_SHIFT) && (o3tl::make_unsigned(mnSplitPos+1) < mpSplitSet->mvItems.size()); + + // here we can set the maximum size + StartSplit(); + + if ( mnMaxSize ) + nCurMaxSize = mnMaxSize; + else + { + Size aSize = GetParent()->GetOutputSizePixel(); + if ( mbHorz ) + nCurMaxSize = aSize.Height(); + else + nCurMaxSize = aSize.Width(); + } + + if ( !mpSplitSet->mvItems.empty() ) + { + bool bDown = true; + if ( (mpSplitSet == mpMainSet.get()) && mbBottomRight ) + bDown = false; + + pSplitItem = &mpSplitSet->mvItems[mnSplitPos]; + maDragRect.SetLeft( pSplitItem->mnLeft ); + maDragRect.SetTop( pSplitItem->mnTop ); + maDragRect.SetRight( pSplitItem->mnLeft+pSplitItem->mnWidth-1 ); + maDragRect.SetBottom( pSplitItem->mnTop+pSplitItem->mnHeight-1 ); + + if ( mnSplitTest & SPLIT_HORZ ) + { + if ( bDown ) + maDragRect.AdjustRight(mpSplitSet->mnSplitSize ); + else + maDragRect.AdjustLeft( -(mpSplitSet->mnSplitSize) ); + } + else + { + if ( bDown ) + maDragRect.AdjustBottom(mpSplitSet->mnSplitSize ); + else + maDragRect.AdjustTop( -(mpSplitSet->mnSplitSize) ); + } + + if ( mnSplitPos ) + { + tools::Long nTemp = mnSplitPos; + while ( nTemp ) + { + pSplitItem = &mpSplitSet->mvItems[nTemp-1]; + if ( pSplitItem->mbFixed ) + break; + else + { + if ( mnSplitTest & SPLIT_HORZ ) + { + if ( bDown ) + maDragRect.AdjustLeft( -(pSplitItem->mnPixSize) ); + else + maDragRect.AdjustRight(pSplitItem->mnPixSize ); + } + else + { + if ( bDown ) + maDragRect.AdjustTop( -(pSplitItem->mnPixSize) ); + else + maDragRect.AdjustBottom(pSplitItem->mnPixSize ); + } + } + nTemp--; + } + } + + if ( (mpSplitSet == mpMainSet.get()) && (mnWinStyle & WB_SIZEABLE) && !bPropSmaller ) + { + if ( bDown ) + { + if ( mbHorz ) + maDragRect.AdjustBottom(nCurMaxSize-mnDY-mnTopBorder ); + else + maDragRect.AdjustRight(nCurMaxSize-mnDX-mnLeftBorder ); + } + else + { + if ( mbHorz ) + maDragRect.AdjustTop( -(nCurMaxSize-mnDY-mnBottomBorder) ); + else + maDragRect.AdjustLeft( -(nCurMaxSize-mnDX-mnRightBorder) ); + } + } + else + { + std::vector<ImplSplitItem *>::size_type nTemp = mnSplitPos+1; + while ( nTemp < mpSplitSet->mvItems.size() ) + { + pSplitItem = &mpSplitSet->mvItems[nTemp]; + if ( pSplitItem->mbFixed ) + break; + else + { + if ( mnSplitTest & SPLIT_HORZ ) + { + if ( bDown ) + maDragRect.AdjustRight(pSplitItem->mnPixSize ); + else + maDragRect.AdjustLeft( -(pSplitItem->mnPixSize) ); + } + else + { + if ( bDown ) + maDragRect.AdjustBottom(pSplitItem->mnPixSize ); + else + maDragRect.AdjustTop( -(pSplitItem->mnPixSize) ); + } + } + nTemp++; + } + } + } + else + { + maDragRect.SetLeft( mnLeftBorder ); + maDragRect.SetTop( mnTopBorder ); + maDragRect.SetRight( mnDX-mnRightBorder-1 ); + maDragRect.SetBottom( mnDY-mnBottomBorder-1 ); + if ( mbHorz ) + { + if ( mbBottomRight ) + maDragRect.AdjustTop( -(nCurMaxSize-mnDY-mnBottomBorder) ); + else + maDragRect.AdjustBottom(nCurMaxSize-mnDY-mnTopBorder ); + } + else + { + if ( mbBottomRight ) + maDragRect.AdjustLeft( -(nCurMaxSize-mnDX-mnRightBorder) ); + else + maDragRect.AdjustRight(nCurMaxSize-mnDX-mnLeftBorder ); + } + } + + StartTracking(); + + mbDragFull = bool(GetSettings().GetStyleSettings().GetDragFullOptions() & DragFullOptions::Split); + + ImplSplitMousePos( aMousePosPixel ); + + if (!mbDragFull) + { + ImplDrawSplitTracking(aMousePosPixel); + } + else + { + std::vector< ImplSplitItem >& rItems = mpSplitSet->mvItems; + sal_uInt16 nItems = mpSplitSet->mvItems.size(); + mpLastSizes.reset(new tools::Long[nItems*2]); + for ( sal_uInt16 i = 0; i < nItems; i++ ) + { + mpLastSizes[i*2] = rItems[i].mnSize; + mpLastSizes[i*2+1] = rItems[i].mnPixSize; + } + } + mnMStartPos = mnMSplitPos; + + PointerStyle eStyle = PointerStyle::Arrow; + if ( mnSplitTest & SPLIT_HORZ ) + eStyle = PointerStyle::HSplit; + else if ( mnSplitTest & SPLIT_VERT ) + eStyle = PointerStyle::VSplit; + + SetPointer( eStyle ); +} + +void SplitWindow::StartSplit() +{ +} + +void SplitWindow::Split() +{ + maSplitHdl.Call( this ); +} + +void SplitWindow::SplitResize() +{ +} + +void SplitWindow::FadeIn() +{ +} + +void SplitWindow::FadeOut() +{ +} + +void SplitWindow::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( !rMEvt.IsLeft() || rMEvt.IsMod2() ) + { + DockingWindow::MouseButtonDown( rMEvt ); + return; + } + + Point aMousePosPixel = rMEvt.GetPosPixel(); + tools::Rectangle aTestRect; + + mbFadeNoButtonMode = false; + + ImplGetFadeOutRect( aTestRect ); + if ( aTestRect.Contains( aMousePosPixel ) ) + { + mbFadeOutDown = true; + mbFadeOutPressed = true; + Invalidate(); + } + else + { + ImplGetFadeInRect( aTestRect, true ); + if ( aTestRect.Contains( aMousePosPixel ) ) + { + mbFadeInDown = true; + mbFadeInPressed = true; + Invalidate(); + } + else if ( !aTestRect.IsEmpty() && !(mnWinStyle & WB_SIZEABLE) ) + { + mbFadeNoButtonMode = true; + FadeIn(); + return; + } + } + + if ( mbFadeInDown || mbFadeOutDown ) + StartTracking(); + else + ImplStartSplit( rMEvt ); +} + +void SplitWindow::MouseMove( const MouseEvent& rMEvt ) +{ + if ( IsTracking() ) + return; + + Point aPos = rMEvt.GetPosPixel(); + tools::Long nTemp; + ImplSplitSet* pTempSplitSet; + sal_uInt16 nTempSplitPos; + sal_uInt16 nSplitTest = ImplTestSplit( this, aPos, nTemp, &pTempSplitSet, nTempSplitPos ); + PointerStyle eStyle = PointerStyle::Arrow; + tools::Rectangle aFadeInRect; + tools::Rectangle aFadeOutRect; + + ImplGetFadeInRect( aFadeInRect ); + ImplGetFadeOutRect( aFadeOutRect ); + if ( !aFadeInRect.Contains( aPos ) && + !aFadeOutRect.Contains( aPos ) ) + { + if ( nSplitTest && !(nSplitTest & SPLIT_NOSPLIT) ) + { + if ( nSplitTest & SPLIT_HORZ ) + eStyle = PointerStyle::HSplit; + else if ( nSplitTest & SPLIT_VERT ) + eStyle = PointerStyle::VSplit; + } + } + + SetPointer( eStyle ); +} + +void SplitWindow::Tracking( const TrackingEvent& rTEvt ) +{ + Point aMousePosPixel = rTEvt.GetMouseEvent().GetPosPixel(); + + if ( mbFadeInDown ) + { + if ( rTEvt.IsTrackingEnded() ) + { + mbFadeInDown = false; + if ( mbFadeInPressed ) + { + mbFadeInPressed = false; + Invalidate(); + + if ( !rTEvt.IsTrackingCanceled() ) + FadeIn(); + } + } + else + { + tools::Rectangle aTestRect; + ImplGetFadeInRect( aTestRect, true ); + bool bNewPressed = aTestRect.Contains( aMousePosPixel ); + if ( bNewPressed != mbFadeInPressed ) + { + mbFadeInPressed = bNewPressed; + Invalidate(); + } + } + } + else if ( mbFadeOutDown ) + { + if ( rTEvt.IsTrackingEnded() ) + { + mbFadeOutDown = false; + if ( mbFadeOutPressed ) + { + mbFadeOutPressed = false; + Invalidate(); + + if ( !rTEvt.IsTrackingCanceled() ) + FadeOut(); + } + } + else + { + tools::Rectangle aTestRect; + ImplGetFadeOutRect( aTestRect ); + bool bNewPressed = aTestRect.Contains( aMousePosPixel ); + if ( !bNewPressed ) + { + mbFadeOutPressed = bNewPressed; + Invalidate(); + + // We need a mouseevent with a position inside the button for the + // ImplStartSplit function! + MouseEvent aOrgMEvt = rTEvt.GetMouseEvent(); + MouseEvent aNewMEvt( aTestRect.Center(), aOrgMEvt.GetClicks(), + aOrgMEvt.GetMode(), aOrgMEvt.GetButtons(), + aOrgMEvt.GetModifier() ); + + ImplStartSplit( aNewMEvt ); + mbFadeOutDown = false; + } + } + } + else + { + ImplSplitMousePos( aMousePosPixel ); + bool bSplit = true; + if ( mbDragFull ) + { + if ( rTEvt.IsTrackingEnded() ) + { + if ( rTEvt.IsTrackingCanceled() ) + { + std::vector< ImplSplitItem >& rItems = mpSplitSet->mvItems; + size_t nItems = rItems.size(); + for ( size_t i = 0; i < nItems; i++ ) + { + rItems[i].mnSize = mpLastSizes[i*2]; + rItems[i].mnPixSize = mpLastSizes[i*2+1]; + } + ImplUpdate(); + Split(); + } + bSplit = false; + } + } + else + { + if ( rTEvt.IsTrackingEnded() ) + { + HideTracking(); + bSplit = !rTEvt.IsTrackingCanceled(); + } + else + { + ImplDrawSplitTracking(aMousePosPixel); + bSplit = false; + } + } + + if ( bSplit ) + { + bool bPropSmaller = (mnMouseModifier & KEY_SHIFT) != 0; + bool bPropGreater = (mnMouseModifier & KEY_MOD1) != 0; + tools::Long nDelta = mnMSplitPos-mnMStartPos; + + if ( (mnSplitTest & SPLIT_WINDOW) && mpMainSet->mvItems.empty() ) + { + if ( (mpSplitSet == mpMainSet.get()) && mbBottomRight ) + nDelta *= -1; + ImplSetWindowSize( nDelta ); + } + else + { + tools::Long nNewSize = mpSplitSet->mvItems[mnSplitPos].mnPixSize; + if ( (mpSplitSet == mpMainSet.get()) && mbBottomRight ) + nNewSize -= nDelta; + else + nNewSize += nDelta; + SplitItem( mpSplitSet->mvItems[mnSplitPos].mnId, nNewSize, + bPropSmaller, bPropGreater ); + } + + Split(); + + if ( mbDragFull ) + { + PaintImmediately(); + mnMStartPos = mnMSplitPos; + } + } + + if ( rTEvt.IsTrackingEnded() ) + { + mpLastSizes.reset(); + mpSplitSet = nullptr; + mnMouseOff = 0; + mnMStartPos = 0; + mnMSplitPos = 0; + mnMouseModifier = 0; + mnSplitTest = 0; + mnSplitPos = 0; + } + } +} + +bool SplitWindow::PreNotify( NotifyEvent& rNEvt ) +{ + if( rNEvt.GetType() == MouseNotifyEvent::MOUSEMOVE ) + { + const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent(); + if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() ) + { + // trigger redraw if mouse over state has changed + tools::Rectangle aFadeInRect; + tools::Rectangle aFadeOutRect; + ImplGetFadeInRect( aFadeInRect ); + ImplGetFadeOutRect( aFadeOutRect ); + + if ( aFadeInRect.Contains( GetPointerPosPixel() ) != aFadeInRect.Contains( GetLastPointerPosPixel() ) ) + Invalidate( aFadeInRect ); + if ( aFadeOutRect.Contains( GetPointerPosPixel() ) != aFadeOutRect.Contains( GetLastPointerPosPixel() ) ) + Invalidate( aFadeOutRect ); + + if( pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow() ) + { + Invalidate( aFadeInRect ); + Invalidate( aFadeOutRect ); + } + } + } + return Window::PreNotify( rNEvt ); +} + +void SplitWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + if (mnWinStyle & WB_BORDER) + ImplDrawBorder(rRenderContext); + + ImplDrawBorderLine(rRenderContext); + ImplDrawFadeOut(rRenderContext); + ImplDrawFadeIn(rRenderContext); + + // draw splitter + if (!(mnWinStyle & WB_NOSPLITDRAW)) + { + ImplDrawSplit(rRenderContext, mpMainSet.get(), mbHorz, !mbBottomRight); + } +} + +void SplitWindow::Resize() +{ + Size aSize = GetOutputSizePixel(); + mnDX = aSize.Width(); + mnDY = aSize.Height(); + + ImplUpdate(); + Invalidate(); +} + +void SplitWindow::RequestHelp( const HelpEvent& rHEvt ) +{ + // no keyboard help for splitwin + if ( rHEvt.GetMode() & (HelpEventMode::BALLOON | HelpEventMode::QUICK) && !rHEvt.KeyboardActivated() ) + { + Point aMousePosPixel = ScreenToOutputPixel( rHEvt.GetMousePosPixel() ); + tools::Rectangle aHelpRect; + TranslateId pHelpResId; + + ImplGetFadeInRect( aHelpRect, true ); + if ( aHelpRect.Contains( aMousePosPixel ) ) + pHelpResId = SV_HELPTEXT_FADEIN; + else + { + ImplGetFadeOutRect( aHelpRect ); + if ( aHelpRect.Contains( aMousePosPixel ) ) + pHelpResId = SV_HELPTEXT_FADEOUT; + } + + // get rectangle + if (pHelpResId) + { + Point aPt = OutputToScreenPixel( aHelpRect.TopLeft() ); + aHelpRect.SetLeft( aPt.X() ); + aHelpRect.SetTop( aPt.Y() ); + aPt = OutputToScreenPixel( aHelpRect.BottomRight() ); + aHelpRect.SetRight( aPt.X() ); + aHelpRect.SetBottom( aPt.Y() ); + + // get and draw text + OUString aStr = VclResId(pHelpResId); + if ( rHEvt.GetMode() & HelpEventMode::BALLOON ) + Help::ShowBalloon( this, aHelpRect.Center(), aHelpRect, aStr ); + else + Help::ShowQuickHelp( this, aHelpRect, aStr ); + return; + } + } + + DockingWindow::RequestHelp( rHEvt ); +} + +void SplitWindow::StateChanged( StateChangedType nType ) +{ + switch ( nType ) + { + case StateChangedType::InitShow: + if ( IsUpdateMode() ) + ImplCalcLayout(); + break; + case StateChangedType::UpdateMode: + if ( IsUpdateMode() && IsReallyShown() ) + ImplCalcLayout(); + break; + case StateChangedType::ControlBackground: + ImplInitSettings(); + Invalidate(); + break; + default:; + } + + DockingWindow::StateChanged( nType ); +} + +void SplitWindow::DataChanged( const DataChangedEvent& rDCEvt ) +{ + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + ImplInitSettings(); + Invalidate(); + } + else + DockingWindow::DataChanged( rDCEvt ); +} + +void SplitWindow::InsertItem( sal_uInt16 nId, vcl::Window* pWindow, tools::Long nSize, + sal_uInt16 nPos, sal_uInt16 nIntoSetId, + SplitWindowItemFlags nBits ) +{ +#ifdef DBG_UTIL + sal_uInt16 nDbgDummy; + SAL_WARN_IF( ImplFindItem( mpMainSet.get(), nId, nDbgDummy ), "vcl", "SplitWindow::InsertItem() - Id already exists" ); +#endif + + // Size has to be at least 1. + if ( nSize < 1 ) + nSize = 1; + + ImplSplitSet* pSet = ImplFindSet( mpMainSet.get(), nIntoSetId ); +#ifdef DBG_UTIL + SAL_WARN_IF( !pSet, "vcl", "SplitWindow::InsertItem() - Set not exists" ); +#endif + if(!pSet) + { + return; + } + + // Don't insert further than the end + if ( nPos > pSet->mvItems.size() ) + nPos = pSet->mvItems.size(); + + // Insert in set + pSet->mvItems.emplace( pSet->mvItems.begin() + nPos ); + + // init new item + ImplSplitItem & aItem = pSet->mvItems[nPos]; + aItem.mnSize = nSize; + aItem.mnPixSize = 0; + aItem.mnId = nId; + aItem.mnBits = nBits; + aItem.mnMinSize=-1; + aItem.mnMaxSize=-1; + + if ( pWindow ) + { + // New VclPtr reference + aItem.mpWindow = pWindow; + aItem.mpOrgParent = pWindow->GetParent(); + + // Attach window to SplitWindow. + pWindow->Hide(); + pWindow->SetParent( this ); + } + else + { + ImplSplitSet * pNewSet = new ImplSplitSet(); + pNewSet->mnId = nId; + pNewSet->mnSplitSize = pSet->mnSplitSize; + + aItem.mpSet.reset(pNewSet); + } + + pSet->mbCalcPix = true; + + ImplUpdate(); +} + +void SplitWindow::InsertItem( sal_uInt16 nId, tools::Long nSize, + sal_uInt16 nPos, sal_uInt16 nIntoSetId, + SplitWindowItemFlags nBits ) +{ + InsertItem( nId, nullptr, nSize, nPos, nIntoSetId, nBits ); +} + +void SplitWindow::RemoveItem( sal_uInt16 nId ) +{ +#ifdef DBG_UTIL + sal_uInt16 nDbgDummy; + SAL_WARN_IF( !ImplFindItem( mpMainSet.get(), nId, nDbgDummy ), "vcl", "SplitWindow::RemoveItem() - Id not found" ); +#endif + + // search set + sal_uInt16 nPos; + ImplSplitSet* pSet = ImplFindItem( mpMainSet.get(), nId, nPos ); + + if (!pSet) + return; + + ImplSplitItem* pItem = &pSet->mvItems[nPos]; + VclPtr<vcl::Window> pWindow = pItem->mpWindow; + VclPtr<vcl::Window> pOrgParent = pItem->mpOrgParent; + + // delete set if required + if ( !pWindow ) + pItem->mpSet.reset(); + + // remove item + pSet->mbCalcPix = true; + pSet->mvItems.erase( pSet->mvItems.begin() + nPos ); + + ImplUpdate(); + + // to have the least amounts of paints delete window only here + if ( pWindow ) + { + // restore window + pWindow->Hide(); + pWindow->SetParent( pOrgParent ); + } + + // Clear and delete + pWindow.clear(); + pOrgParent.clear(); +} + +void SplitWindow::SplitItem( sal_uInt16 nId, tools::Long nNewSize, + bool bPropSmall, bool bPropGreat ) +{ + sal_uInt16 nPos; + ImplSplitSet* pSet = ImplFindItem( mpBaseSet, nId, nPos ); + + if (!pSet) + return; + + size_t nItems = pSet->mvItems.size(); + std::vector< ImplSplitItem >& rItems = pSet->mvItems; + + // When there is an explicit minimum or maximum size then move nNewSize + // into that range (when it is not yet already in it.) + nNewSize = ValidateSize(nNewSize, rItems[nPos]); + + if ( mbCalc ) + { + rItems[nPos].mnSize = nNewSize; + return; + } + + tools::Long nDelta = nNewSize-rItems[nPos].mnPixSize; + if ( !nDelta ) + return; + + // calculate area, which could be affected by splitting + sal_uInt16 nMin = 0; + sal_uInt16 nMax = nItems; + for (size_t i = 0; i < nItems; ++i) + { + if ( rItems[i].mbFixed ) + { + if ( i < nPos ) + nMin = i+1; + else + nMax = i; + } + } + + // treat TopSet different if the window is sizeable + bool bSmall = true; + bool bGreat = true; + if ( (pSet == mpMainSet.get()) && (mnWinStyle & WB_SIZEABLE) ) + { + if ( nPos < pSet->mvItems.size()-1 ) + { + if ( !((bPropSmall && bPropGreat) || + ((nDelta > 0) && bPropSmall) || + ((nDelta < 0) && bPropGreat)) ) + { + if ( nDelta < 0 ) + bGreat = false; + else + bSmall = false; + } + } + else + { + if ( nDelta < 0 ) + bGreat = false; + else + bSmall = false; + } + } + else if ( nPos >= nMax ) + { + bSmall = false; + bGreat = false; + } + else if ( nPos && (nPos >= pSet->mvItems.size()-1) ) + { + nPos--; + nDelta *= -1; + bool bTemp = bPropSmall; + bPropSmall = bPropGreat; + bPropGreat = bTemp; + } + + sal_uInt16 n; + // now splitt the windows + if ( nDelta < 0 ) + { + if ( bGreat ) + { + if ( bPropGreat ) + { + tools::Long nTempDelta = nDelta; + do + { + n = nPos+1; + do + { + if ( nTempDelta ) + { + rItems[n].mnPixSize++; + nTempDelta++; + } + n++; + } + while ( n < nMax ); + } + while ( nTempDelta ); + } + else + rItems[nPos+1].mnPixSize -= nDelta; + } + + if ( bSmall ) + { + if ( bPropSmall ) + { + do + { + n = nPos+1; + do + { + if ( nDelta && rItems[n-1].mnPixSize ) + { + rItems[n-1].mnPixSize--; + nDelta++; + } + + n--; + } + while ( n > nMin ); + } + while ( nDelta ); + } + else + { + n = nPos+1; + do + { + if ( rItems[n-1].mnPixSize+nDelta < 0 ) + { + nDelta += rItems[n-1].mnPixSize; + rItems[n-1].mnPixSize = 0; + } + else + { + rItems[n-1].mnPixSize += nDelta; + break; + } + n--; + } + while ( n > nMin ); + } + } + } + else + { + if ( bGreat ) + { + if ( bPropGreat ) + { + tools::Long nTempDelta = nDelta; + do + { + n = nPos+1; + do + { + if ( nTempDelta ) + { + rItems[n-1].mnPixSize++; + nTempDelta--; + } + n--; + } + while ( n > nMin ); + } + while ( nTempDelta ); + } + else + rItems[nPos].mnPixSize += nDelta; + } + + if ( bSmall ) + { + if ( bPropSmall ) + { + do + { + n = nPos+1; + do + { + if ( nDelta && rItems[n].mnPixSize ) + { + rItems[n].mnPixSize--; + nDelta--; + } + + n++; + } + while ( n < nMax ); + } + while ( nDelta ); + } + else + { + n = nPos+1; + do + { + if ( rItems[n].mnPixSize-nDelta < 0 ) + { + nDelta -= rItems[n].mnPixSize; + rItems[n].mnPixSize = 0; + } + else + { + rItems[n].mnPixSize -= nDelta; + break; + } + n++; + } + while ( n < nMax ); + } + } + } + + // update original sizes + ImplCalcLogSize( rItems, nItems ); + + ImplUpdate(); +} + +void SplitWindow::SetItemSize( sal_uInt16 nId, tools::Long nNewSize ) +{ + sal_uInt16 nPos; + ImplSplitSet* pSet = ImplFindItem( mpBaseSet, nId, nPos ); + ImplSplitItem* pItem; + + if ( !pSet ) + return; + + // check if size is changed + pItem = &pSet->mvItems[nPos]; + if ( pItem->mnSize != nNewSize ) + { + // set new size and re-calculate + pItem->mnSize = nNewSize; + pSet->mbCalcPix = true; + ImplUpdate(); + } +} + +tools::Long SplitWindow::GetItemSize( sal_uInt16 nId ) const +{ + sal_uInt16 nPos; + ImplSplitSet* pSet = ImplFindItem( mpBaseSet, nId, nPos ); + + if ( pSet ) + return pSet->mvItems[nPos].mnSize; + else + return 0; +} + +tools::Long SplitWindow::GetItemSize( sal_uInt16 nId, SplitWindowItemFlags nBits ) const +{ + sal_uInt16 nPos; + ImplSplitSet* pSet = ImplFindItem( mpBaseSet, nId, nPos ); + + if ( pSet ) + { + if ( nBits == pSet->mvItems[nPos].mnBits ) + return pSet->mvItems[nPos].mnSize; + else + { + const_cast<SplitWindow*>(this)->ImplCalcLayout(); + + tools::Long nRelSize = 0; + tools::Long nPerSize = 0; + size_t nItems; + SplitWindowItemFlags nTempBits; + nItems = pSet->mvItems.size(); + std::vector< ImplSplitItem >& rItems = pSet->mvItems; + for ( size_t i = 0; i < nItems; i++ ) + { + if ( i == nPos ) + nTempBits = nBits; + else + nTempBits = rItems[i].mnBits; + if ( nTempBits & SplitWindowItemFlags::RelativeSize ) + nRelSize += rItems[i].mnPixSize; + else if ( nTempBits & SplitWindowItemFlags::PercentSize ) + nPerSize += rItems[i].mnPixSize; + } + nPerSize += nRelSize; + if ( nBits & SplitWindowItemFlags::RelativeSize ) + { + if ( nRelSize ) + return (rItems[nPos].mnPixSize+(nRelSize/2))/nRelSize; + else + return 1; + } + else if ( nBits & SplitWindowItemFlags::PercentSize ) + { + if ( nPerSize ) + return (rItems[nPos].mnPixSize*100)/nPerSize; + else + return 1; + } + else + return rItems[nPos].mnPixSize; + } + } + else + return 0; +} + +void SplitWindow::SetItemSizeRange (sal_uInt16 nId, const Range& rRange) +{ + sal_uInt16 nPos; + ImplSplitSet* pSet = ImplFindItem(mpBaseSet, nId, nPos); + + if (pSet != nullptr) + { + pSet->mvItems[nPos].mnMinSize = rRange.Min(); + pSet->mvItems[nPos].mnMaxSize = rRange.Max(); + } +} + +sal_uInt16 SplitWindow::GetSet( sal_uInt16 nId ) const +{ + sal_uInt16 nPos; + ImplSplitSet* pSet = ImplFindItem( mpBaseSet, nId, nPos ); + + if ( pSet ) + return pSet->mnId; + else + return 0; +} + +bool SplitWindow::IsItemValid( sal_uInt16 nId ) const +{ + sal_uInt16 nPos; + ImplSplitSet* pSet = mpBaseSet ? ImplFindItem(mpBaseSet, nId, nPos) : nullptr; + + return pSet != nullptr; +} + +sal_uInt16 SplitWindow::GetItemId( vcl::Window* pWindow ) const +{ + return ImplFindItem( mpBaseSet, pWindow ); +} + +sal_uInt16 SplitWindow::GetItemId( const Point& rPos ) const +{ + return ImplFindItem( mpBaseSet, rPos, mbHorz, !mbBottomRight ); +} + +sal_uInt16 SplitWindow::GetItemPos( sal_uInt16 nId, sal_uInt16 nSetId ) const +{ + ImplSplitSet* pSet = ImplFindSet( mpBaseSet, nSetId ); + sal_uInt16 nPos = SPLITWINDOW_ITEM_NOTFOUND; + + if ( pSet ) + { + for ( size_t i = 0; i < pSet->mvItems.size(); i++ ) + { + if ( pSet->mvItems[i].mnId == nId ) + { + nPos = i; + break; + } + } + } + + return nPos; +} + +sal_uInt16 SplitWindow::GetItemId( sal_uInt16 nPos ) const +{ + ImplSplitSet* pSet = ImplFindSet( mpBaseSet, 0/*nSetId*/ ); + if ( pSet && (nPos < pSet->mvItems.size()) ) + return pSet->mvItems[nPos].mnId; + else + return 0; +} + +sal_uInt16 SplitWindow::GetItemCount( sal_uInt16 nSetId ) const +{ + ImplSplitSet* pSet = ImplFindSet( mpBaseSet, nSetId ); + if ( pSet ) + return pSet->mvItems.size(); + else + return 0; +} + +void SplitWindow::ImplNewAlign() +{ + switch ( meAlign ) + { + case WindowAlign::Top: + mbHorz = true; + mbBottomRight = false; + break; + case WindowAlign::Bottom: + mbHorz = true; + mbBottomRight = true; + break; + case WindowAlign::Left: + mbHorz = false; + mbBottomRight = false; + break; + case WindowAlign::Right: + mbHorz = false; + mbBottomRight = true; + break; + } + + if ( mnWinStyle & WB_BORDER ) + { + ImplCalcBorder( meAlign, mnLeftBorder, mnTopBorder, + mnRightBorder, mnBottomBorder ); + } + + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(); + ImplUpdate(); +} + +void SplitWindow::SetAlign( WindowAlign eNewAlign ) +{ + if ( meAlign != eNewAlign ) + { + meAlign = eNewAlign; + ImplNewAlign(); + } +} + +void SplitWindow::ShowFadeInHideButton() +{ + mbFadeIn = true; + ImplUpdate(); +} + +void SplitWindow::ShowFadeOutButton() +{ + mbFadeOut = true; + ImplUpdate(); +} + +tools::Long SplitWindow::GetFadeInSize() const +{ + tools::Long n = 0; + + if ( mbHorz ) + n = mnTopBorder+mnBottomBorder; + else + n = mnLeftBorder+mnRightBorder; + + return n+SPLITWIN_SPLITSIZE+SPLITWIN_SPLITSIZEEX-2; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/stacking.cxx b/vcl/source/window/stacking.cxx new file mode 100644 index 000000000..928c456ee --- /dev/null +++ b/vcl/source/window/stacking.cxx @@ -0,0 +1,1151 @@ +/* -*- 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/syswin.hxx> +#include <vcl/window.hxx> +#include <vcl/taskpanelist.hxx> +#include <sal/log.hxx> + +#include <salframe.hxx> +#include <salobj.hxx> +#include <svdata.hxx> +#include <window.h> +#include <brdwin.hxx> + +#include <com/sun/star/awt/XTopWindow.hpp> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::datatransfer::clipboard; +using namespace ::com::sun::star::datatransfer::dnd; +using namespace ::com::sun::star; + +using ::com::sun::star::awt::XTopWindow; + +struct ImplCalcToTopData +{ + std::unique_ptr<ImplCalcToTopData> mpNext; + VclPtr<vcl::Window> mpWindow; + std::unique_ptr<vcl::Region> mpInvalidateRegion; +}; + +namespace vcl { + +vcl::Window* Window::ImplGetTopmostFrameWindow() +{ + vcl::Window *pTopmostParent = this; + while( pTopmostParent->ImplGetParent() ) + pTopmostParent = pTopmostParent->ImplGetParent(); + return pTopmostParent->mpWindowImpl->mpFrameWindow; +} + +void Window::ImplInsertWindow( vcl::Window* pParent ) +{ + mpWindowImpl->mpParent = pParent; + mpWindowImpl->mpRealParent = pParent; + + if ( !pParent || mpWindowImpl->mbFrame ) + return; + + // search frame window and set window frame data + vcl::Window* pFrameParent = pParent->mpWindowImpl->mpFrameWindow; + mpWindowImpl->mpFrameData = pFrameParent->mpWindowImpl->mpFrameData; + if (mpWindowImpl->mpFrame != pFrameParent->mpWindowImpl->mpFrame) + { + mpWindowImpl->mpFrame = pFrameParent->mpWindowImpl->mpFrame; + if (mpWindowImpl->mpSysObj) + mpWindowImpl->mpSysObj->Reparent(mpWindowImpl->mpFrame); + } + mpWindowImpl->mpFrameWindow = pFrameParent; + mpWindowImpl->mbFrame = false; + + // search overlap window and insert window in list + if ( ImplIsOverlapWindow() ) + { + vcl::Window* pFirstOverlapParent = pParent; + while ( !pFirstOverlapParent->ImplIsOverlapWindow() ) + pFirstOverlapParent = pFirstOverlapParent->ImplGetParent(); + mpWindowImpl->mpOverlapWindow = pFirstOverlapParent; + + mpWindowImpl->mpNextOverlap = mpWindowImpl->mpFrameData->mpFirstOverlap; + mpWindowImpl->mpFrameData->mpFirstOverlap = this; + + // Overlap-Windows are by default the uppermost + mpWindowImpl->mpNext = pFirstOverlapParent->mpWindowImpl->mpFirstOverlap; + pFirstOverlapParent->mpWindowImpl->mpFirstOverlap = this; + if ( !pFirstOverlapParent->mpWindowImpl->mpLastOverlap ) + pFirstOverlapParent->mpWindowImpl->mpLastOverlap = this; + else + mpWindowImpl->mpNext->mpWindowImpl->mpPrev = this; + } + else + { + if ( pParent->ImplIsOverlapWindow() ) + mpWindowImpl->mpOverlapWindow = pParent; + else + mpWindowImpl->mpOverlapWindow = pParent->mpWindowImpl->mpOverlapWindow; + mpWindowImpl->mpPrev = pParent->mpWindowImpl->mpLastChild; + pParent->mpWindowImpl->mpLastChild = this; + if ( !pParent->mpWindowImpl->mpFirstChild ) + pParent->mpWindowImpl->mpFirstChild = this; + else + mpWindowImpl->mpPrev->mpWindowImpl->mpNext = this; + } +} + +void Window::ImplRemoveWindow( bool bRemoveFrameData ) +{ + // remove window from the lists + if ( !mpWindowImpl->mbFrame ) + { + if ( ImplIsOverlapWindow() ) + { + if ( mpWindowImpl->mpFrameData->mpFirstOverlap.get() == this ) + mpWindowImpl->mpFrameData->mpFirstOverlap = mpWindowImpl->mpNextOverlap; + else + { + vcl::Window* pTempWin = mpWindowImpl->mpFrameData->mpFirstOverlap; + while ( pTempWin->mpWindowImpl->mpNextOverlap.get() != this ) + pTempWin = pTempWin->mpWindowImpl->mpNextOverlap; + pTempWin->mpWindowImpl->mpNextOverlap = mpWindowImpl->mpNextOverlap; + } + + if ( mpWindowImpl->mpPrev ) + mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext; + else + mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap = mpWindowImpl->mpNext; + if ( mpWindowImpl->mpNext ) + mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev; + else + mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap = mpWindowImpl->mpPrev; + } + else + { + if ( mpWindowImpl->mpPrev ) + mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext; + else if ( mpWindowImpl->mpParent ) + mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild = mpWindowImpl->mpNext; + if ( mpWindowImpl->mpNext ) + mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev; + else if ( mpWindowImpl->mpParent ) + mpWindowImpl->mpParent->mpWindowImpl->mpLastChild = mpWindowImpl->mpPrev; + } + + mpWindowImpl->mpPrev = nullptr; + mpWindowImpl->mpNext = nullptr; + } + + if ( bRemoveFrameData ) + { + // release the graphic + OutputDevice *pOutDev = GetOutDev(); + pOutDev->ReleaseGraphics(); + } +} + +void Window::reorderWithinParent(sal_uInt16 nNewPosition) +{ + sal_uInt16 nChildCount = 0; + vcl::Window *pSource = mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild; + while (pSource) + { + if (nChildCount == nNewPosition) + break; + pSource = pSource->mpWindowImpl->mpNext; + nChildCount++; + } + + if (pSource == this) //already at the right place + return; + + ImplRemoveWindow(false); + + if (pSource) + { + mpWindowImpl->mpNext = pSource; + mpWindowImpl->mpPrev = pSource->mpWindowImpl->mpPrev; + pSource->mpWindowImpl->mpPrev = this; + } + else + mpWindowImpl->mpParent->mpWindowImpl->mpLastChild = this; + + if (mpWindowImpl->mpPrev) + mpWindowImpl->mpPrev->mpWindowImpl->mpNext = this; + else + mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild = this; +} + +void Window::ImplToBottomChild() +{ + if ( ImplIsOverlapWindow() || mpWindowImpl->mbReallyVisible || (mpWindowImpl->mpParent->mpWindowImpl->mpLastChild.get() == this) ) + return; + + // put the window to the end of the list + if ( mpWindowImpl->mpPrev ) + mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext; + else + mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild = mpWindowImpl->mpNext; + mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev; + mpWindowImpl->mpPrev = mpWindowImpl->mpParent->mpWindowImpl->mpLastChild; + mpWindowImpl->mpParent->mpWindowImpl->mpLastChild = this; + mpWindowImpl->mpPrev->mpWindowImpl->mpNext = this; + mpWindowImpl->mpNext = nullptr; +} + +void Window::ImplCalcToTop( ImplCalcToTopData* pPrevData ) +{ + SAL_WARN_IF( !ImplIsOverlapWindow(), "vcl", "Window::ImplCalcToTop(): Is not an OverlapWindow" ); + + if ( mpWindowImpl->mbFrame ) + return; + + if ( !IsReallyVisible() ) + return; + + // calculate region, where the window overlaps with other windows + vcl::Region aRegion( GetOutputRectPixel() ); + vcl::Region aInvalidateRegion; + ImplCalcOverlapRegionOverlaps( aRegion, aInvalidateRegion ); + + if ( !aInvalidateRegion.IsEmpty() ) + { + ImplCalcToTopData* pData = new ImplCalcToTopData; + pPrevData->mpNext.reset(pData); + pData->mpWindow = this; + pData->mpInvalidateRegion.reset(new vcl::Region( aInvalidateRegion )); + } +} + +void Window::ImplToTop( ToTopFlags nFlags ) +{ + SAL_WARN_IF( !ImplIsOverlapWindow(), "vcl", "Window::ImplToTop(): Is not an OverlapWindow" ); + + if ( mpWindowImpl->mbFrame ) + { + // on a mouse click in the external window, it is the latter's + // responsibility to assure our frame is put in front + if ( !mpWindowImpl->mpFrameData->mbHasFocus && + !mpWindowImpl->mpFrameData->mbSysObjFocus && + !mpWindowImpl->mpFrameData->mbInSysObjFocusHdl && + !mpWindowImpl->mpFrameData->mbInSysObjToTopHdl ) + { + // do not bring floating windows on the client to top + if( !ImplGetClientWindow() || !(ImplGetClientWindow()->GetStyle() & WB_SYSTEMFLOATWIN) ) + { + SalFrameToTop nSysFlags = SalFrameToTop::NONE; + if ( nFlags & ToTopFlags::RestoreWhenMin ) + nSysFlags |= SalFrameToTop::RestoreWhenMin; + if ( nFlags & ToTopFlags::ForegroundTask ) + nSysFlags |= SalFrameToTop::ForegroundTask; + if ( nFlags & ToTopFlags::GrabFocusOnly ) + nSysFlags |= SalFrameToTop::GrabFocusOnly; + mpWindowImpl->mpFrame->ToTop( nSysFlags ); + } + } + } + else + { + if ( mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap.get() != this ) + { + // remove window from the list + mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext; + if ( mpWindowImpl->mpNext ) + mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev; + else + mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap = mpWindowImpl->mpPrev; + + // take AlwaysOnTop into account + bool bOnTop = IsAlwaysOnTopEnabled(); + vcl::Window* pNextWin = mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap; + if ( !bOnTop ) + { + while ( pNextWin ) + { + if ( !pNextWin->IsAlwaysOnTopEnabled() ) + break; + pNextWin = pNextWin->mpWindowImpl->mpNext; + } + } + + // add the window to the list again + mpWindowImpl->mpNext = pNextWin; + if ( pNextWin ) + { + mpWindowImpl->mpPrev = pNextWin->mpWindowImpl->mpPrev; + pNextWin->mpWindowImpl->mpPrev = this; + } + else + { + mpWindowImpl->mpPrev = mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap; + mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap = this; + } + if ( mpWindowImpl->mpPrev ) + mpWindowImpl->mpPrev->mpWindowImpl->mpNext = this; + else + mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap = this; + + // recalculate ClipRegion of this and all overlapping windows + if ( IsReallyVisible() ) + { + mpWindowImpl->mpOverlapWindow->ImplSetClipFlagOverlapWindows(); + } + } + } +} + +void Window::ImplStartToTop( ToTopFlags nFlags ) +{ + ImplCalcToTopData aStartData; + ImplCalcToTopData* pCurData; + vcl::Window* pOverlapWindow; + if ( ImplIsOverlapWindow() ) + pOverlapWindow = this; + else + pOverlapWindow = mpWindowImpl->mpOverlapWindow; + + // first calculate paint areas + vcl::Window* pTempOverlapWindow = pOverlapWindow; + aStartData.mpNext = nullptr; + pCurData = &aStartData; + do + { + pTempOverlapWindow->ImplCalcToTop( pCurData ); + if ( pCurData->mpNext ) + pCurData = pCurData->mpNext.get(); + pTempOverlapWindow = pTempOverlapWindow->mpWindowImpl->mpOverlapWindow; + } + while ( !pTempOverlapWindow->mpWindowImpl->mbFrame ); + // next calculate the paint areas of the ChildOverlap windows + pTempOverlapWindow = mpWindowImpl->mpFirstOverlap; + while ( pTempOverlapWindow ) + { + pTempOverlapWindow->ImplCalcToTop( pCurData ); + if ( pCurData->mpNext ) + pCurData = pCurData->mpNext.get(); + pTempOverlapWindow = pTempOverlapWindow->mpWindowImpl->mpNext; + } + + // and next change the windows list + pTempOverlapWindow = pOverlapWindow; + do + { + pTempOverlapWindow->ImplToTop( nFlags ); + pTempOverlapWindow = pTempOverlapWindow->mpWindowImpl->mpOverlapWindow; + } + while ( !pTempOverlapWindow->mpWindowImpl->mbFrame ); + // as last step invalidate the invalid areas + pCurData = aStartData.mpNext.get(); + while ( pCurData ) + { + pCurData->mpWindow->ImplInvalidateFrameRegion( pCurData->mpInvalidateRegion.get(), InvalidateFlags::Children ); + pCurData = pCurData->mpNext.get(); + } +} + +void Window::ImplFocusToTop( ToTopFlags nFlags, bool bReallyVisible ) +{ + // do we need to fetch the focus? + if ( !(nFlags & ToTopFlags::NoGrabFocus) ) + { + // first window with GrabFocus-Activate gets the focus + vcl::Window* pFocusWindow = this; + while ( !pFocusWindow->ImplIsOverlapWindow() ) + { + // if the window has no BorderWindow, we + // should always find the belonging BorderWindow + if ( !pFocusWindow->mpWindowImpl->mpBorderWindow ) + { + if ( pFocusWindow->mpWindowImpl->mnActivateMode & ActivateModeFlags::GrabFocus ) + break; + } + pFocusWindow = pFocusWindow->ImplGetParent(); + } + if ( (pFocusWindow->mpWindowImpl->mnActivateMode & ActivateModeFlags::GrabFocus) && + !pFocusWindow->HasChildPathFocus( true ) ) + pFocusWindow->GrabFocus(); + } + + if ( bReallyVisible ) + ImplGenerateMouseMove(); +} + +void Window::ImplShowAllOverlaps() +{ + vcl::Window* pOverlapWindow = mpWindowImpl->mpFirstOverlap; + while ( pOverlapWindow ) + { + if ( pOverlapWindow->mpWindowImpl->mbOverlapVisible ) + { + pOverlapWindow->Show( true, ShowFlags::NoActivate ); + pOverlapWindow->mpWindowImpl->mbOverlapVisible = false; + } + + pOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext; + } +} + +void Window::ImplHideAllOverlaps() +{ + vcl::Window* pOverlapWindow = mpWindowImpl->mpFirstOverlap; + while ( pOverlapWindow ) + { + if ( pOverlapWindow->IsVisible() ) + { + pOverlapWindow->mpWindowImpl->mbOverlapVisible = true; + pOverlapWindow->Show( false ); + } + + pOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext; + } +} + +void Window::ToTop( ToTopFlags nFlags ) +{ + if (!mpWindowImpl) + return; + + ImplStartToTop( nFlags ); + ImplFocusToTop( nFlags, IsReallyVisible() ); +} + +void Window::SetZOrder( vcl::Window* pRefWindow, ZOrderFlags nFlags ) +{ + + if ( mpWindowImpl->mpBorderWindow ) + { + mpWindowImpl->mpBorderWindow->SetZOrder( pRefWindow, nFlags ); + return; + } + + if ( nFlags & ZOrderFlags::First ) + { + if ( ImplIsOverlapWindow() ) + pRefWindow = mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap; + else + pRefWindow = mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild; + nFlags |= ZOrderFlags::Before; + } + else if ( nFlags & ZOrderFlags::Last ) + { + if ( ImplIsOverlapWindow() ) + pRefWindow = mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap; + else + pRefWindow = mpWindowImpl->mpParent->mpWindowImpl->mpLastChild; + nFlags |= ZOrderFlags::Behind; + } + + while ( pRefWindow && pRefWindow->mpWindowImpl->mpBorderWindow ) + pRefWindow = pRefWindow->mpWindowImpl->mpBorderWindow; + if (!pRefWindow || pRefWindow == this || mpWindowImpl->mbFrame) + return; + + SAL_WARN_IF( pRefWindow->mpWindowImpl->mpParent != mpWindowImpl->mpParent, "vcl", "Window::SetZOrder() - pRefWindow has other parent" ); + if ( nFlags & ZOrderFlags::Before ) + { + if ( pRefWindow->mpWindowImpl->mpPrev.get() == this ) + return; + + if ( ImplIsOverlapWindow() ) + { + if ( mpWindowImpl->mpPrev ) + mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext; + else + mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap = mpWindowImpl->mpNext; + if ( mpWindowImpl->mpNext ) + mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev; + else + mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap = mpWindowImpl->mpPrev; + if ( !pRefWindow->mpWindowImpl->mpPrev ) + mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap = this; + } + else + { + if ( mpWindowImpl->mpPrev ) + mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext; + else + mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild = mpWindowImpl->mpNext; + if ( mpWindowImpl->mpNext ) + mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev; + else + mpWindowImpl->mpParent->mpWindowImpl->mpLastChild = mpWindowImpl->mpPrev; + if ( !pRefWindow->mpWindowImpl->mpPrev ) + mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild = this; + } + + mpWindowImpl->mpPrev = pRefWindow->mpWindowImpl->mpPrev; + mpWindowImpl->mpNext = pRefWindow; + if ( mpWindowImpl->mpPrev ) + mpWindowImpl->mpPrev->mpWindowImpl->mpNext = this; + mpWindowImpl->mpNext->mpWindowImpl->mpPrev = this; + } + else if ( nFlags & ZOrderFlags::Behind ) + { + if ( pRefWindow->mpWindowImpl->mpNext.get() == this ) + return; + + if ( ImplIsOverlapWindow() ) + { + if ( mpWindowImpl->mpPrev ) + mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext; + else + mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap = mpWindowImpl->mpNext; + if ( mpWindowImpl->mpNext ) + mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev; + else + mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap = mpWindowImpl->mpPrev; + if ( !pRefWindow->mpWindowImpl->mpNext ) + mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpLastOverlap = this; + } + else + { + if ( mpWindowImpl->mpPrev ) + mpWindowImpl->mpPrev->mpWindowImpl->mpNext = mpWindowImpl->mpNext; + else + mpWindowImpl->mpParent->mpWindowImpl->mpFirstChild = mpWindowImpl->mpNext; + if ( mpWindowImpl->mpNext ) + mpWindowImpl->mpNext->mpWindowImpl->mpPrev = mpWindowImpl->mpPrev; + else + mpWindowImpl->mpParent->mpWindowImpl->mpLastChild = mpWindowImpl->mpPrev; + if ( !pRefWindow->mpWindowImpl->mpNext ) + mpWindowImpl->mpParent->mpWindowImpl->mpLastChild = this; + } + + mpWindowImpl->mpPrev = pRefWindow; + mpWindowImpl->mpNext = pRefWindow->mpWindowImpl->mpNext; + if ( mpWindowImpl->mpNext ) + mpWindowImpl->mpNext->mpWindowImpl->mpPrev = this; + mpWindowImpl->mpPrev->mpWindowImpl->mpNext = this; + } + + if ( !IsReallyVisible() ) + return; + + if ( !mpWindowImpl->mbInitWinClipRegion && mpWindowImpl->maWinClipRegion.IsEmpty() ) + return; + + bool bInitWinClipRegion = mpWindowImpl->mbInitWinClipRegion; + ImplSetClipFlag(); + + // When ClipRegion was not initialised, assume + // the window has not been sent, therefore do not + // trigger any Invalidates. This is an optimization + // for HTML documents with many controls. If this + // check gives problems, a flag should be introduced + // which tracks whether the window has already been + // emitted after Show + if ( bInitWinClipRegion ) + return; + + // Invalidate all windows which are next to each other + // Is INCOMPLETE !!! + tools::Rectangle aWinRect = GetOutputRectPixel(); + vcl::Window* pWindow = nullptr; + if ( ImplIsOverlapWindow() ) + { + if ( mpWindowImpl->mpOverlapWindow ) + pWindow = mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpFirstOverlap; + } + else + pWindow = ImplGetParent()->mpWindowImpl->mpFirstChild; + // Invalidate all windows in front of us and which are covered by us + while ( pWindow ) + { + if ( pWindow == this ) + break; + tools::Rectangle aCompRect = pWindow->GetOutputRectPixel(); + if ( aWinRect.Overlaps( aCompRect ) ) + pWindow->Invalidate( InvalidateFlags::Children | InvalidateFlags::NoTransparent ); + pWindow = pWindow->mpWindowImpl->mpNext; + } + + // If we are covered by a window in the background + // we should redraw it + while ( pWindow ) + { + if ( pWindow != this ) + { + tools::Rectangle aCompRect = pWindow->GetOutputRectPixel(); + if ( aWinRect.Overlaps( aCompRect ) ) + { + Invalidate( InvalidateFlags::Children | InvalidateFlags::NoTransparent ); + break; + } + } + pWindow = pWindow->mpWindowImpl->mpNext; + } +} + +void Window::EnableAlwaysOnTop( bool bEnable ) +{ + + mpWindowImpl->mbAlwaysOnTop = bEnable; + + if ( mpWindowImpl->mpBorderWindow ) + mpWindowImpl->mpBorderWindow->EnableAlwaysOnTop( bEnable ); + else if ( bEnable && IsReallyVisible() ) + ToTop(); + + if ( mpWindowImpl->mbFrame ) + mpWindowImpl->mpFrame->SetAlwaysOnTop( bEnable ); +} + +bool Window::IsTopWindow() const +{ + if ( !mpWindowImpl || mpWindowImpl->mbInDispose ) + return false; + + // topwindows must be frames or they must have a borderwindow which is a frame + if( !mpWindowImpl->mbFrame && (!mpWindowImpl->mpBorderWindow || !mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame ) ) + return false; + + ImplGetWinData(); + if( mpWindowImpl->mpWinData->mnIsTopWindow == sal_uInt16(~0)) // still uninitialized + { + // #113722#, cache result of expensive queryInterface call + vcl::Window *pThisWin = const_cast<vcl::Window*>(this); + uno::Reference< XTopWindow > xTopWindow( pThisWin->GetComponentInterface(), UNO_QUERY ); + pThisWin->mpWindowImpl->mpWinData->mnIsTopWindow = xTopWindow.is() ? 1 : 0; + } + return mpWindowImpl->mpWinData->mnIsTopWindow == 1; +} + +vcl::Window* Window::ImplFindWindow( const Point& rFramePos ) +{ + vcl::Window* pTempWindow; + vcl::Window* pFindWindow; + + // first check all overlapping windows + pTempWindow = mpWindowImpl->mpFirstOverlap; + while ( pTempWindow ) + { + pFindWindow = pTempWindow->ImplFindWindow( rFramePos ); + if ( pFindWindow ) + return pFindWindow; + pTempWindow = pTempWindow->mpWindowImpl->mpNext; + } + + // then we check our window + if ( !mpWindowImpl->mbVisible ) + return nullptr; + + WindowHitTest nHitTest = ImplHitTest( rFramePos ); + if ( nHitTest & WindowHitTest::Inside ) + { + // and then we check all child windows + pTempWindow = mpWindowImpl->mpFirstChild; + while ( pTempWindow ) + { + pFindWindow = pTempWindow->ImplFindWindow( rFramePos ); + if ( pFindWindow ) + return pFindWindow; + pTempWindow = pTempWindow->mpWindowImpl->mpNext; + } + + if ( nHitTest & WindowHitTest::Transparent ) + return nullptr; + else + return this; + } + + return nullptr; +} + +bool Window::ImplIsRealParentPath( const vcl::Window* pWindow ) const +{ + pWindow = pWindow->GetParent(); + while ( pWindow ) + { + if ( pWindow == this ) + return true; + pWindow = pWindow->GetParent(); + } + + return false; +} + +bool Window::ImplIsChild( const vcl::Window* pWindow, bool bSystemWindow ) const +{ + do + { + if ( !bSystemWindow && pWindow->ImplIsOverlapWindow() ) + break; + + pWindow = pWindow->ImplGetParent(); + + if ( pWindow == this ) + return true; + } + while ( pWindow ); + + return false; +} + +bool Window::ImplIsWindowOrChild( const vcl::Window* pWindow, bool bSystemWindow ) const +{ + if ( this == pWindow ) + return true; + return ImplIsChild( pWindow, bSystemWindow ); +} + +void Window::ImplResetReallyVisible() +{ + bool bBecameReallyInvisible = mpWindowImpl->mbReallyVisible; + + GetOutDev()->mbDevOutput = false; + mpWindowImpl->mbReallyVisible = false; + mpWindowImpl->mbReallyShown = false; + + // the SHOW/HIDE events serve as indicators to send child creation/destroy events to the access bridge. + // For this, the data member of the event must not be NULL. + // Previously, we did this in Window::Show, but there some events got lost in certain situations. + if( bBecameReallyInvisible && ImplIsAccessibleCandidate() ) + CallEventListeners( VclEventId::WindowHide, this ); + // TODO. It's kind of a hack that we're re-using the VclEventId::WindowHide. Normally, we should + // introduce another event which explicitly triggers the Accessibility implementations. + + vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap; + while ( pWindow ) + { + if ( pWindow->mpWindowImpl->mbReallyVisible ) + pWindow->ImplResetReallyVisible(); + pWindow = pWindow->mpWindowImpl->mpNext; + } + + pWindow = mpWindowImpl->mpFirstChild; + while ( pWindow ) + { + if ( pWindow->mpWindowImpl->mbReallyVisible ) + pWindow->ImplResetReallyVisible(); + pWindow = pWindow->mpWindowImpl->mpNext; + } +} + +void Window::ImplUpdateWindowPtr( vcl::Window* pWindow ) +{ + if ( mpWindowImpl->mpFrameWindow != pWindow->mpWindowImpl->mpFrameWindow ) + { + // release graphic + OutputDevice *pOutDev = GetOutDev(); + pOutDev->ReleaseGraphics(); + } + + mpWindowImpl->mpFrameData = pWindow->mpWindowImpl->mpFrameData; + if (mpWindowImpl->mpFrame != pWindow->mpWindowImpl->mpFrame) + { + mpWindowImpl->mpFrame = pWindow->mpWindowImpl->mpFrame; + if (mpWindowImpl->mpSysObj) + mpWindowImpl->mpSysObj->Reparent(mpWindowImpl->mpFrame); + } + mpWindowImpl->mpFrameWindow = pWindow->mpWindowImpl->mpFrameWindow; + if ( pWindow->ImplIsOverlapWindow() ) + mpWindowImpl->mpOverlapWindow = pWindow; + else + mpWindowImpl->mpOverlapWindow = pWindow->mpWindowImpl->mpOverlapWindow; + + vcl::Window* pChild = mpWindowImpl->mpFirstChild; + while ( pChild ) + { + pChild->ImplUpdateWindowPtr( pWindow ); + pChild = pChild->mpWindowImpl->mpNext; + } +} + +void Window::ImplUpdateWindowPtr() +{ + vcl::Window* pChild = mpWindowImpl->mpFirstChild; + while ( pChild ) + { + pChild->ImplUpdateWindowPtr( this ); + pChild = pChild->mpWindowImpl->mpNext; + } +} + +void Window::ImplUpdateOverlapWindowPtr( bool bNewFrame ) +{ + bool bVisible = IsVisible(); + Show( false ); + ImplRemoveWindow( bNewFrame ); + vcl::Window* pRealParent = mpWindowImpl->mpRealParent; + ImplInsertWindow( ImplGetParent() ); + mpWindowImpl->mpRealParent = pRealParent; + ImplUpdateWindowPtr(); + if ( ImplUpdatePos() ) + ImplUpdateSysObjPos(); + + if ( bNewFrame ) + { + vcl::Window* pOverlapWindow = mpWindowImpl->mpFirstOverlap; + while ( pOverlapWindow ) + { + vcl::Window* pNextOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext; + pOverlapWindow->ImplUpdateOverlapWindowPtr( bNewFrame ); + pOverlapWindow = pNextOverlapWindow; + } + } + + if ( bVisible ) + Show(); +} + +SystemWindow* Window::GetSystemWindow() const +{ + + const vcl::Window* pWin = this; + while ( pWin && !pWin->IsSystemWindow() ) + pWin = pWin->GetParent(); + return static_cast<SystemWindow*>(const_cast<Window*>(pWin)); +} + +static SystemWindow *ImplGetLastSystemWindow( vcl::Window *pWin ) +{ + // get the most top-level system window, the one that contains the taskpanelist + SystemWindow *pSysWin = nullptr; + if( !pWin ) + return pSysWin; + vcl::Window *pMyParent = pWin; + while ( pMyParent ) + { + if ( pMyParent->IsSystemWindow() ) + pSysWin = static_cast<SystemWindow*>(pMyParent); + pMyParent = pMyParent->GetParent(); + } + return pSysWin; +} + +void Window::SetParent( vcl::Window* pNewParent ) +{ + SAL_WARN_IF( !pNewParent, "vcl", "Window::SetParent(): pParent == NULL" ); + SAL_WARN_IF( pNewParent == this, "vcl", "someone tried to reparent a window to itself" ); + + if( !pNewParent || pNewParent == this ) + return; + + // check if the taskpanelist would change and move the window pointer accordingly + SystemWindow *pSysWin = ImplGetLastSystemWindow(this); + SystemWindow *pNewSysWin = nullptr; + bool bChangeTaskPaneList = false; + if( pSysWin && pSysWin->ImplIsInTaskPaneList( this ) ) + { + pNewSysWin = ImplGetLastSystemWindow( pNewParent ); + if( pNewSysWin && pNewSysWin != pSysWin ) + { + bChangeTaskPaneList = true; + pSysWin->GetTaskPaneList()->RemoveWindow( this ); + } + } + // remove ownerdraw decorated windows from list in the top-most frame window + if( (GetStyle() & WB_OWNERDRAWDECORATION) && mpWindowImpl->mbFrame ) + { + ::std::vector< VclPtr<vcl::Window> >& rList = ImplGetOwnerDrawList(); + auto p = ::std::find( rList.begin(), rList.end(), VclPtr<vcl::Window>(this) ); + if( p != rList.end() ) + rList.erase( p ); + } + + ImplSetFrameParent( pNewParent ); + + if ( mpWindowImpl->mpBorderWindow ) + { + mpWindowImpl->mpRealParent = pNewParent; + mpWindowImpl->mpBorderWindow->SetParent( pNewParent ); + return; + } + + if ( mpWindowImpl->mpParent.get() == pNewParent ) + return; + + if ( mpWindowImpl->mbFrame ) + mpWindowImpl->mpFrame->SetParent( pNewParent->mpWindowImpl->mpFrame ); + + bool bVisible = IsVisible(); + Show( false, ShowFlags::NoFocusChange ); + + // check if the overlap window changes + vcl::Window* pOldOverlapWindow; + vcl::Window* pNewOverlapWindow = nullptr; + if ( ImplIsOverlapWindow() ) + pOldOverlapWindow = nullptr; + else + { + pNewOverlapWindow = pNewParent->ImplGetFirstOverlapWindow(); + if ( mpWindowImpl->mpOverlapWindow.get() != pNewOverlapWindow ) + pOldOverlapWindow = mpWindowImpl->mpOverlapWindow; + else + pOldOverlapWindow = nullptr; + } + + // convert windows in the hierarchy + bool bFocusOverlapWin = HasChildPathFocus( true ); + bool bFocusWin = HasChildPathFocus(); + bool bNewFrame = pNewParent->mpWindowImpl->mpFrameWindow != mpWindowImpl->mpFrameWindow; + if ( bNewFrame ) + { + if ( mpWindowImpl->mpFrameData->mpFocusWin ) + { + if ( IsWindowOrChild( mpWindowImpl->mpFrameData->mpFocusWin ) ) + mpWindowImpl->mpFrameData->mpFocusWin = nullptr; + } + if ( mpWindowImpl->mpFrameData->mpMouseMoveWin ) + { + if ( IsWindowOrChild( mpWindowImpl->mpFrameData->mpMouseMoveWin ) ) + mpWindowImpl->mpFrameData->mpMouseMoveWin = nullptr; + } + if ( mpWindowImpl->mpFrameData->mpMouseDownWin ) + { + if ( IsWindowOrChild( mpWindowImpl->mpFrameData->mpMouseDownWin ) ) + mpWindowImpl->mpFrameData->mpMouseDownWin = nullptr; + } + } + ImplRemoveWindow( bNewFrame ); + ImplInsertWindow( pNewParent ); + if ( mpWindowImpl->mnParentClipMode & ParentClipMode::Clip ) + pNewParent->mpWindowImpl->mbClipChildren = true; + ImplUpdateWindowPtr(); + if ( ImplUpdatePos() ) + ImplUpdateSysObjPos(); + + // If the Overlap-Window has changed, we need to test whether + // OverlapWindows that had the Child window as their parent + // need to be put into the window hierarchy. + if ( ImplIsOverlapWindow() ) + { + if ( bNewFrame ) + { + vcl::Window* pOverlapWindow = mpWindowImpl->mpFirstOverlap; + while ( pOverlapWindow ) + { + vcl::Window* pNextOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext; + pOverlapWindow->ImplUpdateOverlapWindowPtr( bNewFrame ); + pOverlapWindow = pNextOverlapWindow; + } + } + } + else if ( pOldOverlapWindow ) + { + // reset Focus-Save + if ( bFocusWin || + (pOldOverlapWindow->mpWindowImpl->mpLastFocusWindow && + IsWindowOrChild( pOldOverlapWindow->mpWindowImpl->mpLastFocusWindow )) ) + pOldOverlapWindow->mpWindowImpl->mpLastFocusWindow = nullptr; + + vcl::Window* pOverlapWindow = pOldOverlapWindow->mpWindowImpl->mpFirstOverlap; + while ( pOverlapWindow ) + { + vcl::Window* pNextOverlapWindow = pOverlapWindow->mpWindowImpl->mpNext; + if ( ImplIsRealParentPath( pOverlapWindow->ImplGetWindow() ) ) + pOverlapWindow->ImplUpdateOverlapWindowPtr( bNewFrame ); + pOverlapWindow = pNextOverlapWindow; + } + + // update activate-status at next overlap window + if ( HasChildPathFocus( true ) ) + ImplCallFocusChangeActivate( pNewOverlapWindow, pOldOverlapWindow ); + } + + // also convert Activate-Status + if ( bNewFrame ) + { + if ( (GetType() == WindowType::BORDERWINDOW) && + (ImplGetWindow()->GetType() == WindowType::FLOATINGWINDOW) ) + static_cast<ImplBorderWindow*>(this)->SetDisplayActive( mpWindowImpl->mpFrameData->mbHasFocus ); + } + + // when required give focus to new frame if + // FocusWindow is changed with SetParent() + if ( bFocusOverlapWin ) + { + mpWindowImpl->mpFrameData->mpFocusWin = Application::GetFocusWindow(); + if ( !mpWindowImpl->mpFrameData->mbHasFocus ) + { + mpWindowImpl->mpFrame->ToTop( SalFrameToTop::NONE ); + } + } + + // Assure DragSource and DropTarget members are created + if ( bNewFrame ) + { + GetDropTarget(); + } + + if( bChangeTaskPaneList ) + pNewSysWin->GetTaskPaneList()->AddWindow( this ); + + if( (GetStyle() & WB_OWNERDRAWDECORATION) && mpWindowImpl->mbFrame ) + ImplGetOwnerDrawList().emplace_back(this ); + + if ( bVisible ) + Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate ); +} + +bool Window::IsAncestorOf( const vcl::Window& rWindow ) const +{ + return ImplIsRealParentPath(&rWindow); +} + +sal_uInt16 Window::GetChildCount() const +{ + if (!mpWindowImpl) + return 0; + + sal_uInt16 nChildCount = 0; + vcl::Window* pChild = mpWindowImpl->mpFirstChild; + while ( pChild ) + { + nChildCount++; + pChild = pChild->mpWindowImpl->mpNext; + } + + return nChildCount; +} + +vcl::Window* Window::GetChild( sal_uInt16 nChild ) const +{ + if (!mpWindowImpl) + return nullptr; + + sal_uInt16 nChildCount = 0; + vcl::Window* pChild = mpWindowImpl->mpFirstChild; + while ( pChild ) + { + if ( nChild == nChildCount ) + return pChild; + pChild = pChild->mpWindowImpl->mpNext; + nChildCount++; + } + + return nullptr; +} + +vcl::Window* Window::GetWindow( GetWindowType nType ) const +{ + if (!mpWindowImpl) + return nullptr; + + switch ( nType ) + { + case GetWindowType::Parent: + return mpWindowImpl->mpRealParent; + + case GetWindowType::FirstChild: + return mpWindowImpl->mpFirstChild; + + case GetWindowType::LastChild: + return mpWindowImpl->mpLastChild; + + case GetWindowType::Prev: + return mpWindowImpl->mpPrev; + + case GetWindowType::Next: + return mpWindowImpl->mpNext; + + case GetWindowType::FirstOverlap: + return mpWindowImpl->mpFirstOverlap; + + case GetWindowType::Overlap: + if ( ImplIsOverlapWindow() ) + return const_cast<vcl::Window*>(this); + else + return mpWindowImpl->mpOverlapWindow; + + case GetWindowType::ParentOverlap: + if ( ImplIsOverlapWindow() ) + return mpWindowImpl->mpOverlapWindow; + else + return mpWindowImpl->mpOverlapWindow->mpWindowImpl->mpOverlapWindow; + + case GetWindowType::Client: + return this->ImplGetWindow(); + + case GetWindowType::RealParent: + return ImplGetParent(); + + case GetWindowType::Frame: + return mpWindowImpl->mpFrameWindow; + + case GetWindowType::Border: + if ( mpWindowImpl->mpBorderWindow ) + return mpWindowImpl->mpBorderWindow->GetWindow( GetWindowType::Border ); + return const_cast<vcl::Window*>(this); + + case GetWindowType::FirstTopWindowChild: + return ImplGetWinData()->maTopWindowChildren.empty() ? nullptr : (*ImplGetWinData()->maTopWindowChildren.begin()).get(); + + case GetWindowType::NextTopWindowSibling: + { + if ( !mpWindowImpl->mpRealParent ) + return nullptr; + const ::std::list< VclPtr<vcl::Window> >& rTopWindows( mpWindowImpl->mpRealParent->ImplGetWinData()->maTopWindowChildren ); + ::std::list< VclPtr<vcl::Window> >::const_iterator myPos = + ::std::find( rTopWindows.begin(), rTopWindows.end(), this ); + if ( ( myPos == rTopWindows.end() ) || ( ++myPos == rTopWindows.end() ) ) + return nullptr; + return *myPos; + } + + } + + return nullptr; +} + +bool Window::IsChild( const vcl::Window* pWindow ) const +{ + do + { + if ( pWindow->ImplIsOverlapWindow() ) + break; + + pWindow = pWindow->ImplGetParent(); + + if ( pWindow == this ) + return true; + } + while ( pWindow ); + + return false; +} + +bool Window::IsWindowOrChild( const vcl::Window* pWindow, bool bSystemWindow ) const +{ + + if ( this == pWindow ) + return true; + return ImplIsChild( pWindow, bSystemWindow ); +} + +void Window::ImplSetFrameParent( const vcl::Window* pParent ) +{ + vcl::Window* pFrameWindow = ImplGetSVData()->maFrameData.mpFirstFrame; + while( pFrameWindow ) + { + // search all frames that are children of this window + // and reparent them + if( ImplIsRealParentPath( pFrameWindow ) ) + { + SAL_WARN_IF( mpWindowImpl->mpFrame == pFrameWindow->mpWindowImpl->mpFrame, "vcl", "SetFrameParent to own" ); + SAL_WARN_IF( !mpWindowImpl->mpFrame, "vcl", "no frame" ); + SalFrame* pParentFrame = pParent ? pParent->mpWindowImpl->mpFrame : nullptr; + pFrameWindow->mpWindowImpl->mpFrame->SetParent( pParentFrame ); + } + pFrameWindow = pFrameWindow->mpWindowImpl->mpFrameData->mpNextFrame; + } +} + +} /* namespace vcl */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/status.cxx b/vcl/source/window/status.cxx new file mode 100644 index 000000000..8c701b496 --- /dev/null +++ b/vcl/source/window/status.cxx @@ -0,0 +1,1464 @@ +/* -*- 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 <comphelper/string.hxx> +#include <vcl/event.hxx> +#include <vcl/decoview.hxx> +#include <vcl/glyphitemcache.hxx> +#include <vcl/svapp.hxx> +#include <vcl/help.hxx> +#include <vcl/vcllayout.hxx> +#include <vcl/status.hxx> +#include <vcl/virdev.hxx> +#include <vcl/settings.hxx> +#include <config_features.h> +#include <svdata.hxx> +#include <window.h> + +#define STATUSBAR_OFFSET_X STATUSBAR_OFFSET +#define STATUSBAR_OFFSET_Y 2 +#define STATUSBAR_OFFSET_TEXTY 3 + +#define STATUSBAR_PRGS_OFFSET 3 +#define STATUSBAR_PRGS_COUNT 100 +#define STATUSBAR_PRGS_MIN 5 + +class StatusBar::ImplData +{ +public: + ImplData(); + + VclPtr<VirtualDevice> mpVirDev; +}; + +StatusBar::ImplData::ImplData() +{ + mpVirDev = nullptr; +} + +struct ImplStatusItem +{ + sal_uInt16 mnId; + StatusBarItemBits mnBits; + tools::Long mnWidth; + tools::Long mnOffset; + tools::Long mnExtraWidth; + tools::Long mnX; + OUString maText; + OUString maHelpText; + OUString maQuickHelpText; + OString maHelpId; + void* mpUserData; + bool mbVisible; + OUString maAccessibleName; + OUString maCommand; + std::optional<SalLayoutGlyphs> mLayoutGlyphsCache; + SalLayoutGlyphs* GetTextGlyphs(const OutputDevice* pOutputDevice); +}; + +SalLayoutGlyphs* ImplStatusItem::GetTextGlyphs(const OutputDevice* outputDevice) +{ + if(!mLayoutGlyphsCache.has_value()) + { + std::unique_ptr<SalLayout> pSalLayout = outputDevice->ImplLayout( + maText, 0, -1, Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly); + mLayoutGlyphsCache = pSalLayout ? pSalLayout->GetGlyphs() : SalLayoutGlyphs(); + } + return mLayoutGlyphsCache->IsValid() ? &mLayoutGlyphsCache.value() : nullptr; +} + +static tools::Long ImplCalcProgressWidth( sal_uInt16 nMax, tools::Long nSize ) +{ + return ((nMax*(nSize+(nSize/2)))-(nSize/2)+(STATUSBAR_PRGS_OFFSET*2)); +} + +static Point ImplGetItemTextPos( const Size& rRectSize, const Size& rTextSize, + StatusBarItemBits nStyle ) +{ + tools::Long nX; + tools::Long nY; + tools::Long delta = (rTextSize.Height()/4) + 1; + if( delta + rTextSize.Width() > rRectSize.Width() ) + delta = 0; + + if ( nStyle & StatusBarItemBits::Left ) + nX = delta; + else if ( nStyle & StatusBarItemBits::Right ) + nX = rRectSize.Width()-rTextSize.Width()-delta; + else // StatusBarItemBits::Center + nX = (rRectSize.Width()-rTextSize.Width())/2; + nY = (rRectSize.Height()-rTextSize.Height())/2 + 1; + return Point( nX, nY ); +} + +bool StatusBar::ImplIsItemUpdate() const +{ + return !mbProgressMode && IsReallyVisible() && IsUpdateMode(); +} + +void StatusBar::ImplInit( vcl::Window* pParent, WinBits nStyle ) +{ + mpImplData.reset(new ImplData); + + // default: RightAlign + if ( !(nStyle & (WB_LEFT | WB_RIGHT)) ) + nStyle |= WB_RIGHT; + + Window::ImplInit( pParent, nStyle & ~WB_BORDER, nullptr ); + + // remember WinBits + mpImplData->mpVirDev = VclPtr<VirtualDevice>::Create( *GetOutDev() ); + mnCurItemId = 0; + mbFormat = true; + mbProgressMode = false; + mbInUserDraw = false; + mbAdjustHiDPI = false; + mnItemsWidth = STATUSBAR_OFFSET_X; + mnDX = 0; + mnDY = 0; + mnCalcHeight = 0; + mnTextY = STATUSBAR_OFFSET_TEXTY; + + ImplInitSettings(); + + SetOutputSizePixel( CalcWindowSizePixel() ); +} + +StatusBar::StatusBar( vcl::Window* pParent, WinBits nStyle ) : + Window( WindowType::STATUSBAR ), + mnLastProgressPaint_ms(osl_getGlobalTimer()) +{ + ImplInit( pParent, nStyle ); +} + +StatusBar::~StatusBar() +{ + disposeOnce(); +} + +void StatusBar::dispose() +{ + // delete all items + mvItemList.clear(); + + // delete VirtualDevice + mpImplData->mpVirDev.disposeAndClear(); + mpImplData.reset(); + Window::dispose(); +} + +void StatusBar::AdjustItemWidthsForHiDPI() +{ + mbAdjustHiDPI = true; +} + +void StatusBar::ApplySettings(vcl::RenderContext& rRenderContext) +{ + rRenderContext.SetLineColor(); + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + ApplyControlFont(rRenderContext, rStyleSettings.GetToolFont()); + + Color aColor; + if (IsControlForeground()) + aColor = GetControlForeground(); + else if (GetStyle() & WB_3DLOOK) + aColor = rStyleSettings.GetButtonTextColor(); + else + aColor = rStyleSettings.GetWindowTextColor(); + rRenderContext.SetTextColor(aColor); + + rRenderContext.SetTextFillColor(); + + if (IsControlBackground()) + aColor = GetControlBackground(); + else if (GetStyle() & WB_3DLOOK) + aColor = rStyleSettings.GetFaceColor(); + else + aColor = rStyleSettings.GetWindowColor(); + rRenderContext.SetBackground(aColor); + + // NWF background + if (!IsControlBackground() && + rRenderContext.IsNativeControlSupported(ControlType::WindowBackground, ControlPart::BackgroundWindow)) + { + ImplGetWindowImpl()->mnNativeBackground = ControlPart::BackgroundWindow; + EnableChildTransparentMode(); + } +} + +void StatusBar::ImplInitSettings() +{ + ApplySettings(*GetOutDev()); + + mpImplData->mpVirDev->SetFont(GetFont()); + mpImplData->mpVirDev->SetTextColor(GetTextColor()); + mpImplData->mpVirDev->SetTextAlign(GetTextAlign()); + mpImplData->mpVirDev->SetTextFillColor(); + mpImplData->mpVirDev->SetBackground(GetBackground()); +} + +void StatusBar::ImplFormat() +{ + tools::Long nExtraWidth; + tools::Long nExtraWidth2; + tools::Long nX; + sal_uInt16 nAutoSizeItems; + bool bChanged; + + do { + // sum up widths + nAutoSizeItems = 0; + mnItemsWidth = STATUSBAR_OFFSET_X; + bChanged = false; + tools::Long nOffset = 0; + for ( const auto & pItem : mvItemList ) { + if ( pItem->mbVisible ) + { + if ( pItem->mnBits & StatusBarItemBits::AutoSize ) { + nAutoSizeItems++; + } + + mnItemsWidth += pItem->mnWidth + nOffset; + nOffset = pItem->mnOffset; + } + } + + if ( mnDX > 0 && mnDX < mnItemsWidth ) + { + // Total width of items is more than available width + // Try to hide secondary elements, if any + for ( auto & pItem : mvItemList ) + { + if ( pItem->mbVisible && !(pItem->mnBits & StatusBarItemBits::Mandatory) ) + { + pItem->mbVisible = false; + bChanged = true; + break; + } + } + } + else if ( mnDX > mnItemsWidth ) + { + // Width of statusbar is sufficient. + // Try to restore hidden items, if any + for ( auto & pItem : mvItemList ) + { + if ( !pItem->mbVisible && + !(pItem->mnBits & StatusBarItemBits::Mandatory) && + pItem->mnWidth + nOffset + mnItemsWidth < mnDX ) + { + pItem->mbVisible = true; + bChanged = true; + break; + } + } + } + } while ( bChanged ); + + if ( GetStyle() & WB_RIGHT ) + { + // AutoSize isn't computed for right-alignment, + // because we show the text that is declared by SetText on the left side + nX = mnDX - mnItemsWidth; + nExtraWidth = 0; + nExtraWidth2 = 0; + } + else + { + mnItemsWidth += STATUSBAR_OFFSET_X; + + // calling AutoSize is potentially necessary for left-aligned text, + if ( nAutoSizeItems && (mnDX > (mnItemsWidth - STATUSBAR_OFFSET)) ) + { + nExtraWidth = (mnDX - mnItemsWidth - 1) / nAutoSizeItems; + nExtraWidth2 = (mnDX - mnItemsWidth - 1) % nAutoSizeItems; + } + else + { + nExtraWidth = 0; + nExtraWidth2 = 0; + } + nX = STATUSBAR_OFFSET_X; + + if( GetOutDev()->HasMirroredGraphics() && IsRTLEnabled() ) + nX += ImplGetSVData()->maNWFData.mnStatusBarLowerRightOffset; + } + + for (auto & pItem : mvItemList) { + if ( pItem->mbVisible ) { + if ( pItem->mnBits & StatusBarItemBits::AutoSize ) { + pItem->mnExtraWidth = nExtraWidth; + if ( nExtraWidth2 ) { + pItem->mnExtraWidth++; + nExtraWidth2--; + } + } else { + pItem->mnExtraWidth = 0; + } + + pItem->mnX = nX; + nX += pItem->mnWidth + pItem->mnExtraWidth + pItem->mnOffset; + } + } + + mbFormat = false; +} + +tools::Rectangle StatusBar::ImplGetItemRectPos( sal_uInt16 nPos ) const +{ + tools::Rectangle aRect; + ImplStatusItem* pItem = ( nPos < mvItemList.size() ) ? mvItemList[ nPos ].get() : nullptr; + if ( pItem && pItem->mbVisible ) + { + aRect.SetLeft( pItem->mnX ); + aRect.SetRight( aRect.Left() + pItem->mnWidth + pItem->mnExtraWidth ); + aRect.SetTop( STATUSBAR_OFFSET_Y ); + aRect.SetBottom( mnCalcHeight - STATUSBAR_OFFSET_Y ); + } + + return aRect; +} + +sal_uInt16 StatusBar::ImplGetFirstVisiblePos() const +{ + for( size_t nPos = 0; nPos < mvItemList.size(); nPos++ ) + { + ImplStatusItem* pItem = mvItemList[ nPos ].get(); + if ( pItem->mbVisible ) + return sal_uInt16(nPos); + } + + return SAL_MAX_UINT16; +} + +void StatusBar::ImplDrawText(vcl::RenderContext& rRenderContext) +{ + // prevent item box from being overwritten + tools::Rectangle aTextRect; + aTextRect.SetLeft( STATUSBAR_OFFSET_X + 1 ); + aTextRect.SetTop( mnTextY ); + if (GetStyle() & WB_RIGHT) + aTextRect.SetRight( mnDX - mnItemsWidth - 1 ); + else + aTextRect.SetRight( mnDX - 1 ); + if (aTextRect.Right() > aTextRect.Left()) + { + // compute position + OUString aStr = GetText(); + sal_Int32 nPos = aStr.indexOf('\n'); + if (nPos != -1) + aStr = aStr.copy(0, nPos); + + aTextRect.SetBottom( aTextRect.Top()+GetTextHeight()+1 ); + + rRenderContext.DrawText(aTextRect, aStr, DrawTextFlags::Left | DrawTextFlags::Top | DrawTextFlags::Clip | DrawTextFlags::EndEllipsis); + } +} + +void StatusBar::ImplDrawItem(vcl::RenderContext& rRenderContext, bool bOffScreen, sal_uInt16 nPos) +{ + tools::Rectangle aRect = ImplGetItemRectPos(nPos); + + if (aRect.IsEmpty()) + return; + + // compute output region + ImplStatusItem* pItem = mvItemList[nPos].get(); + tools::Long nW = 1; + tools::Rectangle aTextRect(aRect.Left() + nW, aRect.Top() + nW, + aRect.Right() - nW, aRect.Bottom() - nW); + + Size aTextRectSize(aTextRect.GetSize()); + + if (bOffScreen) + { + mpImplData->mpVirDev->SetOutputSizePixel(aTextRectSize); + } + else + { + vcl::Region aRegion(aTextRect); + rRenderContext.SetClipRegion(aRegion); + } + + // if the framework code is drawing status, let it do all the work + if (!(pItem->mnBits & StatusBarItemBits::UserDraw)) + { + SalLayoutGlyphs* pGlyphs = pItem->GetTextGlyphs(&rRenderContext); + Size aTextSize(rRenderContext.GetTextWidth(pItem->maText,0,-1,nullptr,pGlyphs), + rRenderContext.GetTextHeight()); + Point aTextPos = ImplGetItemTextPos(aTextRectSize, aTextSize, pItem->mnBits); + + if (bOffScreen) + { + mpImplData->mpVirDev->DrawText( + aTextPos, + pItem->maText, + 0, -1, nullptr, nullptr, + pGlyphs ); + } + else + { + aTextPos.AdjustX(aTextRect.Left() ); + aTextPos.AdjustY(aTextRect.Top() ); + rRenderContext.DrawText( + aTextPos, + pItem->maText, + 0, -1, nullptr, nullptr, + pGlyphs ); + } + } + + // call DrawItem if necessary + if (pItem->mnBits & StatusBarItemBits::UserDraw) + { + if (bOffScreen) + { + mbInUserDraw = true; + mpImplData->mpVirDev->EnableRTL( IsRTLEnabled() ); + UserDrawEvent aODEvt(mpImplData->mpVirDev, tools::Rectangle(Point(), aTextRectSize), pItem->mnId); + UserDraw(aODEvt); + mpImplData->mpVirDev->EnableRTL(false); + mbInUserDraw = false; + } + else + { + UserDrawEvent aODEvt(&rRenderContext, aTextRect, pItem->mnId); + UserDraw(aODEvt); + } + } + + if (bOffScreen) + rRenderContext.DrawOutDev(aTextRect.TopLeft(), aTextRectSize, Point(), aTextRectSize, *mpImplData->mpVirDev); + else + rRenderContext.SetClipRegion(); + + if (nPos != ImplGetFirstVisiblePos()) + { + // draw separator + Point aFrom(aRect.TopLeft()); + aFrom.AdjustX( -4 ); + aFrom.AdjustY( 1 ); + Point aTo(aRect.BottomLeft()); + aTo.AdjustX( -4 ); + aTo.AdjustY( -1 ); + + DecorationView aDecoView(&rRenderContext); + aDecoView.DrawSeparator(aFrom, aTo); + } + + if (!rRenderContext.ImplIsRecordLayout()) + CallEventListeners(VclEventId::StatusbarDrawItem, reinterpret_cast<void*>(pItem->mnId)); +} + +void DrawProgress(vcl::Window* pWindow, vcl::RenderContext& rRenderContext, const Point& rPos, + tools::Long nOffset, tools::Long nPrgsWidth, tools::Long nPrgsHeight, + sal_uInt16 nPercent1, sal_uInt16 nPercent2, sal_uInt16 nPercentCount, + const tools::Rectangle& rFramePosSize) +{ + if (rRenderContext.IsNativeControlSupported(ControlType::Progress, ControlPart::Entire)) + { + bool bNeedErase = ImplGetSVData()->maNWFData.mbProgressNeedsErase; + + tools::Long nFullWidth = (nPrgsWidth + nOffset) * (10000 / nPercentCount); + tools::Long nPerc = std::min<sal_uInt16>(nPercent2, 10000); + ImplControlValue aValue(nFullWidth * nPerc / 10000); + tools::Rectangle aDrawRect(rPos, Size(nFullWidth, nPrgsHeight)); + tools::Rectangle aControlRegion(aDrawRect); + + if(bNeedErase) + { + vcl::Window* pEraseWindow = pWindow; + while (pEraseWindow->IsPaintTransparent() && !pEraseWindow->ImplGetWindowImpl()->mbFrame) + { + pEraseWindow = pEraseWindow->ImplGetWindowImpl()->mpParent; + } + + if (pEraseWindow == pWindow) + { + // restore background of pWindow + rRenderContext.Erase(rFramePosSize); + } + else + { + // restore transparent background + Point aTL(pWindow->OutputToAbsoluteScreenPixel(rFramePosSize.TopLeft())); + aTL = pEraseWindow->AbsoluteScreenToOutputPixel(aTL); + tools::Rectangle aRect(aTL, rFramePosSize.GetSize()); + pEraseWindow->Invalidate(aRect, InvalidateFlags::NoChildren | + InvalidateFlags::NoClipChildren | + InvalidateFlags::Transparent); + pEraseWindow->PaintImmediately(); + } + rRenderContext.Push(vcl::PushFlags::CLIPREGION); + rRenderContext.IntersectClipRegion(rFramePosSize); + } + + bool bNativeOK = rRenderContext.DrawNativeControl(ControlType::Progress, ControlPart::Entire, aControlRegion, + ControlState::ENABLED, aValue, OUString()); + if (bNeedErase) + rRenderContext.Pop(); + if (bNativeOK) + return; + } + + // precompute values + sal_uInt16 nPerc1 = nPercent1 / nPercentCount; + sal_uInt16 nPerc2 = nPercent2 / nPercentCount; + + if (nPerc1 > nPerc2) + { + // support progress that can also decrease + + // compute rectangle + tools::Long nDX = nPrgsWidth + nOffset; + tools::Long nLeft = rPos.X() + ((nPerc1 - 1) * nDX); + tools::Rectangle aRect(nLeft, rPos.Y(), nLeft + nPrgsWidth, rPos.Y() + nPrgsHeight); + + do + { + rRenderContext.Erase(aRect); + aRect.AdjustLeft( -nDX ); + aRect.AdjustRight( -nDX ); + nPerc1--; + } + while (nPerc1 > nPerc2); + } + else if (nPerc1 < nPerc2) + { + // draw Percent rectangle + // if Percent2 greater than 100%, adapt values + if (nPercent2 > 10000) + { + nPerc2 = 10000 / nPercentCount; + if (nPerc1 >= nPerc2) + nPerc1 = nPerc2 - 1; + } + + // compute rectangle + tools::Long nDX = nPrgsWidth + nOffset; + tools::Long nLeft = rPos.X() + (nPerc1 * nDX); + tools::Rectangle aRect(nLeft, rPos.Y(), nLeft + nPrgsWidth, rPos.Y() + nPrgsHeight); + + do + { + rRenderContext.DrawRect(aRect); + aRect.AdjustLeft(nDX ); + aRect.AdjustRight(nDX ); + nPerc1++; + } + while (nPerc1 < nPerc2); + + // if greater than 100%, set rectangle to blink + if (nPercent2 > 10000) + { + // define on/off status + if (((nPercent2 / nPercentCount) & 0x01) == (nPercentCount & 0x01)) + { + aRect.AdjustLeft( -nDX ); + aRect.AdjustRight( -nDX ); + rRenderContext.Erase(aRect); + } + } + } +} + +void StatusBar::ImplDrawProgress(vcl::RenderContext& rRenderContext, sal_uInt16 nPercent2) +{ + bool bNative = rRenderContext.IsNativeControlSupported(ControlType::Progress, ControlPart::Entire); + // bPaint: draw text also, else only update progress + rRenderContext.DrawText(maPrgsTxtPos, maPrgsTxt); + if (!bNative) + { + DecorationView aDecoView(&rRenderContext); + aDecoView.DrawFrame(maPrgsFrameRect, DrawFrameStyle::In); + } + + Point aPos(maPrgsFrameRect.Left() + STATUSBAR_PRGS_OFFSET, + maPrgsFrameRect.Top() + STATUSBAR_PRGS_OFFSET); + tools::Long nPrgsHeight = mnPrgsSize; + if (bNative) + { + aPos = maPrgsFrameRect.TopLeft(); + nPrgsHeight = maPrgsFrameRect.GetHeight(); + } + DrawProgress(this, rRenderContext, aPos, mnPrgsSize / 2, mnPrgsSize, nPrgsHeight, + 0, nPercent2 * 100, mnPercentCount, maPrgsFrameRect); +} + +void StatusBar::ImplCalcProgressRect() +{ + // calculate text size + Size aPrgsTxtSize( GetTextWidth( maPrgsTxt ), GetTextHeight() ); + maPrgsTxtPos.setX( STATUSBAR_OFFSET_X+1 ); + + // calculate progress frame + maPrgsFrameRect.SetLeft( maPrgsTxtPos.X()+aPrgsTxtSize.Width()+STATUSBAR_OFFSET ); + maPrgsFrameRect.SetTop( STATUSBAR_OFFSET_Y ); + maPrgsFrameRect.SetBottom( mnCalcHeight - STATUSBAR_OFFSET_Y ); + + // calculate size of progress rects + mnPrgsSize = maPrgsFrameRect.Bottom()-maPrgsFrameRect.Top()-(STATUSBAR_PRGS_OFFSET*2); + sal_uInt16 nMaxPercent = STATUSBAR_PRGS_COUNT; + + tools::Long nMaxWidth = mnDX-STATUSBAR_OFFSET-1; + + // make smaller if there are too many rects + while ( maPrgsFrameRect.Left()+ImplCalcProgressWidth( nMaxPercent, mnPrgsSize ) > nMaxWidth ) + { + nMaxPercent--; + if ( nMaxPercent <= STATUSBAR_PRGS_MIN ) + break; + } + maPrgsFrameRect.SetRight( maPrgsFrameRect.Left() + ImplCalcProgressWidth( nMaxPercent, mnPrgsSize ) ); + + // save the divisor for later + mnPercentCount = 10000 / nMaxPercent; + bool bNativeOK = false; + if( IsNativeControlSupported( ControlType::Progress, ControlPart::Entire ) ) + { + ImplControlValue aValue; + tools::Rectangle aControlRegion( tools::Rectangle( Point(), maPrgsFrameRect.GetSize() ) ); + tools::Rectangle aNativeControlRegion, aNativeContentRegion; + if( (bNativeOK = GetNativeControlRegion( ControlType::Progress, ControlPart::Entire, aControlRegion, + ControlState::ENABLED, aValue, + aNativeControlRegion, aNativeContentRegion ) ) ) + { + tools::Long nProgressHeight = aNativeControlRegion.GetHeight(); + if( nProgressHeight > maPrgsFrameRect.GetHeight() ) + { + tools::Long nDelta = nProgressHeight - maPrgsFrameRect.GetHeight(); + maPrgsFrameRect.AdjustTop( -(nDelta - nDelta/2) ); + maPrgsFrameRect.AdjustBottom(nDelta/2 ); + } + maPrgsTxtPos.setY( maPrgsFrameRect.Top() + (nProgressHeight - GetTextHeight())/2 ); + } + } + if( ! bNativeOK ) + maPrgsTxtPos.setY( mnTextY ); +} + +void StatusBar::MouseButtonDown( const MouseEvent& rMEvt ) +{ + // trigger toolbox only for left mouse button + if ( !rMEvt.IsLeft() ) + return; + + Point aMousePos = rMEvt.GetPosPixel(); + + // search for clicked item + for ( size_t i = 0; i < mvItemList.size(); ++i ) + { + ImplStatusItem* pItem = mvItemList[ i ].get(); + // check item for being clicked + if ( ImplGetItemRectPos( sal_uInt16(i) ).Contains( aMousePos ) ) + { + mnCurItemId = pItem->mnId; + if ( rMEvt.GetClicks() == 2 ) + DoubleClick(); + else + Click(); + mnCurItemId = 0; + + // Item found + return; + } + } + + // if there's no item, trigger Click or DoubleClick + if ( rMEvt.GetClicks() == 2 ) + DoubleClick(); + else + Click(); +} + +void StatusBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + if (mbFormat) + ImplFormat(); + + sal_uInt16 nItemCount = sal_uInt16( mvItemList.size() ); + + if (mbProgressMode) + { + rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR); + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + Color aProgressColor = rStyleSettings.GetHighlightColor(); + if (aProgressColor == rStyleSettings.GetFaceColor()) + aProgressColor = rStyleSettings.GetDarkShadowColor(); + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(aProgressColor); + + ImplDrawProgress(rRenderContext, mnPercent); + + rRenderContext.Pop(); + } + else + { + // draw text + if (GetStyle() & WB_RIGHT) + ImplDrawText(rRenderContext); + + // draw items + + // Do offscreen only when we are not recording layout... + bool bOffscreen = !rRenderContext.ImplIsRecordLayout(); + + if (!bOffscreen) + rRenderContext.Erase(rRect); + + for (sal_uInt16 i = 0; i < nItemCount; i++) + ImplDrawItem(rRenderContext, bOffscreen, i); + } + + // draw line at the top of the status bar (to visually distinguish it from + // shell / docking area) + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + rRenderContext.DrawLine(Point(0, 0), Point(mnDX-1, 0)); +} + +void StatusBar::Resize() +{ + // save width and height + Size aSize = GetOutputSizePixel(); + mnDX = aSize.Width() - ImplGetSVData()->maNWFData.mnStatusBarLowerRightOffset; + mnDY = aSize.Height(); + mnCalcHeight = mnDY; + + mnTextY = (mnCalcHeight-GetTextHeight())/2; + + // provoke re-formatting + mbFormat = true; + + if ( mbProgressMode ) + ImplCalcProgressRect(); + + Invalidate(); +} + +void StatusBar::RequestHelp( const HelpEvent& rHEvt ) +{ + // no keyboard help in status bar + if( rHEvt.KeyboardActivated() ) + return; + + sal_uInt16 nItemId = GetItemId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) ); + + if ( nItemId ) + { + tools::Rectangle aItemRect = GetItemRect( nItemId ); + Point aPt = OutputToScreenPixel( aItemRect.TopLeft() ); + aItemRect.SetLeft( aPt.X() ); + aItemRect.SetTop( aPt.Y() ); + aPt = OutputToScreenPixel( aItemRect.BottomRight() ); + aItemRect.SetRight( aPt.X() ); + aItemRect.SetBottom( aPt.Y() ); + + if ( rHEvt.GetMode() & HelpEventMode::BALLOON ) + { + OUString aStr = GetHelpText( nItemId ); + Help::ShowBalloon( this, aItemRect.Center(), aItemRect, aStr ); + return; + } + else if ( rHEvt.GetMode() & HelpEventMode::QUICK ) + { + OUString aStr(GetQuickHelpText(nItemId)); + // show quickhelp if available + if (!aStr.isEmpty()) + { + Help::ShowQuickHelp( this, aItemRect, aStr ); + return; + } + aStr = GetItemText( nItemId ); + // show a quick help if item text doesn't fit + if ( GetTextWidth( aStr ) > aItemRect.GetWidth() ) + { + Help::ShowQuickHelp( this, aItemRect, aStr ); + return; + } + } + } + + Window::RequestHelp( rHEvt ); +} + +void StatusBar::StateChanged( StateChangedType nType ) +{ + Window::StateChanged( nType ); + + if ( nType == StateChangedType::InitShow ) + ImplFormat(); + else if ( nType == StateChangedType::UpdateMode ) + Invalidate(); + else if ( (nType == StateChangedType::Zoom) || + (nType == StateChangedType::ControlFont) ) + { + mbFormat = true; + ImplInitSettings(); + Invalidate(); + } + else if ( nType == StateChangedType::ControlForeground ) + { + ImplInitSettings(); + Invalidate(); + } + else if ( nType == StateChangedType::ControlBackground ) + { + ImplInitSettings(); + Invalidate(); + } + + //invalidate layout cache + for (auto & pItem : mvItemList) + { + pItem->mLayoutGlyphsCache.reset(); + } + +} + +void StatusBar::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + + if ( !((rDCEvt.GetType() == DataChangedEventType::DISPLAY ) + || (rDCEvt.GetType() == DataChangedEventType::FONTS ) + || (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) + || ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) + && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE ) + )) + ) + return; + + mbFormat = true; + ImplInitSettings(); + tools::Long nFudge = GetTextHeight() / 4; + for (auto & pItem : mvItemList) + { + tools::Long nWidth = GetTextWidth( pItem->maText ) + nFudge; + if( nWidth > pItem->mnWidth + STATUSBAR_OFFSET ) + pItem->mnWidth = nWidth + STATUSBAR_OFFSET; + + pItem->mLayoutGlyphsCache.reset(); + } + Size aSize = GetSizePixel(); + // do not disturb current width, since + // CalcWindowSizePixel calculates a minimum width + aSize.setHeight( CalcWindowSizePixel().Height() ); + SetSizePixel( aSize ); + Invalidate(); +} + +void StatusBar::Click() +{ + maClickHdl.Call( this ); +} + +void StatusBar::DoubleClick() +{ + maDoubleClickHdl.Call( this ); +} + +void StatusBar::UserDraw( const UserDrawEvent& ) +{ +} + +void StatusBar::InsertItem( sal_uInt16 nItemId, sal_uLong nWidth, + StatusBarItemBits nBits, + tools::Long nOffset, sal_uInt16 nPos ) +{ + SAL_WARN_IF( !nItemId, "vcl", "StatusBar::InsertItem(): ItemId == 0" ); + SAL_WARN_IF( GetItemPos( nItemId ) != STATUSBAR_ITEM_NOTFOUND, "vcl", + "StatusBar::InsertItem(): ItemId already exists" ); + + // default: IN and CENTER + if ( !(nBits & (StatusBarItemBits::In | StatusBarItemBits::Out | StatusBarItemBits::Flat)) ) + nBits |= StatusBarItemBits::In; + if ( !(nBits & (StatusBarItemBits::Left | StatusBarItemBits::Right | StatusBarItemBits::Center)) ) + nBits |= StatusBarItemBits::Center; + + // create item + if (mbAdjustHiDPI) + { + nWidth *= GetDPIScaleFactor(); + } + tools::Long nFudge = GetTextHeight()/4; + std::unique_ptr<ImplStatusItem> pItem(new ImplStatusItem); + pItem->mnId = nItemId; + pItem->mnBits = nBits; + pItem->mnWidth = static_cast<tools::Long>(nWidth)+nFudge+STATUSBAR_OFFSET; + pItem->mnOffset = nOffset; + pItem->mpUserData = nullptr; + pItem->mbVisible = true; + + // add item to list + if ( nPos < mvItemList.size() ) { + mvItemList.insert( mvItemList.begin() + nPos, std::move(pItem) ); + } else { + mvItemList.push_back( std::move(pItem) ); + } + + mbFormat = true; + if ( ImplIsItemUpdate() ) + Invalidate(); + + CallEventListeners( VclEventId::StatusbarItemAdded, reinterpret_cast<void*>(nItemId) ); +} + +void StatusBar::RemoveItem( sal_uInt16 nItemId ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + if ( nPos != STATUSBAR_ITEM_NOTFOUND ) + { + mvItemList.erase( mvItemList.begin() + nPos ); + + mbFormat = true; + if ( ImplIsItemUpdate() ) + Invalidate(); + + CallEventListeners( VclEventId::StatusbarItemRemoved, reinterpret_cast<void*>(nItemId) ); + } +} + +void StatusBar::ShowItem( sal_uInt16 nItemId ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos == STATUSBAR_ITEM_NOTFOUND ) + return; + + ImplStatusItem* pItem = mvItemList[ nPos ].get(); + if ( !pItem->mbVisible ) + { + pItem->mbVisible = true; + + mbFormat = true; + if ( ImplIsItemUpdate() ) + Invalidate(); + + CallEventListeners( VclEventId::StatusbarShowItem, reinterpret_cast<void*>(nItemId) ); + } +} + +void StatusBar::HideItem( sal_uInt16 nItemId ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos == STATUSBAR_ITEM_NOTFOUND ) + return; + + ImplStatusItem* pItem = mvItemList[ nPos ].get(); + if ( pItem->mbVisible ) + { + pItem->mbVisible = false; + + mbFormat = true; + if ( ImplIsItemUpdate() ) + Invalidate(); + + CallEventListeners( VclEventId::StatusbarHideItem, reinterpret_cast<void*>(nItemId) ); + } +} + +bool StatusBar::IsItemVisible( sal_uInt16 nItemId ) const +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos != STATUSBAR_ITEM_NOTFOUND ) + return mvItemList[ nPos ]->mbVisible; + else + return false; +} + +void StatusBar::Clear() +{ + // delete all items + mvItemList.clear(); + + mbFormat = true; + if ( ImplIsItemUpdate() ) + Invalidate(); + + CallEventListeners( VclEventId::StatusbarAllItemsRemoved ); +} + +sal_uInt16 StatusBar::GetItemCount() const +{ + return static_cast<sal_uInt16>(mvItemList.size()); +} + +sal_uInt16 StatusBar::GetItemId( sal_uInt16 nPos ) const +{ + if ( nPos < mvItemList.size() ) + return mvItemList[ nPos ]->mnId; + return 0; +} + +sal_uInt16 StatusBar::GetItemPos( sal_uInt16 nItemId ) const +{ + for ( size_t i = 0, n = mvItemList.size(); i < n; ++i ) { + if ( mvItemList[ i ]->mnId == nItemId ) { + return sal_uInt16( i ); + } + } + + return STATUSBAR_ITEM_NOTFOUND; +} + +sal_uInt16 StatusBar::GetItemId( const Point& rPos ) const +{ + if ( !mbFormat ) + { + sal_uInt16 nItemCount = GetItemCount(); + sal_uInt16 nPos; + for ( nPos = 0; nPos < nItemCount; nPos++ ) + { + // get rectangle + tools::Rectangle aRect = ImplGetItemRectPos( nPos ); + if ( aRect.Contains( rPos ) ) + return mvItemList[ nPos ]->mnId; + } + } + + return 0; +} + +tools::Rectangle StatusBar::GetItemRect( sal_uInt16 nItemId ) const +{ + tools::Rectangle aRect; + + if ( !mbFormat ) + { + sal_uInt16 nPos = GetItemPos( nItemId ); + if ( nPos != STATUSBAR_ITEM_NOTFOUND ) + { + // get rectangle and subtract frame + aRect = ImplGetItemRectPos( nPos ); + tools::Long nW = 1; + aRect.AdjustTop(nW-1 ); + aRect.AdjustBottom( -(nW-1) ); + aRect.AdjustLeft(nW ); + aRect.AdjustRight( -nW ); + return aRect; + } + } + + return aRect; +} + +Point StatusBar::GetItemTextPos( sal_uInt16 nItemId ) const +{ + if ( !mbFormat ) + { + sal_uInt16 nPos = GetItemPos( nItemId ); + if ( nPos != STATUSBAR_ITEM_NOTFOUND ) + { + // get rectangle + ImplStatusItem* pItem = mvItemList[ nPos ].get(); + tools::Rectangle aRect = ImplGetItemRectPos( nPos ); + tools::Long nW = 1; + tools::Rectangle aTextRect( aRect.Left()+nW, aRect.Top()+nW, + aRect.Right()-nW, aRect.Bottom()-nW ); + Point aPos = ImplGetItemTextPos( aTextRect.GetSize(), + Size( GetTextWidth( pItem->maText ), GetTextHeight() ), + pItem->mnBits ); + if ( !mbInUserDraw ) + { + aPos.AdjustX(aTextRect.Left() ); + aPos.AdjustY(aTextRect.Top() ); + } + return aPos; + } + } + + return Point(); +} + +sal_uLong StatusBar::GetItemWidth( sal_uInt16 nItemId ) const +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos != STATUSBAR_ITEM_NOTFOUND ) + return mvItemList[ nPos ]->mnWidth; + + return 0; +} + +StatusBarItemBits StatusBar::GetItemBits( sal_uInt16 nItemId ) const +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos != STATUSBAR_ITEM_NOTFOUND ) + return mvItemList[ nPos ]->mnBits; + + return StatusBarItemBits::NONE; +} + +tools::Long StatusBar::GetItemOffset( sal_uInt16 nItemId ) const +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos != STATUSBAR_ITEM_NOTFOUND ) + return mvItemList[ nPos ]->mnOffset; + + return 0; +} + +void StatusBar::SetItemText( sal_uInt16 nItemId, const OUString& rText, int nCharsWidth ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos == STATUSBAR_ITEM_NOTFOUND ) + return; + + ImplStatusItem* pItem = mvItemList[ nPos ].get(); + + if ( pItem->maText == rText ) + return; + + pItem->maText = rText; + + // adjust item width - see also DataChanged() + tools::Long nFudge = GetTextHeight()/4; + + tools::Long nWidth; + if (nCharsWidth != -1) + { + nWidth = GetTextWidth("0",0,-1,nullptr, + SalLayoutGlyphsCache::self()->GetLayoutGlyphs(GetOutDev(),"0")); + nWidth = nWidth * nCharsWidth + nFudge; + } + else + { + pItem->mLayoutGlyphsCache.reset(); + nWidth = GetTextWidth( pItem->maText,0,-1,nullptr, pItem->GetTextGlyphs(GetOutDev())) + nFudge; + } + + if( (nWidth > pItem->mnWidth + STATUSBAR_OFFSET) || + ((nWidth < pItem->mnWidth) && (mnDX - STATUSBAR_OFFSET) < mnItemsWidth )) + { + pItem->mnWidth = nWidth + STATUSBAR_OFFSET; + ImplFormat(); + Invalidate(); + } + + // re-draw item if StatusBar is visible and UpdateMode active + if ( pItem->mbVisible && !mbFormat && ImplIsItemUpdate() ) + { + tools::Rectangle aRect = ImplGetItemRectPos(nPos); + Invalidate(aRect); + PaintImmediately(); + } +} + +const OUString& StatusBar::GetItemText( sal_uInt16 nItemId ) const +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + assert( nPos != STATUSBAR_ITEM_NOTFOUND ); + + return mvItemList[ nPos ]->maText; +} + +void StatusBar::SetItemCommand( sal_uInt16 nItemId, const OUString& rCommand ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos != STATUSBAR_ITEM_NOTFOUND ) + { + ImplStatusItem* pItem = mvItemList[ nPos ].get(); + + if ( pItem->maCommand != rCommand ) + pItem->maCommand = rCommand; + } +} + +OUString StatusBar::GetItemCommand( sal_uInt16 nItemId ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos != STATUSBAR_ITEM_NOTFOUND ) + return mvItemList[ nPos ]->maCommand; + + return OUString(); +} + +void StatusBar::SetItemData( sal_uInt16 nItemId, void* pNewData ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos == STATUSBAR_ITEM_NOTFOUND ) + return; + + ImplStatusItem* pItem = mvItemList[ nPos ].get(); + // invalidate cache + pItem->mLayoutGlyphsCache.reset(); + pItem->mpUserData = pNewData; + + // call Draw-Item if it's a User-Item + if ( (pItem->mnBits & StatusBarItemBits::UserDraw) && pItem->mbVisible && + !mbFormat && ImplIsItemUpdate() ) + { + tools::Rectangle aRect = ImplGetItemRectPos(nPos); + Invalidate(aRect, InvalidateFlags::NoErase); + PaintImmediately(); + } +} + +void* StatusBar::GetItemData( sal_uInt16 nItemId ) const +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos != STATUSBAR_ITEM_NOTFOUND ) + return mvItemList[ nPos ]->mpUserData; + + return nullptr; +} + +void StatusBar::RedrawItem(sal_uInt16 nItemId) +{ + if ( mbFormat ) + return; + + sal_uInt16 nPos = GetItemPos(nItemId); + if ( nPos == STATUSBAR_ITEM_NOTFOUND ) + return; + + ImplStatusItem* pItem = mvItemList[ nPos ].get(); + if ((pItem->mnBits & StatusBarItemBits::UserDraw) && + pItem->mbVisible && ImplIsItemUpdate()) + { + tools::Rectangle aRect = ImplGetItemRectPos(nPos); + Invalidate(aRect); + PaintImmediately(); + } +} + +void StatusBar::SetHelpText( sal_uInt16 nItemId, const OUString& rText ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos != STATUSBAR_ITEM_NOTFOUND ) + mvItemList[ nPos ]->maHelpText = rText; +} + +const OUString& StatusBar::GetHelpText( sal_uInt16 nItemId ) const +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + assert ( nPos != STATUSBAR_ITEM_NOTFOUND ); + + ImplStatusItem* pItem = mvItemList[ nPos ].get(); + if ( pItem->maHelpText.isEmpty() && ( !pItem->maHelpId.isEmpty() || !pItem->maCommand.isEmpty() )) + { + Help* pHelp = Application::GetHelp(); + if ( pHelp ) + { + if ( !pItem->maCommand.isEmpty() ) + pItem->maHelpText = pHelp->GetHelpText( pItem->maCommand, this ); + if ( pItem->maHelpText.isEmpty() && !pItem->maHelpId.isEmpty() ) + pItem->maHelpText = pHelp->GetHelpText( OStringToOUString( pItem->maHelpId, RTL_TEXTENCODING_UTF8 ), this ); + } + } + + return pItem->maHelpText; +} + +void StatusBar::SetQuickHelpText( sal_uInt16 nItemId, const OUString& rText ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos != STATUSBAR_ITEM_NOTFOUND ) + mvItemList[ nPos ]->maQuickHelpText = rText; +} + +const OUString& StatusBar::GetQuickHelpText( sal_uInt16 nItemId ) const +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + assert ( nPos != STATUSBAR_ITEM_NOTFOUND ); + + ImplStatusItem* pItem = mvItemList[ nPos ].get(); + return pItem->maQuickHelpText; +} + +void StatusBar::SetHelpId( sal_uInt16 nItemId, const OString& rHelpId ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos != STATUSBAR_ITEM_NOTFOUND ) + mvItemList[ nPos ]->maHelpId = rHelpId; +} + +void StatusBar::StartProgressMode( const OUString& rText ) +{ + SAL_WARN_IF( mbProgressMode, "vcl", "StatusBar::StartProgressMode(): progress mode is active" ); + + mbProgressMode = true; + mnPercent = 0; + maPrgsTxt = rText; + + // compute size + ImplCalcProgressRect(); + + // trigger Paint, which draws text and frame + if ( IsReallyVisible() ) + { + Invalidate(); + PaintImmediately(); + } +} + +void StatusBar::SetProgressValue( sal_uInt16 nNewPercent ) +{ + SAL_WARN_IF( !mbProgressMode, "vcl", "StatusBar::SetProgressValue(): no progress mode" ); + SAL_WARN_IF( nNewPercent > 100, "vcl", "StatusBar::SetProgressValue(): nPercent > 100" ); + + bool bInvalidate = mbProgressMode && IsReallyVisible() && (!mnPercent || (mnPercent != nNewPercent)); + + mnPercent = nNewPercent; + + if (bInvalidate) + { + // Rate limit how often we paint, otherwise in some loading scenarios we can spend significant + // time just painting progress bars. + sal_uInt32 nTime_ms = osl_getGlobalTimer(); + if ((nTime_ms - mnLastProgressPaint_ms) > 100) + { + Invalidate(maPrgsFrameRect); + PaintImmediately(); + mnLastProgressPaint_ms = nTime_ms; + } + } +} + +void StatusBar::EndProgressMode() +{ + SAL_WARN_IF( !mbProgressMode, "vcl", "StatusBar::EndProgressMode(): no progress mode" ); + + mbProgressMode = false; + maPrgsTxt.clear(); + + if ( IsReallyVisible() ) + { + Invalidate(); + PaintImmediately(); + } +} + +void StatusBar::SetText(const OUString& rText) +{ + if ((GetStyle() & WB_RIGHT) && !mbProgressMode && IsReallyVisible() && IsUpdateMode()) + { + if (mbFormat) + { + Invalidate(); + Window::SetText(rText); + } + else + { + Invalidate(); + Window::SetText(rText); + PaintImmediately(); + } + } + else if (mbProgressMode) + { + maPrgsTxt = rText; + if (IsReallyVisible()) + { + Invalidate(); + PaintImmediately(); + } + } + else + { + Window::SetText(rText); + } +} + +Size StatusBar::CalcWindowSizePixel() const +{ + size_t i = 0; + size_t nCount = mvItemList.size(); + tools::Long nOffset = 0; + tools::Long nCalcWidth = STATUSBAR_OFFSET_X*2; + tools::Long nCalcHeight; + + while ( i < nCount ) + { + ImplStatusItem* pItem = mvItemList[ i ].get(); + nCalcWidth += pItem->mnWidth + nOffset; + nOffset = pItem->mnOffset; + i++; + } + + tools::Long nMinHeight = GetTextHeight(); + const tools::Long nBarTextOffset = STATUSBAR_OFFSET_TEXTY*2; + tools::Long nProgressHeight = nMinHeight + nBarTextOffset; + + if( IsNativeControlSupported( ControlType::Progress, ControlPart::Entire ) ) + { + ImplControlValue aValue; + tools::Rectangle aControlRegion( Point(), Size( nCalcWidth, nMinHeight ) ); + tools::Rectangle aNativeControlRegion, aNativeContentRegion; + if( GetNativeControlRegion( ControlType::Progress, ControlPart::Entire, + aControlRegion, ControlState::ENABLED, aValue, + aNativeControlRegion, aNativeContentRegion ) ) + { + nProgressHeight = aNativeControlRegion.GetHeight(); + } + } + + nCalcHeight = nMinHeight+nBarTextOffset; + if( nCalcHeight < nProgressHeight+2 ) + nCalcHeight = nProgressHeight+2; + + return Size( nCalcWidth, nCalcHeight ); +} + +void StatusBar::SetAccessibleName( sal_uInt16 nItemId, const OUString& rName ) +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + if ( nPos != STATUSBAR_ITEM_NOTFOUND ) + { + ImplStatusItem* pItem = mvItemList[ nPos ].get(); + + if ( pItem->maAccessibleName != rName ) + { + pItem->maAccessibleName = rName; + CallEventListeners( VclEventId::StatusbarNameChanged, reinterpret_cast<void*>(pItem->mnId) ); + } + } +} + +const OUString& StatusBar::GetAccessibleName( sal_uInt16 nItemId ) const +{ + sal_uInt16 nPos = GetItemPos( nItemId ); + + assert ( nPos != STATUSBAR_ITEM_NOTFOUND ); + + return mvItemList[ nPos ]->maAccessibleName; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/syschild.cxx b/vcl/source/window/syschild.cxx new file mode 100644 index 000000000..644aa1f09 --- /dev/null +++ b/vcl/source/window/syschild.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 <vcl/window.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/svapp.hxx> +#include <vcl/syschild.hxx> + +#include <window.h> +#include <salframe.hxx> +#include <salinst.hxx> +#include <salobj.hxx> +#include <svdata.hxx> + +using namespace ::com::sun::star; + +static void ImplSysChildProc( SystemChildWindow* pInst, SalObjEvent nEvent ) +{ + VclPtr<SystemChildWindow> pWindow = pInst; + + switch ( nEvent ) + { + case SalObjEvent::GetFocus: + // get focus, such that all handlers are called, + // as if this window gets the focus assuring + // that the frame does not steal it + pWindow->ImplGetFrameData()->mbSysObjFocus = true; + pWindow->ImplGetFrameData()->mbInSysObjToTopHdl = true; + pWindow->ToTop( ToTopFlags::NoGrabFocus ); + if( pWindow->isDisposed() ) + break; + pWindow->ImplGetFrameData()->mbInSysObjToTopHdl = false; + pWindow->ImplGetFrameData()->mbInSysObjFocusHdl = true; + pWindow->GrabFocus(); + if( pWindow->isDisposed() ) + break; + pWindow->ImplGetFrameData()->mbInSysObjFocusHdl = false; + break; + + case SalObjEvent::LoseFocus: + // trigger a LoseFocus which matches the status + // of the window with matching Activate-Status + if (pWindow->isDisposed()) + break; + pWindow->ImplGetFrameData()->mbSysObjFocus = false; + if ( !pWindow->ImplGetFrameData()->mnFocusId ) + { + pWindow->ImplGetFrameData()->mbStartFocusState = true; + pWindow->ImplGetFrameData()->mnFocusId = Application::PostUserEvent( LINK( pWindow->ImplGetFrameWindow(), vcl::Window, ImplAsyncFocusHdl ), nullptr, true ); + } + break; + + case SalObjEvent::ToTop: + pWindow->ImplGetFrameData()->mbInSysObjToTopHdl = true; + if ( !Application::GetFocusWindow() || pWindow->HasChildPathFocus() ) + pWindow->ToTop( ToTopFlags::NoGrabFocus ); + else + pWindow->ToTop(); + if( pWindow->isDisposed() ) + break; + pWindow->GrabFocus(); + if( pWindow->isDisposed() ) + break; + pWindow->ImplGetFrameData()->mbInSysObjToTopHdl = false; + break; + + default: break; + } +} + +void SystemChildWindow::ImplInitSysChild( vcl::Window* pParent, WinBits nStyle, SystemWindowData *pData, bool bShow ) +{ + mpWindowImpl->mpSysObj = ImplGetSVData()->mpDefInst->CreateObject( pParent->ImplGetFrame(), pData, bShow ); + + Window::ImplInit( pParent, nStyle, nullptr ); + + // we do not paint if it is the right SysChild + if ( GetSystemData() ) + { + mpWindowImpl->mpSysObj->SetCallback( this, ImplSysChildProc ); + SetParentClipMode( ParentClipMode::Clip ); + SetBackground(); + } +} + +SystemChildWindow::SystemChildWindow( vcl::Window* pParent, WinBits nStyle ) : + Window( WindowType::SYSTEMCHILDWINDOW ) +{ + ImplInitSysChild( pParent, nStyle, nullptr ); +} + +SystemChildWindow::SystemChildWindow( vcl::Window* pParent, WinBits nStyle, SystemWindowData *pData, bool bShow ) : + Window( WindowType::SYSTEMCHILDWINDOW ) +{ + ImplInitSysChild( pParent, nStyle, pData, bShow ); +} + +SystemChildWindow::~SystemChildWindow() +{ + disposeOnce(); +} + +void SystemChildWindow::dispose() +{ + Hide(); + if ( mpWindowImpl && mpWindowImpl->mpSysObj ) + { + ImplGetSVData()->mpDefInst->DestroyObject( mpWindowImpl->mpSysObj ); + mpWindowImpl->mpSysObj = nullptr; + } + Window::dispose(); +} + +const SystemEnvData* SystemChildWindow::GetSystemData() const +{ + if ( mpWindowImpl->mpSysObj ) + return mpWindowImpl->mpSysObj->GetSystemData(); + else + return nullptr; +} + +void SystemChildWindow::EnableEraseBackground( bool bEnable ) +{ + if ( mpWindowImpl->mpSysObj ) + mpWindowImpl->mpSysObj->EnableEraseBackground( bEnable ); +} + +Size SystemChildWindow::GetOptimalSize() const +{ + if (mpWindowImpl->mpSysObj) + return mpWindowImpl->mpSysObj->GetOptimalSize(); + return vcl::Window::GetOptimalSize(); +} + +void SystemChildWindow::SetLeaveEnterBackgrounds(const css::uno::Sequence<css::uno::Any>& rLeaveArgs, const css::uno::Sequence<css::uno::Any>& rEnterArgs) +{ + if (mpWindowImpl->mpSysObj) + mpWindowImpl->mpSysObj->SetLeaveEnterBackgrounds(rLeaveArgs, rEnterArgs); +} + +void SystemChildWindow::SetForwardKey( bool bEnable ) +{ + if ( mpWindowImpl->mpSysObj ) + mpWindowImpl->mpSysObj->SetForwardKey( bEnable ); +} + +sal_IntPtr SystemChildWindow::GetParentWindowHandle() const +{ + sal_IntPtr nRet = 0; + +#if defined(_WIN32) + nRet = reinterpret_cast< sal_IntPtr >( GetSystemData()->hWnd ); +#elif defined MACOSX + // FIXME: this is wrong + nRet = reinterpret_cast< sal_IntPtr >( GetSystemData()->mpNSView ); +#elif defined ANDROID + // Nothing +#elif defined IOS + // Nothing +#elif defined UNX + nRet = GetSystemData()->GetWindowHandle(ImplGetFrame()); +#endif + + return nRet; +} + +void* SystemChildWindow::CreateGStreamerSink() +{ + return ImplGetSVData()->mpDefInst->CreateGStreamerSink(this); +} + +#if defined( MACOSX ) +#elif defined( ANDROID ) +#elif defined( IOS ) +#elif defined( UNX ) +sal_uIntPtr SystemEnvData::GetWindowHandle(const SalFrame* pReference) const +{ + if (!aWindow && pReference) + pReference->ResolveWindowHandle(const_cast<SystemEnvData&>(*this)); + return aWindow; +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/syswin.cxx b/vcl/source/window/syswin.cxx new file mode 100644 index 000000000..40fff00aa --- /dev/null +++ b/vcl/source/window/syswin.cxx @@ -0,0 +1,1183 @@ +/* -*- 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 <o3tl/safeint.hxx> +#include <sal/config.h> +#include <sal/log.hxx> + +#include <vcl/layout.hxx> +#include <vcl/mnemonic.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/menu.hxx> +#include <vcl/event.hxx> +#include <vcl/syswin.hxx> +#include <vcl/taskpanelist.hxx> +#include <vcl/tabctrl.hxx> +#include <vcl/tabpage.hxx> +#include <vcl/virdev.hxx> + +#include <rtl/strbuf.hxx> +#include <o3tl/string_view.hxx> + +#include <accel.hxx> +#include <salframe.hxx> +#include <svdata.hxx> +#include <brdwin.hxx> +#include <window.h> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; + +class SystemWindow::ImplData +{ +public: + ImplData(); + + std::unique_ptr<TaskPaneList> + mpTaskPaneList; + Size maMaxOutSize; + OUString maRepresentedURL; + Link<SystemWindow&,void> maCloseHdl; +}; + +SystemWindow::ImplData::ImplData() +{ + mpTaskPaneList = nullptr; + maMaxOutSize = Size( SHRT_MAX, SHRT_MAX ); +} + +SystemWindow::SystemWindow(WindowType nType, const char* pIdleDebugName) + : Window(nType) + , mbDockBtn(false) + , mbHideBtn(false) + , mbSysChild(false) + , mbIsCalculatingInitialLayoutSize(false) + , mbInitialLayoutSizeCalculated(false) + , mbPaintComplete(false) + , mnMenuBarMode(MenuBarMode::Normal) + , mnIcon(0) + , mpImplData(new ImplData) + , maLayoutIdle( pIdleDebugName ) + , mbIsDeferredInit(false) +{ + mpWindowImpl->mbSysWin = true; + mpWindowImpl->mnActivateMode = ActivateModeFlags::GrabFocus; + + //To-Do, reuse maResizeTimer + maLayoutIdle.SetPriority(TaskPriority::RESIZE); + maLayoutIdle.SetInvokeHandler( LINK( this, SystemWindow, ImplHandleLayoutTimerHdl ) ); +} + +void SystemWindow::loadUI(vcl::Window* pParent, const OString& rID, const OUString& rUIXMLDescription, + const css::uno::Reference<css::frame::XFrame> &rFrame) +{ + mbIsDeferredInit = true; + mpDialogParent = pParent; //should be unset in doDeferredInit + m_pUIBuilder.reset( new VclBuilder(this, AllSettings::GetUIRootDir(), rUIXMLDescription, rID, rFrame) ); +} + +SystemWindow::~SystemWindow() +{ + disposeOnce(); +} + +void SystemWindow::dispose() +{ + maLayoutIdle.Stop(); + mpImplData.reset(); + + // Hack to make sure code called from base ~Window does not interpret this + // as a SystemWindow (which it no longer is by then): + mpWindowImpl->mbSysWin = false; + disposeBuilder(); + mpDialogParent.clear(); + mpMenuBar.clear(); + Window::dispose(); +} + +static void ImplHandleControlAccelerator( const vcl::Window* pWindow, bool bShow ) +{ + Control *pControl = dynamic_cast<Control*>(pWindow->ImplGetWindow()); + if (pControl && pControl->GetText().indexOf('~') != -1) + { + pControl->SetShowAccelerator( bShow ); + pControl->Invalidate(InvalidateFlags::Update); + } +} + +namespace +{ + void processChildren(const vcl::Window *pParent, bool bShowAccel) + { + // go through its children + vcl::Window* pChild = firstLogicalChildOfParent(pParent); + while (pChild) + { + if (pChild->GetType() == WindowType::TABCONTROL) + { + // find currently shown tab page + TabControl* pTabControl = static_cast<TabControl*>(pChild); + TabPage* pTabPage = pTabControl->GetTabPage( pTabControl->GetCurPageId() ); + processChildren(pTabPage, bShowAccel); + } + else if (pChild->GetType() == WindowType::TABPAGE) + { + // bare tabpage without tabcontrol parent (options dialog) + processChildren(pChild, bShowAccel); + } + else if ((pChild->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL) + { + // special controls that manage their children outside of widget layout + processChildren(pChild, bShowAccel); + } + else + { + ImplHandleControlAccelerator(pChild, bShowAccel); + } + pChild = nextLogicalChildOfParent(pParent, pChild); + } + } +} + +namespace +{ + bool ToggleMnemonicsOnHierarchy(const CommandEvent& rCEvent, const vcl::Window *pWindow) + { + if (rCEvent.GetCommand() == CommandEventId::ModKeyChange && ImplGetSVData()->maNWFData.mbAutoAccel) + { + const CommandModKeyData *pCData = rCEvent.GetModKeyData(); + const bool bShowAccel = pCData && pCData->IsMod2() && pCData->IsDown(); + processChildren(pWindow, bShowAccel); + return true; + } + return false; + } +} + +bool SystemWindow::EventNotify( NotifyEvent& rNEvt ) +{ + if (rNEvt.GetType() == MouseNotifyEvent::COMMAND) + ToggleMnemonicsOnHierarchy(*rNEvt.GetCommandEvent(), this); + + // capture KeyEvents for menu handling + if (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) + { + MenuBar* pMBar = mpMenuBar; + if ( !pMBar && ( GetType() == WindowType::FLOATINGWINDOW ) ) + { + vcl::Window* pWin = ImplGetFrameWindow()->ImplGetWindow(); + if( pWin && pWin->IsSystemWindow() ) + pMBar = static_cast<SystemWindow*>(pWin)->GetMenuBar(); + } + if (pMBar && pMBar->ImplHandleKeyEvent(*rNEvt.GetKeyEvent())) + return true; + } + + return Window::EventNotify( rNEvt ); +} + +bool SystemWindow::PreNotify( NotifyEvent& rNEvt ) +{ + // capture KeyEvents for taskpane cycling + if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) + { + if( rNEvt.GetKeyEvent()->GetKeyCode().GetCode() == KEY_F6 && + rNEvt.GetKeyEvent()->GetKeyCode().IsMod1() && + !rNEvt.GetKeyEvent()->GetKeyCode().IsShift() ) + { + // Ctrl-F6 goes directly to the document + GrabFocusToDocument(); + return true; + } + else + { + TaskPaneList *pTList = mpImplData->mpTaskPaneList.get(); + if( !pTList && ( GetType() == WindowType::FLOATINGWINDOW ) ) + { + vcl::Window* pWin = ImplGetFrameWindow()->ImplGetWindow(); + if( pWin && pWin->IsSystemWindow() ) + pTList = static_cast<SystemWindow*>(pWin)->mpImplData->mpTaskPaneList.get(); + } + if( !pTList ) + { + // search topmost system window which is the one to handle dialog/toolbar cycling + SystemWindow *pSysWin = this; + vcl::Window *pWin = this; + while( pWin ) + { + pWin = pWin->GetParent(); + if( pWin && pWin->IsSystemWindow() ) + pSysWin = static_cast<SystemWindow*>(pWin); + } + pTList = pSysWin->mpImplData->mpTaskPaneList.get(); + } + if( pTList && pTList->HandleKeyEvent( *rNEvt.GetKeyEvent() ) ) + return true; + } + } + return Window::PreNotify( rNEvt ); +} + +TaskPaneList* SystemWindow::GetTaskPaneList() +{ + if( !mpImplData ) + return nullptr; + if( mpImplData->mpTaskPaneList ) + return mpImplData->mpTaskPaneList.get(); + else + { + mpImplData->mpTaskPaneList.reset( new TaskPaneList ); + MenuBar* pMBar = mpMenuBar; + if ( !pMBar && ( GetType() == WindowType::FLOATINGWINDOW ) ) + { + vcl::Window* pWin = ImplGetFrameWindow()->ImplGetWindow(); + if ( pWin && pWin->IsSystemWindow() ) + pMBar = static_cast<SystemWindow*>(pWin)->GetMenuBar(); + } + if( pMBar ) + mpImplData->mpTaskPaneList->AddWindow( pMBar->ImplGetWindow() ); + return mpImplData->mpTaskPaneList.get(); + } +} + +bool SystemWindow::Close() +{ + VclPtr<vcl::Window> xWindow = this; + CallEventListeners( VclEventId::WindowClose ); + if ( xWindow->isDisposed() ) + return false; + + if ( mpWindowImpl->mxWindowPeer.is() && IsCreatedWithToolkit() ) + return false; + + // Is Window not closeable, ignore close + vcl::Window* pBorderWin = ImplGetBorderWindow(); + WinBits nStyle; + if ( pBorderWin ) + nStyle = pBorderWin->GetStyle(); + else + nStyle = GetStyle(); + if ( !(nStyle & WB_CLOSEABLE) ) + return false; + + Hide(); + + return true; +} + +void SystemWindow::TitleButtonClick( TitleButton ) +{ +} + +void SystemWindow::Resizing( Size& ) +{ +} + +void SystemWindow::SetRepresentedURL( const OUString& i_rURL ) +{ + bool bChanged = (i_rURL != mpImplData->maRepresentedURL); + mpImplData->maRepresentedURL = i_rURL; + if ( !mbSysChild && bChanged ) + { + const vcl::Window* pWindow = this; + while ( pWindow->mpWindowImpl->mpBorderWindow ) + pWindow = pWindow->mpWindowImpl->mpBorderWindow; + + if ( pWindow->mpWindowImpl->mbFrame ) + pWindow->mpWindowImpl->mpFrame->SetRepresentedURL( i_rURL ); + } +} + +void SystemWindow::SetIcon( sal_uInt16 nIcon ) +{ + if ( mnIcon == nIcon ) + return; + + mnIcon = nIcon; + + if ( !mbSysChild ) + { + const vcl::Window* pWindow = this; + while ( pWindow->mpWindowImpl->mpBorderWindow ) + pWindow = pWindow->mpWindowImpl->mpBorderWindow; + + if ( pWindow->mpWindowImpl->mbFrame ) + pWindow->mpWindowImpl->mpFrame->SetIcon( nIcon ); + } +} + +void SystemWindow::ShowTitleButton( TitleButton nButton, bool bVisible ) +{ + if ( nButton == TitleButton::Docking ) + { + if ( mbDockBtn != bVisible ) + { + mbDockBtn = bVisible; + if ( mpWindowImpl->mpBorderWindow ) + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetDockButton( bVisible ); + } + } + else if ( nButton == TitleButton::Hide ) + { + if ( mbHideBtn != bVisible ) + { + mbHideBtn = bVisible; + if ( mpWindowImpl->mpBorderWindow ) + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetHideButton( bVisible ); + } + } + else if ( nButton == TitleButton::Menu ) + { + if ( mpWindowImpl->mpBorderWindow ) + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuButton( bVisible ); + } + else + return; +} + +bool SystemWindow::IsTitleButtonVisible( TitleButton nButton ) const +{ + if ( nButton == TitleButton::Docking ) + return mbDockBtn; + else /* if ( nButton == TitleButton::Hide ) */ + return mbHideBtn; +} + +void SystemWindow::SetMinOutputSizePixel( const Size& rSize ) +{ + maMinOutSize = rSize; + if ( mpWindowImpl->mpBorderWindow ) + { + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMinOutputSize( rSize.Width(), rSize.Height() ); + if ( mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame ) + mpWindowImpl->mpBorderWindow->mpWindowImpl->mpFrame->SetMinClientSize( rSize.Width(), rSize.Height() ); + } + else if ( mpWindowImpl->mbFrame ) + mpWindowImpl->mpFrame->SetMinClientSize( rSize.Width(), rSize.Height() ); +} + +void SystemWindow::SetMaxOutputSizePixel( const Size& rSize ) +{ + Size aSize( rSize ); + if( aSize.Width() > SHRT_MAX || aSize.Width() <= 0 ) + aSize.setWidth( SHRT_MAX ); + if( aSize.Height() > SHRT_MAX || aSize.Height() <= 0 ) + aSize.setHeight( SHRT_MAX ); + + mpImplData->maMaxOutSize = aSize; + if ( mpWindowImpl->mpBorderWindow ) + { + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMaxOutputSize( aSize.Width(), aSize.Height() ); + if ( mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame ) + mpWindowImpl->mpBorderWindow->mpWindowImpl->mpFrame->SetMaxClientSize( aSize.Width(), aSize.Height() ); + } + else if ( mpWindowImpl->mbFrame ) + mpWindowImpl->mpFrame->SetMaxClientSize( aSize.Width(), aSize.Height() ); +} + +const Size& SystemWindow::GetMaxOutputSizePixel() const +{ + return mpImplData->maMaxOutSize; +} + +void ImplWindowStateFromStr(WindowStateData& rData, std::string_view rStr) +{ + WindowStateMask nValidMask = WindowStateMask::NONE; + sal_Int32 nIndex = 0; + + std::string_view aTokenStr = o3tl::getToken(rStr, 0, ',', nIndex); + if (!aTokenStr.empty()) + { + rData.SetX(o3tl::toInt32(aTokenStr)); + if( rData.GetX() > -16384 && rData.GetX() < 16384 ) + nValidMask |= WindowStateMask::X; + else + rData.SetX( 0 ); + } + else + rData.SetX( 0 ); + aTokenStr = o3tl::getToken(rStr, 0, ',', nIndex); + if (!aTokenStr.empty()) + { + rData.SetY(o3tl::toInt32(aTokenStr)); + if( rData.GetY() > -16384 && rData.GetY() < 16384 ) + nValidMask |= WindowStateMask::Y; + else + rData.SetY( 0 ); + } + else + rData.SetY( 0 ); + aTokenStr = o3tl::getToken(rStr, 0, ',', nIndex); + if (!aTokenStr.empty()) + { + rData.SetWidth(o3tl::toInt32(aTokenStr)); + if( rData.GetWidth() > 0 && rData.GetWidth() < 16384 ) + nValidMask |= WindowStateMask::Width; + else + rData.SetWidth( 0 ); + } + else + rData.SetWidth( 0 ); + aTokenStr = o3tl::getToken(rStr, 0, ';', nIndex); + if (!aTokenStr.empty()) + { + rData.SetHeight(o3tl::toInt32(aTokenStr)); + if( rData.GetHeight() > 0 && rData.GetHeight() < 16384 ) + nValidMask |= WindowStateMask::Height; + else + rData.SetHeight( 0 ); + } + else + rData.SetHeight( 0 ); + aTokenStr = o3tl::getToken(rStr, 0, ';', nIndex); + if (!aTokenStr.empty()) + { + // #94144# allow Minimize again, should be masked out when read from configuration + // 91625 - ignore Minimize + WindowStateState nState = static_cast<WindowStateState>(o3tl::toInt32(aTokenStr)); + //nState &= ~(WindowStateState::Minimized); + rData.SetState( nState ); + nValidMask |= WindowStateMask::State; + } + else + rData.SetState( WindowStateState::NONE ); + + // read maximized pos/size + aTokenStr = o3tl::getToken(rStr, 0, ',', nIndex); + if (!aTokenStr.empty()) + { + rData.SetMaximizedX(o3tl::toInt32(aTokenStr)); + if( rData.GetMaximizedX() > -16384 && rData.GetMaximizedX() < 16384 ) + nValidMask |= WindowStateMask::MaximizedX; + else + rData.SetMaximizedX( 0 ); + } + else + rData.SetMaximizedX( 0 ); + aTokenStr = o3tl::getToken(rStr, 0, ',', nIndex); + if (!aTokenStr.empty()) + { + rData.SetMaximizedY(o3tl::toInt32(aTokenStr)); + if( rData.GetMaximizedY() > -16384 && rData.GetMaximizedY() < 16384 ) + nValidMask |= WindowStateMask::MaximizedY; + else + rData.SetMaximizedY( 0 ); + } + else + rData.SetMaximizedY( 0 ); + aTokenStr = o3tl::getToken(rStr, 0, ',', nIndex); + if (!aTokenStr.empty()) + { + rData.SetMaximizedWidth(o3tl::toInt32(aTokenStr)); + if( rData.GetMaximizedWidth() > 0 && rData.GetMaximizedWidth() < 16384 ) + nValidMask |= WindowStateMask::MaximizedWidth; + else + rData.SetMaximizedWidth( 0 ); + } + else + rData.SetMaximizedWidth( 0 ); + aTokenStr = o3tl::getToken(rStr, 0, ';', nIndex); + if (!aTokenStr.empty()) + { + rData.SetMaximizedHeight(o3tl::toInt32(aTokenStr)); + if( rData.GetMaximizedHeight() > 0 && rData.GetMaximizedHeight() < 16384 ) + nValidMask |= WindowStateMask::MaximizedHeight; + else + rData.SetMaximizedHeight( 0 ); + } + else + rData.SetMaximizedHeight( 0 ); + + // mark valid fields + rData.SetMask( nValidMask ); +} + +OString WindowStateData::ToStr() const +{ + const WindowStateMask nValidMask = GetMask(); + if ( nValidMask == WindowStateMask::NONE ) + return OString(); + + OStringBuffer rStrBuf(64); + + if ( nValidMask & WindowStateMask::X ) + rStrBuf.append(static_cast<sal_Int32>(GetX())); + rStrBuf.append(','); + if ( nValidMask & WindowStateMask::Y ) + rStrBuf.append(static_cast<sal_Int32>(GetY())); + rStrBuf.append(','); + if ( nValidMask & WindowStateMask::Width ) + rStrBuf.append(static_cast<sal_Int32>(GetWidth())); + rStrBuf.append(','); + if ( nValidMask & WindowStateMask::Height ) + rStrBuf.append(static_cast<sal_Int32>(GetHeight())); + rStrBuf.append( ';' ); + if ( nValidMask & WindowStateMask::State ) + { + // #94144# allow Minimize again, should be masked out when read from configuration + // 91625 - ignore Minimize + WindowStateState nState = GetState(); + rStrBuf.append(static_cast<sal_Int32>(nState)); + } + rStrBuf.append(';'); + if ( nValidMask & WindowStateMask::MaximizedX ) + rStrBuf.append(static_cast<sal_Int32>(GetMaximizedX())); + rStrBuf.append(','); + if ( nValidMask & WindowStateMask::MaximizedY ) + rStrBuf.append(static_cast<sal_Int32>(GetMaximizedY())); + rStrBuf.append( ',' ); + if ( nValidMask & WindowStateMask::MaximizedWidth ) + rStrBuf.append(static_cast<sal_Int32>(GetMaximizedWidth())); + rStrBuf.append(','); + if ( nValidMask & WindowStateMask::MaximizedHeight ) + rStrBuf.append(static_cast<sal_Int32>(GetMaximizedHeight())); + rStrBuf.append(';'); + + return rStrBuf.makeStringAndClear(); +} + +void SystemWindow::ImplMoveToScreen( tools::Long& io_rX, tools::Long& io_rY, tools::Long i_nWidth, tools::Long i_nHeight, vcl::Window const * i_pConfigureWin ) +{ + tools::Rectangle aScreenRect; + if( !Application::IsUnifiedDisplay() ) + aScreenRect = Application::GetScreenPosSizePixel( GetScreenNumber() ); + else + { + aScreenRect = Application::GetScreenPosSizePixel( 0 ); + for( unsigned int i = 1; i < Application::GetScreenCount(); i++ ) + aScreenRect.Union( Application::GetScreenPosSizePixel( i ) ); + } + // unfortunately most of the time width and height are not really known + if( i_nWidth < 1 ) + i_nWidth = 50; + if( i_nHeight < 1 ) + i_nHeight = 50; + + // check left border + bool bMove = false; + if( io_rX + i_nWidth < aScreenRect.Left() ) + { + bMove = true; + io_rX = aScreenRect.Left(); + } + // check right border + if( io_rX > aScreenRect.Right() - i_nWidth ) + { + bMove = true; + io_rX = aScreenRect.Right() - i_nWidth; + } + // check top border + if( io_rY + i_nHeight < aScreenRect.Top() ) + { + bMove = true; + io_rY = aScreenRect.Top(); + } + // check bottom border + if( io_rY > aScreenRect.Bottom() - i_nHeight ) + { + bMove = true; + io_rY = aScreenRect.Bottom() - i_nHeight; + } + vcl::Window* pParent = i_pConfigureWin->GetParent(); + if( bMove && pParent ) + { + // calculate absolute screen pos here, since that is what is contained in WindowState + Point aParentAbsPos( pParent->OutputToAbsoluteScreenPixel( Point(0,0) ) ); + Size aParentSizePixel( pParent->GetOutputSizePixel() ); + Point aPos( (aParentSizePixel.Width() - i_nWidth) / 2, + (aParentSizePixel.Height() - i_nHeight) / 2 ); + io_rX = aParentAbsPos.X() + aPos.X(); + io_rY = aParentAbsPos.Y() + aPos.Y(); + } +} + +void SystemWindow::SetWindowStateData( const WindowStateData& rData ) +{ + const WindowStateMask nValidMask = rData.GetMask(); + if ( nValidMask == WindowStateMask::NONE ) + return; + + if ( mbSysChild ) + return; + + vcl::Window* pWindow = this; + while ( pWindow->mpWindowImpl->mpBorderWindow ) + pWindow = pWindow->mpWindowImpl->mpBorderWindow; + + if ( pWindow->mpWindowImpl->mbFrame ) + { + const WindowStateState nState = rData.GetState(); + SalFrameState aState; + aState.mnMask = rData.GetMask(); + aState.mnX = rData.GetX(); + aState.mnY = rData.GetY(); + aState.mnWidth = rData.GetWidth(); + aState.mnHeight = rData.GetHeight(); + + if( rData.GetMask() & (WindowStateMask::Width|WindowStateMask::Height) ) + { + // #i43799# adjust window state sizes if a minimal output size was set + // otherwise the frame and the client might get different sizes + if( maMinOutSize.Width() > aState.mnWidth ) + aState.mnWidth = maMinOutSize.Width(); + if( maMinOutSize.Height() > aState.mnHeight ) + aState.mnHeight = maMinOutSize.Height(); + } + + aState.mnMaximizedX = rData.GetMaximizedX(); + aState.mnMaximizedY = rData.GetMaximizedY(); + aState.mnMaximizedWidth = rData.GetMaximizedWidth(); + aState.mnMaximizedHeight = rData.GetMaximizedHeight(); + // #94144# allow Minimize again, should be masked out when read from configuration + // 91625 - ignore Minimize + //nState &= ~(WindowStateState::Minimized); + aState.mnState = nState & WindowStateState::SystemMask; + + // normalize window positions onto screen + ImplMoveToScreen( aState.mnX, aState.mnY, aState.mnWidth, aState.mnHeight, pWindow ); + ImplMoveToScreen( aState.mnMaximizedX, aState.mnMaximizedY, aState.mnMaximizedWidth, aState.mnMaximizedHeight, pWindow ); + + // #96568# avoid having multiple frames at the same screen location + // do the check only if not maximized + if( !((rData.GetMask() & WindowStateMask::State) && (nState & WindowStateState::Maximized)) ) + if( rData.GetMask() & (WindowStateMask::Pos|WindowStateMask::Width|WindowStateMask::Height) ) + { + tools::Rectangle aDesktop = GetDesktopRectPixel(); + ImplSVData *pSVData = ImplGetSVData(); + vcl::Window *pWin = pSVData->maFrameData.mpFirstFrame; + bool bWrapped = false; + while( pWin ) + { + if( !pWin->ImplIsRealParentPath( this ) && ( pWin != this ) && + pWin->ImplGetWindow()->IsTopWindow() && pWin->mpWindowImpl->mbReallyVisible ) + { + SalFrameGeometry g = pWin->mpWindowImpl->mpFrame->GetGeometry(); + if( std::abs(g.nX-aState.mnX) < 2 && std::abs(g.nY-aState.mnY) < 5 ) + { + tools::Long displacement = g.nTopDecoration ? g.nTopDecoration : 20; + if( static_cast<tools::Long>(aState.mnX + displacement + aState.mnWidth + g.nRightDecoration) > aDesktop.Right() || + static_cast<tools::Long>(aState.mnY + displacement + aState.mnHeight + g.nBottomDecoration) > aDesktop.Bottom() ) + { + // displacing would leave screen + aState.mnX = g.nLeftDecoration ? g.nLeftDecoration : 10; // should result in (0,0) + aState.mnY = displacement; + if( bWrapped || + static_cast<tools::Long>(aState.mnX + displacement + aState.mnWidth + g.nRightDecoration) > aDesktop.Right() || + static_cast<tools::Long>(aState.mnY + displacement + aState.mnHeight + g.nBottomDecoration) > aDesktop.Bottom() ) + break; // further displacement not possible -> break + // avoid endless testing + bWrapped = true; + } + else + { + // displace + aState.mnX += displacement; + aState.mnY += displacement; + } + pWin = pSVData->maFrameData.mpFirstFrame; // check new pos again + } + } + pWin = pWin->mpWindowImpl->mpFrameData->mpNextFrame; + } + } + + mpWindowImpl->mpFrame->SetWindowState( &aState ); + + // do a synchronous resize for layout reasons + // but use rData only when the window is not to be maximized (#i38089#) + // otherwise we have no useful size information + if( (rData.GetMask() & WindowStateMask::State) && (nState & WindowStateState::Maximized) ) + { + // query maximized size from frame + SalFrameGeometry aGeometry = mpWindowImpl->mpFrame->GetGeometry(); + + // but use it only if it is different from the restore size (rData) + // as currently only on windows the exact size of a maximized window + // can be computed without actually showing the window + if( aGeometry.nWidth != rData.GetWidth() || aGeometry.nHeight != rData.GetHeight() ) + ImplHandleResize( pWindow, aGeometry.nWidth, aGeometry.nHeight ); + } + else + if( rData.GetMask() & (WindowStateMask::Width|WindowStateMask::Height) ) + ImplHandleResize( pWindow, aState.mnWidth, aState.mnHeight ); // #i43799# use aState and not rData, see above + } + else + { + PosSizeFlags nPosSize = PosSizeFlags::NONE; + if ( nValidMask & WindowStateMask::X ) + nPosSize |= PosSizeFlags::X; + if ( nValidMask & WindowStateMask::Y ) + nPosSize |= PosSizeFlags::Y; + if ( nValidMask & WindowStateMask::Width ) + nPosSize |= PosSizeFlags::Width; + if ( nValidMask & WindowStateMask::Height ) + nPosSize |= PosSizeFlags::Height; + + tools::Long nX = rData.GetX(); + tools::Long nY = rData.GetY(); + tools::Long nWidth = rData.GetWidth(); + tools::Long nHeight = rData.GetHeight(); + const SalFrameGeometry& rGeom = pWindow->mpWindowImpl->mpFrame->GetGeometry(); + if( nX < 0 ) + nX = 0; + if( nX + nWidth > static_cast<tools::Long>(rGeom.nWidth) ) + nX = rGeom.nWidth - nWidth; + if( nY < 0 ) + nY = 0; + if( nY + nHeight > static_cast<tools::Long>(rGeom.nHeight) ) + nY = rGeom.nHeight - nHeight; + setPosSizePixel( nX, nY, nWidth, nHeight, nPosSize ); + } + + // tdf#146648 if an explicit size state was set, then use it as the preferred + // size for layout + if (nValidMask & WindowStateMask::Size) + mbInitialLayoutSizeCalculated = true; +} + +void SystemWindow::GetWindowStateData( WindowStateData& rData ) const +{ + WindowStateMask nValidMask = rData.GetMask(); + if ( nValidMask == WindowStateMask::NONE ) + return; + + if ( mbSysChild ) + return; + + const vcl::Window* pWindow = this; + while ( pWindow->mpWindowImpl->mpBorderWindow ) + pWindow = pWindow->mpWindowImpl->mpBorderWindow; + + if ( pWindow->mpWindowImpl->mbFrame ) + { + SalFrameState aState; + aState.mnMask = WindowStateMask::All; + if ( mpWindowImpl->mpFrame->GetWindowState( &aState ) ) + { + if ( nValidMask & WindowStateMask::X ) + rData.SetX( aState.mnX ); + if ( nValidMask & WindowStateMask::Y ) + rData.SetY( aState.mnY ); + if ( nValidMask & WindowStateMask::Width ) + rData.SetWidth( aState.mnWidth ); + if ( nValidMask & WindowStateMask::Height ) + rData.SetHeight( aState.mnHeight ); + if ( aState.mnMask & WindowStateMask::MaximizedX ) + { + rData.SetMaximizedX( aState.mnMaximizedX ); + nValidMask |= WindowStateMask::MaximizedX; + } + if ( aState.mnMask & WindowStateMask::MaximizedY ) + { + rData.SetMaximizedY( aState.mnMaximizedY ); + nValidMask |= WindowStateMask::MaximizedY; + } + if ( aState.mnMask & WindowStateMask::MaximizedWidth ) + { + rData.SetMaximizedWidth( aState.mnMaximizedWidth ); + nValidMask |= WindowStateMask::MaximizedWidth; + } + if ( aState.mnMask & WindowStateMask::MaximizedHeight ) + { + rData.SetMaximizedHeight( aState.mnMaximizedHeight ); + nValidMask |= WindowStateMask::MaximizedHeight; + } + if ( nValidMask & WindowStateMask::State ) + { + // #94144# allow Minimize again, should be masked out when read from configuration + // 91625 - ignore Minimize + if ( !(nValidMask&WindowStateMask::Minimized) ) + aState.mnState &= ~WindowStateState::Minimized; + rData.SetState( aState.mnState ); + } + rData.SetMask( nValidMask ); + } + else + rData.SetMask( WindowStateMask::NONE ); + } + else + { + Point aPos = GetPosPixel(); + Size aSize = GetSizePixel(); + WindowStateState nState = WindowStateState::NONE; + + if ( nValidMask & WindowStateMask::X ) + rData.SetX( aPos.X() ); + if ( nValidMask & WindowStateMask::Y ) + rData.SetY( aPos.Y() ); + if ( nValidMask & WindowStateMask::Width ) + rData.SetWidth( aSize.Width() ); + if ( nValidMask & WindowStateMask::Height ) + rData.SetHeight( aSize.Height() ); + if ( nValidMask & WindowStateMask::State ) + rData.SetState( nState ); + } +} + +void SystemWindow::SetWindowState(std::string_view rStr) +{ + if (rStr.empty()) + return; + + WindowStateData aData; + ImplWindowStateFromStr( aData, rStr ); + SetWindowStateData( aData ); +} + +OString SystemWindow::GetWindowState( WindowStateMask nMask ) const +{ + WindowStateData aData; + aData.SetMask( nMask ); + GetWindowStateData( aData ); + + return aData.ToStr(); +} + +void SystemWindow::SetMenuBar(MenuBar* pMenuBar) +{ + if ( mpMenuBar == pMenuBar ) + return; + + MenuBar* pOldMenuBar = mpMenuBar; + vcl::Window* pOldWindow = nullptr; + VclPtr<vcl::Window> pNewWindow; + mpMenuBar = pMenuBar; + + if ( mpWindowImpl->mpBorderWindow && (mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW) ) + { + if ( pOldMenuBar ) + pOldWindow = pOldMenuBar->ImplGetWindow(); + else + pOldWindow = nullptr; + if ( pOldWindow ) + { + CallEventListeners( VclEventId::WindowMenubarRemoved, static_cast<void*>(pOldMenuBar) ); + pOldWindow->SetAccessible( css::uno::Reference< css::accessibility::XAccessible >() ); + } + if ( pMenuBar ) + { + SAL_WARN_IF( pMenuBar->pWindow, "vcl", "SystemWindow::SetMenuBar() - MenuBars can only set in one SystemWindow at time" ); + + pNewWindow = MenuBar::ImplCreate(mpWindowImpl->mpBorderWindow, pOldWindow, pMenuBar); + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuBarWindow(pNewWindow); + + CallEventListeners( VclEventId::WindowMenubarAdded, static_cast<void*>(pMenuBar) ); + } + else + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuBarWindow( nullptr ); + ImplToBottomChild(); + if ( pOldMenuBar ) + { + bool bDelete = (pMenuBar == nullptr); + if( bDelete && pOldWindow ) + { + if( mpImplData->mpTaskPaneList ) + mpImplData->mpTaskPaneList->RemoveWindow( pOldWindow ); + } + MenuBar::ImplDestroy( pOldMenuBar, bDelete ); + if( bDelete ) + pOldWindow = nullptr; // will be deleted in MenuBar::ImplDestroy, + } + + } + else + { + if( pMenuBar ) + pNewWindow = pMenuBar->ImplGetWindow(); + if( pOldMenuBar ) + pOldWindow = pOldMenuBar->ImplGetWindow(); + } + + // update taskpane list to make menubar accessible + if( mpImplData->mpTaskPaneList ) + { + if( pOldWindow ) + mpImplData->mpTaskPaneList->RemoveWindow( pOldWindow ); + if( pNewWindow ) + mpImplData->mpTaskPaneList->AddWindow( pNewWindow ); + } +} + +void SystemWindow::SetNotebookBar(const OUString& rUIXMLDescription, + const css::uno::Reference<css::frame::XFrame>& rFrame, + const NotebookBarAddonsItem& aNotebookBarAddonsItem, + bool bReloadNotebookbar) +{ + if (rUIXMLDescription != maNotebookBarUIFile || bReloadNotebookbar) + { + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get()) + ->SetNotebookBar(rUIXMLDescription, rFrame, aNotebookBarAddonsItem); + maNotebookBarUIFile = rUIXMLDescription; + if(GetNotebookBar()) + GetNotebookBar()->SetSystemWindow(this); + } +} + +void SystemWindow::CloseNotebookBar() +{ + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->CloseNotebookBar(); + maNotebookBarUIFile.clear(); +} + +VclPtr<NotebookBar> const & SystemWindow::GetNotebookBar() const +{ + return static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->GetNotebookBar(); +} + +void SystemWindow::SetMenuBarMode( MenuBarMode nMode ) +{ + if ( mnMenuBarMode != nMode ) + { + mnMenuBarMode = nMode; + if ( mpWindowImpl->mpBorderWindow && (mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW) ) + { + if ( nMode == MenuBarMode::Hide ) + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuBarMode( true ); + else + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetMenuBarMode( false ); + } + } +} + +bool SystemWindow::ImplIsInTaskPaneList( vcl::Window* pWin ) +{ + if( mpImplData && mpImplData->mpTaskPaneList ) + return mpImplData->mpTaskPaneList->IsInList( pWin ); + return false; +} + +unsigned int SystemWindow::GetScreenNumber() const +{ + return mpWindowImpl->mpFrame->maGeometry.nDisplayScreenNumber; +} + +void SystemWindow::SetScreenNumber(unsigned int nDisplayScreen) +{ + mpWindowImpl->mpFrame->SetScreenNumber( nDisplayScreen ); +} + +void SystemWindow::SetApplicationID(const OUString &rApplicationID) +{ + mpWindowImpl->mpFrame->SetApplicationID( rApplicationID ); +} + +void SystemWindow::SetCloseHdl(const Link<SystemWindow&,void>& rLink) +{ + mpImplData->maCloseHdl = rLink; +} + +const Link<SystemWindow&,void>& SystemWindow::GetCloseHdl() const +{ + return mpImplData->maCloseHdl; +} + +void SystemWindow::queue_resize(StateChangedType /*eReason*/) +{ + if (!isLayoutEnabled()) + return; + if (isCalculatingInitialLayoutSize()) + return; + InvalidateSizeCache(); + if (hasPendingLayout()) + return; + maLayoutIdle.Start(); +} + +void SystemWindow::Resize() +{ + queue_resize(); +} + +bool SystemWindow::isLayoutEnabled() const +{ + //pre dtor called, and single child is a container => we're layout enabled + return mpImplData && ::isLayoutEnabled(this); +} + +Size SystemWindow::GetOptimalSize() const +{ + if (!isLayoutEnabled()) + return Window::GetOptimalSize(); + + Window *pBox = GetWindow(GetWindowType::FirstChild); + // tdf#141318 Do the same as SystemWindow::setOptimalLayoutSize in case we're called before initial layout + const_cast<SystemWindow*>(this)->settingOptimalLayoutSize(pBox); + Size aSize = VclContainer::getLayoutRequisition(*pBox); + + sal_Int32 nBorderWidth = get_border_width(); + + aSize.AdjustHeight(2 * nBorderWidth ); + aSize.AdjustWidth(2 * nBorderWidth ); + + return Window::CalcWindowSize(aSize); +} + +void SystemWindow::setPosSizeOnContainee(Size aSize, Window &rBox) +{ + sal_Int32 nBorderWidth = get_border_width(); + + aSize.AdjustWidth( -(2 * nBorderWidth) ); + aSize.AdjustHeight( -(2 * nBorderWidth) ); + + Point aPos(nBorderWidth, nBorderWidth); + VclContainer::setLayoutAllocation(rBox, aPos, CalcOutputSize(aSize)); +} + +IMPL_LINK_NOARG( SystemWindow, ImplHandleLayoutTimerHdl, Timer*, void ) +{ + Window *pBox = GetWindow(GetWindowType::FirstChild); + if (!isLayoutEnabled()) + { + SAL_WARN_IF(pBox, "vcl.layout", "SystemWindow has become non-layout because extra children have been added directly to it."); + return; + } + assert(pBox); + setPosSizeOnContainee(GetSizePixel(), *pBox); +} + +void SystemWindow::SetText(const OUString& rStr) +{ + setDeferredProperties(); + Window::SetText(rStr); +} + +OUString SystemWindow::GetText() const +{ + const_cast<SystemWindow*>(this)->setDeferredProperties(); + return Window::GetText(); +} + +void SystemWindow::settingOptimalLayoutSize(Window* /*pBox*/) +{ +} + +void SystemWindow::setOptimalLayoutSize(bool bAllowWindowShrink) +{ + maLayoutIdle.Stop(); + + //resize SystemWindow to fit requisition on initial show + Window *pBox = GetWindow(GetWindowType::FirstChild); + + settingOptimalLayoutSize(pBox); + + Size aSize = get_preferred_size(); + + Size aMax(bestmaxFrameSizeForScreenSize(GetDesktopRectPixel().GetSize())); + + aSize.setWidth( std::min(aMax.Width(), aSize.Width()) ); + aSize.setHeight( std::min(aMax.Height(), aSize.Height()) ); + + SetMinOutputSizePixel(aSize); + + if (!bAllowWindowShrink) + { + Size aCurrentSize = GetSizePixel(); + aSize.setWidth(std::max(aSize.Width(), aCurrentSize.Width())); + aSize.setHeight(std::max(aSize.Height(), aCurrentSize.Height())); + } + + SetSizePixel(aSize); + setPosSizeOnContainee(aSize, *pBox); +} + +void SystemWindow::DoInitialLayout() +{ + if (GetSettings().GetStyleSettings().GetAutoMnemonic()) + GenerateAutoMnemonicsOnHierarchy(this); + + if (isLayoutEnabled()) + { + mbIsCalculatingInitialLayoutSize = true; + setDeferredProperties(); + setOptimalLayoutSize(!mbInitialLayoutSizeCalculated); + mbInitialLayoutSizeCalculated = true; + mbIsCalculatingInitialLayoutSize = false; + } +} + +void SystemWindow::doDeferredInit(WinBits /*nBits*/) +{ + SAL_WARN("vcl.layout", "SystemWindow in layout without doDeferredInit impl"); +} + +VclPtr<VirtualDevice> SystemWindow::createScreenshot() +{ + // same prerequisites as in Execute() + setDeferredProperties(); + ImplAdjustNWFSizes(); + Show(); + ToTop(); + ensureRepaint(); + + Size aSize(GetOutputSizePixel()); + + VclPtr<VirtualDevice> xOutput(VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT)); + xOutput->SetOutputSizePixel(aSize); + + Point aPos; + xOutput->DrawOutDev(aPos, aSize, aPos, aSize, *GetOutDev()); + + return xOutput; +} + +void SystemWindow::PrePaint(vcl::RenderContext& rRenderContext) +{ + Window::PrePaint(rRenderContext); + mbPaintComplete = false; +} + +void SystemWindow::PostPaint(vcl::RenderContext& rRenderContext) +{ + Window::PostPaint(rRenderContext); + mbPaintComplete = true; +} + +void SystemWindow::ensureRepaint() +{ + // ensure repaint + Invalidate(); + mbPaintComplete = false; + + while (!mbPaintComplete && !Application::IsQuit()) + { + Application::Yield(); + } +} + +void SystemWindow::CollectMenuBarMnemonics(MnemonicGenerator& rMnemonicGenerator) const +{ + if (MenuBar* pMenu = GetMenuBar()) + { + sal_uInt16 nMenuItems = pMenu->GetItemCount(); + for ( sal_uInt16 i = 0; i < nMenuItems; ++i ) + rMnemonicGenerator.RegisterMnemonic( pMenu->GetItemText( pMenu->GetItemId( i ) ) ); + } +} + +int SystemWindow::GetMenuBarHeight() const +{ + if (MenuBar* pMenuBar = GetMenuBar()) + return pMenuBar->GetMenuBarHeight(); + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/tabdlg.cxx b/vcl/source/window/tabdlg.cxx new file mode 100644 index 000000000..f132300a0 --- /dev/null +++ b/vcl/source/window/tabdlg.cxx @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/toolkit/fixed.hxx> +#include <vcl/layout.hxx> +#include <vcl/tabctrl.hxx> +#include <vcl/toolkit/tabdlg.hxx> +#include <vcl/tabpage.hxx> + +void TabDialog::ImplInitTabDialogData() +{ + mpFixedLine = nullptr; + mbPosControls = true; +} + +void TabDialog::ImplPosControls() +{ + if (isLayoutEnabled()) + return; + + Size aCtrlSize( IMPL_MINSIZE_BUTTON_WIDTH, IMPL_MINSIZE_BUTTON_HEIGHT ); + tools::Long nDownCtrl = 0; + tools::Long nOffY = 0; + vcl::Window* pTabControl = nullptr; + + vcl::Window* pChild = GetWindow( GetWindowType::FirstChild ); + while ( pChild ) + { + if ( pChild->IsVisible() ) + { + if (pChild->GetType() == WindowType::TABCONTROL || isContainerWindow(*pChild)) + pTabControl = pChild; + else if ( pTabControl ) + { + Size aOptimalSize(pChild->get_preferred_size()); + tools::Long nTxtWidth = aOptimalSize.Width(); + if ( nTxtWidth > aCtrlSize.Width() ) + aCtrlSize.setWidth( nTxtWidth ); + tools::Long nTxtHeight = aOptimalSize.Height(); + if ( nTxtHeight > aCtrlSize.Height() ) + aCtrlSize.setHeight( nTxtHeight ); + nDownCtrl++; + } + else + { + tools::Long nHeight = pChild->GetSizePixel().Height(); + if ( nHeight > nOffY ) + nOffY = nHeight; + } + } + + pChild = pChild->GetWindow( GetWindowType::Next ); + } + + // do we have a TabControl ? + if ( pTabControl ) + { + // adapt offset for other controls by an extra distance + if ( nOffY ) + nOffY += IMPL_DIALOG_BAR_OFFSET*2 + 2; + + Point aTabOffset( IMPL_DIALOG_OFFSET, IMPL_DIALOG_OFFSET+nOffY ); + + if (isContainerWindow(*pTabControl)) + pTabControl->SetSizePixel(pTabControl->get_preferred_size()); + + Size aTabSize = pTabControl->GetSizePixel(); + + Size aDlgSize( aTabSize.Width() + IMPL_DIALOG_OFFSET*2, + aTabSize.Height() + IMPL_DIALOG_OFFSET*2 + nOffY ); + + // adapt positioning + pTabControl->SetPosPixel( aTabOffset ); + + // position all other Children + bool bTabCtrl = false; + int nLines = 0; + tools::Long nX; + tools::Long nY = aDlgSize.Height(); + tools::Long nTopX = IMPL_DIALOG_OFFSET; + + // all buttons are right aligned under Windows 95 + nX = IMPL_DIALOG_OFFSET; + tools::Long nCtrlBarWidth = ((aCtrlSize.Width()+IMPL_DIALOG_OFFSET)*nDownCtrl)-IMPL_DIALOG_OFFSET; + if ( nCtrlBarWidth <= aTabSize.Width() ) + nX = aTabSize.Width() - nCtrlBarWidth + IMPL_DIALOG_OFFSET; + + vcl::Window* pChild2 = GetWindow( GetWindowType::FirstChild ); + while ( pChild2 ) + { + if ( pChild2->IsVisible() ) + { + if ( pChild2 == pTabControl ) + bTabCtrl = true; + else if ( bTabCtrl ) + { + if ( !nLines ) + nLines = 1; + + if ( nX+aCtrlSize.Width()-IMPL_DIALOG_OFFSET > aTabSize.Width() ) + { + nY += aCtrlSize.Height()+IMPL_DIALOG_OFFSET; + nX = IMPL_DIALOG_OFFSET; + nLines++; + } + + pChild2->SetPosSizePixel( Point( nX, nY ), aCtrlSize ); + nX += aCtrlSize.Width()+IMPL_DIALOG_OFFSET; + } + else + { + Size aChildSize = pChild2->GetSizePixel(); + pChild2->SetPosPixel( Point( nTopX, (nOffY-aChildSize.Height())/2 ) ); + nTopX += aChildSize.Width()+2; + } + } + + pChild2 = pChild2->GetWindow( GetWindowType::Next ); + } + + aDlgSize.AdjustHeight(nLines * (aCtrlSize.Height()+IMPL_DIALOG_OFFSET) ); + SetOutputSizePixel( aDlgSize ); + } + + // store offset + if ( nOffY ) + { + Size aDlgSize = GetOutputSizePixel(); + if ( !mpFixedLine ) + mpFixedLine = VclPtr<FixedLine>::Create( this ); + mpFixedLine->SetPosSizePixel( Point( 0, nOffY ), + Size( aDlgSize.Width(), 2 ) ); + mpFixedLine->Show(); + } + + mbPosControls = false; +} + +TabDialog::TabDialog( vcl::Window* pParent, WinBits nStyle ) : + Dialog( WindowType::TABDIALOG ) +{ + ImplInitTabDialogData(); + ImplInitDialog( pParent, nStyle ); +} + +TabDialog::~TabDialog() +{ + disposeOnce(); +} + +void TabDialog::dispose() +{ + mpFixedLine.disposeAndClear(); + Dialog::dispose(); +} + +void TabDialog::StateChanged( StateChangedType nType ) +{ + if ( nType == StateChangedType::InitShow ) + { + // Calculate the Layout only for the initialized state + if ( mbPosControls ) + ImplPosControls(); + } + Dialog::StateChanged( nType ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/tabpage.cxx b/vcl/source/window/tabpage.cxx new file mode 100644 index 000000000..34f52d8b3 --- /dev/null +++ b/vcl/source/window/tabpage.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 <vcl/event.hxx> +#include <vcl/layout.hxx> +#include <vcl/tabpage.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/settings.hxx> +#include <vcl/scrbar.hxx> +#include <svdata.hxx> + +void TabPage::ImplInit( vcl::Window* pParent, WinBits nStyle ) +{ + if ( !(nStyle & WB_NODIALOGCONTROL) ) + nStyle |= WB_DIALOGCONTROL; + + Window::ImplInit( pParent, nStyle, nullptr ); + + mbHasHoriBar = false; + mbHasVertBar = false; + + Link<ScrollBar*,void> aLink( LINK( this, TabPage, ScrollBarHdl ) ); + + if ( nStyle & ( WB_AUTOHSCROLL | WB_AUTOVSCROLL ) ) + { + if ( nStyle & WB_AUTOHSCROLL ) + { + mbHasHoriBar = true; + m_pHScroll.set(VclPtr<ScrollBar>::Create(this, (WB_HSCROLL | WB_DRAG))); + m_pHScroll->Show(); + m_pHScroll->SetScrollHdl(aLink); + } + if ( nStyle & WB_AUTOVSCROLL ) + { + mbHasVertBar = true; + m_pVScroll.set(VclPtr<ScrollBar>::Create(this, (WB_VSCROLL | WB_DRAG))); + m_pVScroll->Show(); + m_pVScroll->SetScrollHdl(aLink); + } + } + + if ( mbHasHoriBar || mbHasVertBar ) + { + SetStyle( GetStyle() | WB_CLIPCHILDREN ); + } + + mnScrWidth = Application::GetSettings().GetStyleSettings().GetScrollBarSize(); + + ImplInitSettings(); + + // if the tabpage is drawn (ie filled) by a native widget, make sure all controls will have transparent background + // otherwise they will paint with a wrong background + if( IsNativeControlSupported(ControlType::TabBody, ControlPart::Entire) && GetParent() && (GetParent()->GetType() == WindowType::TABCONTROL) ) + EnableChildTransparentMode(); +} + +void TabPage::ImplInitSettings() +{ + vcl::Window* pParent = GetParent(); + if (pParent && pParent->IsChildTransparentModeEnabled() && !IsControlBackground()) + { + EnableChildTransparentMode(); + SetParentClipMode( ParentClipMode::NoClip ); + SetPaintTransparent( true ); + SetBackground(); + } + else + { + EnableChildTransparentMode( false ); + SetParentClipMode(); + SetPaintTransparent( false ); + + if (IsControlBackground() || !pParent) + SetBackground( GetControlBackground() ); + else + SetBackground( pParent->GetBackground() ); + } +} + +TabPage::TabPage( vcl::Window* pParent, WinBits nStyle ) : + Window( WindowType::TABPAGE ) +{ + ImplInit( pParent, nStyle ); +} + +TabPage::~TabPage() +{ + disposeOnce(); +} + +void TabPage::dispose() +{ + m_pVScroll.disposeAndClear(); + m_pHScroll.disposeAndClear(); + vcl::Window::dispose(); +} + +void TabPage::StateChanged( StateChangedType nType ) +{ + Window::StateChanged( nType ); + + if ( nType == StateChangedType::InitShow ) + { + if (GetSettings().GetStyleSettings().GetAutoMnemonic()) + GenerateAutoMnemonicsOnHierarchy(this); + // FIXME: no layouting, workaround some clipping issues + ImplAdjustNWFSizes(); + } + else if ( nType == StateChangedType::ControlBackground ) + { + ImplInitSettings(); + Invalidate(); + } +} + +void TabPage::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + ImplInitSettings(); + Invalidate(); + } +} + +void TabPage::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& ) +{ + // draw native tabpage only inside tabcontrols, standalone tabpages look ugly (due to bad dialog design) + if( !(IsNativeControlSupported(ControlType::TabBody, ControlPart::Entire) && GetParent() && (GetParent()->GetType() == WindowType::TABCONTROL)) ) + return; + + const ImplControlValue aControlValue; + + ControlState nState = ControlState::ENABLED; + if ( !IsEnabled() ) + nState &= ~ControlState::ENABLED; + if ( HasFocus() ) + nState |= ControlState::FOCUSED; + // pass the whole window region to NWF as the tab body might be a gradient or bitmap + // that has to be scaled properly, clipping makes sure that we do not paint too much + tools::Rectangle aCtrlRegion( Point(), GetOutputSizePixel() ); + rRenderContext.DrawNativeControl( ControlType::TabBody, ControlPart::Entire, aCtrlRegion, nState, + aControlValue, OUString() ); +} + +void TabPage::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags ) +{ + Point aPos = pDev->LogicToPixel( rPos ); + Size aSize = GetSizePixel(); + + Wallpaper aWallpaper = GetBackground(); + if ( !aWallpaper.IsBitmap() ) + ImplInitSettings(); + + pDev->Push(); + pDev->SetMapMode(); + pDev->SetLineColor(); + + if ( aWallpaper.IsBitmap() ) + pDev->DrawBitmapEx( aPos, aSize, aWallpaper.GetBitmap() ); + else + { + if( aWallpaper.GetColor() == COL_AUTO ) + pDev->SetFillColor( GetSettings().GetStyleSettings().GetDialogColor() ); + else + pDev->SetFillColor( aWallpaper.GetColor() ); + pDev->DrawRect( tools::Rectangle( aPos, aSize ) ); + } + + pDev->Pop(); +} + +Size TabPage::GetOptimalSize() const +{ + if (isLayoutEnabled(this)) + return VclContainer::getLayoutRequisition(*GetWindow(GetWindowType::FirstChild)); + return getLegacyBestSizeForChildren(*this); +} + +void TabPage::SetPosSizePixel(const Point& rAllocPos, const Size& rAllocation) +{ + Window::SetPosSizePixel(rAllocPos, rAllocation); + if (isLayoutEnabled(this) && rAllocation.Width() && rAllocation.Height()) + VclContainer::setLayoutAllocation(*GetWindow(GetWindowType::FirstChild), Point(0, 0), rAllocation); +} + +void TabPage::SetSizePixel(const Size& rAllocation) +{ + Window::SetSizePixel(rAllocation); + if (isLayoutEnabled(this) && rAllocation.Width() && rAllocation.Height()) + VclContainer::setLayoutAllocation(*GetWindow(GetWindowType::FirstChild), Point(0, 0), rAllocation); +} + +void TabPage::SetPosPixel(const Point& rAllocPos) +{ + Window::SetPosPixel(rAllocPos); + Size aAllocation(GetOutputSizePixel()); + if (isLayoutEnabled(this) && aAllocation.Width() && aAllocation.Height()) + { + VclContainer::setLayoutAllocation(*GetWindow(GetWindowType::FirstChild), Point(0, 0), aAllocation); + } +} + +void TabPage::lcl_Scroll( tools::Long nX, tools::Long nY ) +{ + tools::Long nXScroll = mnScrollPos.X() - nX; + tools::Long nYScroll = mnScrollPos.Y() - nY; + mnScrollPos = Point( nX, nY ); + + tools::Rectangle aScrollableArea( 0, 0, maScrollArea.Width(), maScrollArea.Height() ); + Scroll(nXScroll, nYScroll, aScrollableArea ); + // Manually scroll all children ( except the scrollbars ) + for ( int index = 0; index < GetChildCount(); ++index ) + { + vcl::Window* pChild = GetChild( index ); + if ( pChild && pChild != m_pVScroll.get() && pChild != m_pHScroll.get() ) + { + Point aPos = pChild->GetPosPixel(); + aPos += Point( nXScroll, nYScroll ); + pChild->SetPosPixel( aPos ); + } + } +} + +IMPL_LINK( TabPage, ScrollBarHdl, ScrollBar*, pSB, void ) +{ + sal_uInt16 nPos = static_cast<sal_uInt16>(pSB->GetThumbPos()); + if( pSB == m_pVScroll.get() ) + lcl_Scroll(mnScrollPos.X(), nPos ); + else if( pSB == m_pHScroll.get() ) + lcl_Scroll(nPos, mnScrollPos.Y() ); +} + +void TabPage::SetScrollTop( tools::Long nTop ) +{ + Point aOld = mnScrollPos; + lcl_Scroll( mnScrollPos.X() , mnScrollPos.Y() - nTop ); + if( m_pHScroll ) + m_pHScroll->SetThumbPos( 0 ); + // new pos is 0,0 + mnScrollPos = aOld; +} +void TabPage::SetScrollLeft( tools::Long nLeft ) +{ + Point aOld = mnScrollPos; + lcl_Scroll( mnScrollPos.X() - nLeft , mnScrollPos.Y() ); + if( m_pVScroll ) + m_pVScroll->SetThumbPos( 0 ); + // new pos is 0,0 + mnScrollPos = aOld; +} + +void TabPage::SetScrollWidth( tools::Long nWidth ) +{ + maScrollArea.setWidth( nWidth ); + ResetScrollBars(); +} + +void TabPage::SetScrollHeight( tools::Long nHeight ) +{ + maScrollArea.setHeight( nHeight ); + ResetScrollBars(); +} + +void TabPage::Resize() +{ + ResetScrollBars(); +} + +void TabPage::ResetScrollBars() +{ + Size aOutSz = GetOutputSizePixel(); + + Point aVPos( aOutSz.Width() - mnScrWidth, 0 ); + Point aHPos( 0, aOutSz.Height() - mnScrWidth ); + + if( m_pVScroll ) + { + m_pVScroll->SetPosSizePixel( aVPos, Size( mnScrWidth, GetSizePixel().Height() - mnScrWidth ) ); + m_pVScroll->SetRangeMax( maScrollArea.Height() + mnScrWidth ); + m_pVScroll->SetVisibleSize( GetSizePixel().Height() ); + } + + if( m_pHScroll ) + { + m_pHScroll->SetPosSizePixel( aHPos, Size( GetSizePixel().Width() - mnScrWidth, mnScrWidth ) ); + m_pHScroll->SetRangeMax( maScrollArea.Width() + mnScrWidth ); + m_pHScroll->SetVisibleSize( GetSizePixel().Width() ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/taskpanelist.cxx b/vcl/source/window/taskpanelist.cxx new file mode 100644 index 000000000..833c82a0a --- /dev/null +++ b/vcl/source/window/taskpanelist.cxx @@ -0,0 +1,287 @@ +/* -*- 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/dockwin.hxx> +#include <vcl/toolkit/floatwin.hxx> +#include <vcl/taskpanelist.hxx> + +#include <svdata.hxx> +#include "menubarwindow.hxx" + +#include <algorithm> + +namespace { + +Point ImplTaskPaneListGetPos( const vcl::Window *w ) +{ + Point pos; + if( w->IsDockingWindow() ) + { + pos = static_cast<const DockingWindow*>(w)->GetPosPixel(); + vcl::Window *pF = static_cast<const DockingWindow*>(w)->GetFloatingWindow(); + if( pF ) + pos = pF->OutputToAbsoluteScreenPixel( pF->ScreenToOutputPixel( pos ) ); + else + pos = w->OutputToAbsoluteScreenPixel( pos ); + } + else + pos = w->OutputToAbsoluteScreenPixel( w->GetPosPixel() ); + + return pos; +} + +// compares window pos left-to-right +struct LTRSort +{ + bool operator()( const vcl::Window* w1, const vcl::Window* w2 ) const + { + Point pos1(ImplTaskPaneListGetPos( w1 )); + Point pos2(ImplTaskPaneListGetPos( w2 )); + + if( pos1.X() == pos2.X() ) + return ( pos1.Y() < pos2.Y() ); + else + return ( pos1.X() < pos2.X() ); + } +}; + +} + +static void ImplTaskPaneListGrabFocus( vcl::Window *pWindow, bool bForward ) +{ + // put focus in child of floating windows which is typically a toolbar + // that can deal with the focus + if( pWindow->ImplIsFloatingWindow() && pWindow->GetWindow( GetWindowType::FirstChild ) ) + pWindow = pWindow->GetWindow( GetWindowType::FirstChild ); + pWindow->ImplGrabFocus( GetFocusFlags::F6 | (bForward ? GetFocusFlags::Forward : GetFocusFlags::Backward)); +} + +TaskPaneList::TaskPaneList() +{ +} + +TaskPaneList::~TaskPaneList() +{ +} + +void TaskPaneList::AddWindow( vcl::Window *pWindow ) +{ + if( !pWindow ) + return; + + auto insertionPos = dynamic_cast<MenuBarWindow*>(pWindow) ? mTaskPanes.begin() : mTaskPanes.end(); + for ( auto p = mTaskPanes.begin(); p != mTaskPanes.end(); ++p ) + { + if ( *p == pWindow ) + // avoid duplicates + return; + + // If the new window is the child of an existing pane window, or vice versa, + // ensure that in our pane list, *first* the child window appears, *then* + // the ancestor window. + // This is necessary for HandleKeyEvent: There, the list is traveled from the + // beginning, until the first window is found which has the ChildPathFocus. Now + // if this would be the ancestor window of another pane window, this would fudge + // the result + if ( pWindow->IsWindowOrChild( *p ) ) + { + insertionPos = p + 1; + break; + } + if ( (*p)->IsWindowOrChild( pWindow ) ) + { + insertionPos = p; + break; + } + } + + mTaskPanes.insert( insertionPos, pWindow ); + pWindow->ImplIsInTaskPaneList( true ); +} + +void TaskPaneList::RemoveWindow( vcl::Window *pWindow ) +{ + auto p = ::std::find( mTaskPanes.begin(), mTaskPanes.end(), VclPtr<vcl::Window>(pWindow) ); + if( p != mTaskPanes.end() ) + { + mTaskPanes.erase( p ); + pWindow->ImplIsInTaskPaneList( false ); + } +} + +bool TaskPaneList::IsInList( vcl::Window *pWindow ) +{ + auto p = ::std::find( mTaskPanes.begin(), mTaskPanes.end(), VclPtr<vcl::Window>(pWindow) ); + return p != mTaskPanes.end(); +} + +bool TaskPaneList::IsCycleKey(const vcl::KeyCode& rKeyCode) +{ + return rKeyCode.GetCode() == KEY_F6 && !rKeyCode.IsMod2(); // F6 +} + +bool TaskPaneList::HandleKeyEvent(const KeyEvent& rKeyEvent) +{ + + // F6 cycles through everything and works always + + // MAV, #i104204# + // The old design was the following one: + // < Ctrl-TAB cycles through Menubar, Toolbars and Floatingwindows only and is + // < only active if one of those items has the focus + + // Since the design of Ctrl-Tab looks to be inconsistent ( non-modal dialogs are not reachable + // and the shortcut conflicts with tab-control shortcut ), it is no more supported + vcl::KeyCode aKeyCode = rKeyEvent.GetKeyCode(); + bool bForward = !aKeyCode.IsShift(); + if (TaskPaneList::IsCycleKey(aKeyCode)) + { + bool bSplitterOnly = aKeyCode.IsMod1() && aKeyCode.IsShift(); + + // is the focus in the list ? + auto p = std::find_if(mTaskPanes.begin(), mTaskPanes.end(), + [](const VclPtr<vcl::Window>& rWinPtr) { return rWinPtr->HasChildPathFocus( true ); }); + if( p != mTaskPanes.end() ) + { + vcl::Window *pWin = p->get(); + + // Ctrl-F6 goes directly to the document + if( !pWin->IsDialog() && aKeyCode.IsMod1() && !aKeyCode.IsShift() ) + { + pWin->ImplGrabFocusToDocument( GetFocusFlags::F6 ); + return true; + } + + // activate next task pane + vcl::Window *pNextWin = nullptr; + + if( bSplitterOnly ) + pNextWin = FindNextSplitter( *p ); + else + pNextWin = FindNextFloat( *p, bForward ); + + if( pNextWin != pWin ) + { + ImplGetSVData()->mpWinData->mbNoSaveFocus = true; + ImplTaskPaneListGrabFocus( pNextWin, bForward ); + ImplGetSVData()->mpWinData->mbNoSaveFocus = false; + } + else + { + // forward key if no splitter found + if( bSplitterOnly ) + return false; + + // we did not find another taskpane, so + // put focus back into document + pWin->ImplGrabFocusToDocument( GetFocusFlags::F6 | (bForward ? GetFocusFlags::Forward : GetFocusFlags::Backward)); + } + + return true; + } + + // the focus is not in the list: activate first float if F6 was pressed + vcl::Window *pWin; + if( bSplitterOnly ) + pWin = FindNextSplitter( nullptr ); + else + pWin = FindNextFloat( nullptr, bForward ); + if( pWin ) + { + ImplTaskPaneListGrabFocus( pWin, bForward ); + return true; + } + } + + return false; +} + +// returns next splitter +vcl::Window* TaskPaneList::FindNextSplitter( vcl::Window *pWindow ) +{ + ::std::stable_sort( mTaskPanes.begin(), mTaskPanes.end(), LTRSort() ); + + auto p = mTaskPanes.begin(); + if( pWindow ) + p = std::find(mTaskPanes.begin(), mTaskPanes.end(), pWindow); + + if( p != mTaskPanes.end() ) + { + unsigned n = mTaskPanes.size(); + while( --n ) + { + if( pWindow ) // increment before test + ++p; + if( p == mTaskPanes.end() ) + p = mTaskPanes.begin(); + if( (*p)->ImplIsSplitter() && (*p)->IsReallyVisible() && !(*p)->IsDialog() && (*p)->GetParent()->HasChildPathFocus() ) + { + pWindow = (*p).get(); + break; + } + if( !pWindow ) // increment after test, otherwise first element is skipped + ++p; + } + } + + return pWindow; +} + +// returns first valid item (regardless of type) if pWindow==0, otherwise returns next valid float +vcl::Window* TaskPaneList::FindNextFloat( vcl::Window *pWindow, bool bForward ) +{ + ::std::stable_sort( mTaskPanes.begin(), mTaskPanes.end(), LTRSort() ); + + if ( !bForward ) + ::std::reverse( mTaskPanes.begin(), mTaskPanes.end() ); + + auto p = mTaskPanes.begin(); + if( pWindow ) + p = std::find(mTaskPanes.begin(), mTaskPanes.end(), pWindow); + + while( p != mTaskPanes.end() ) + { + if( pWindow ) // increment before test + ++p; + if( p == mTaskPanes.end() ) + break; // do not wrap, send focus back to document at end of list + /* #i83908# do not use the menubar if it is native and invisible + */ + + bool bSkip = false; // used to skip infobar when it has no children + if( (*p)->GetType() == WindowType::WINDOW && (*p)->GetChildCount() == 0 ) + bSkip = true; + + if( !bSkip && (*p)->IsReallyVisible() && !(*p)->ImplIsSplitter() && + ( (*p)->GetType() != WindowType::MENUBARWINDOW || static_cast<MenuBarWindow*>(p->get())->CanGetFocus() ) ) + { + pWindow = (*p).get(); + break; + } + if( !pWindow ) // increment after test, otherwise first element is skipped + ++p; + } + + if ( !bForward ) + ::std::reverse( mTaskPanes.begin(), mTaskPanes.end() ); + + return pWindow; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/toolbox.cxx b/vcl/source/window/toolbox.cxx new file mode 100644 index 000000000..64879c682 --- /dev/null +++ b/vcl/source/window/toolbox.cxx @@ -0,0 +1,4846 @@ +/* -*- 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/toolbox.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/event.hxx> +#include <vcl/decoview.hxx> +#include <vcl/toolkit/floatwin.hxx> +#include <vcl/svapp.hxx> +#include <vcl/help.hxx> +#include <vcl/mnemonic.hxx> +#include <vcl/gradient.hxx> +#include <vcl/layout.hxx> +#include <vcl/menu.hxx> +#include <vcl/settings.hxx> +#include <vclstatuslistener.hxx> +#include <vcl/ptrstyle.hxx> +#include <bitmaps.hlst> +#include <toolbarvalue.hxx> + +#include <tools/poly.hxx> +#include <svl/imageitm.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> +#include <osl/diagnose.h> + +#include <accel.hxx> +#include <svdata.hxx> +#include <window.h> +#include <toolbox.h> +#include <spin.hxx> +#if defined(_WIN32) +#include <svsys.h> +#endif + +#include <cstdlib> +#include <map> +#include <string_view> +#include <vector> +#include <math.h> + +#include "impldockingwrapper.hxx" + +#define SMALLBUTTON_HSIZE 7 +#define SMALLBUTTON_VSIZE 7 + +#define SMALLBUTTON_OFF_NORMAL_X 3 +#define SMALLBUTTON_OFF_NORMAL_Y 3 + +#define TB_TEXTOFFSET 2 +#define TB_IMAGETEXTOFFSET 3 +#define TB_LINESPACING 3 +#define TB_SPIN_SIZE 14 +#define TB_SPIN_OFFSET 2 +#define TB_BORDER_OFFSET1 4 +#define TB_BORDER_OFFSET2 2 +#define TB_MAXLINES 5 +#define TB_MAXNOSCROLL 32765 + +#define TB_DRAGWIDTH 8 // the default width of the drag grip + +#define TB_CALCMODE_HORZ 1 +#define TB_CALCMODE_VERT 2 +#define TB_CALCMODE_FLOAT 3 + +#define TB_WBLINESIZING (WB_SIZEABLE | WB_DOCKABLE | WB_SCROLL) + +#define DOCK_LINEHSIZE (sal_uInt16(0x0001)) +#define DOCK_LINEVSIZE (sal_uInt16(0x0002)) +#define DOCK_LINERIGHT (sal_uInt16(0x1000)) +#define DOCK_LINEBOTTOM (sal_uInt16(0x2000)) +#define DOCK_LINELEFT (sal_uInt16(0x4000)) +#define DOCK_LINETOP (sal_uInt16(0x8000)) +#define DOCK_LINEOFFSET 3 + +class ImplTBDragMgr +{ +private: + VclPtr<ToolBox> mpDragBox; + Point maMouseOff; + tools::Rectangle maRect; + tools::Rectangle maStartRect; + Accelerator maAccel; + sal_uInt16 mnLineMode; + ToolBox::ImplToolItems::size_type mnStartLines; + + ImplTBDragMgr(const ImplTBDragMgr&) = delete; + ImplTBDragMgr& operator=(const ImplTBDragMgr&) = delete; + +public: + ImplTBDragMgr(); + + void StartDragging( ToolBox* pDragBox, const Point& rPos, const tools::Rectangle& rRect, sal_uInt16 nLineMode ); + void Dragging( const Point& rPos ); + void EndDragging( bool bOK = true ); + DECL_LINK( SelectHdl, Accelerator&, void ); +}; + + +static ImplTBDragMgr* ImplGetTBDragMgr() +{ + ImplSVData* pSVData = ImplGetSVData(); + if ( !pSVData->maCtrlData.mpTBDragMgr ) + pSVData->maCtrlData.mpTBDragMgr = new ImplTBDragMgr; + return pSVData->maCtrlData.mpTBDragMgr; +} + +int ToolBox::ImplGetDragWidth( const vcl::Window& rWindow, bool bHorz ) +{ + return ImplGetDragWidth(*rWindow.GetOutDev(), bHorz); +} +int ToolBox::ImplGetDragWidth( const vcl::RenderContext& rRenderContext, bool bHorz ) +{ + int nWidth = TB_DRAGWIDTH; + if( rRenderContext.IsNativeControlSupported( ControlType::Toolbar, ControlPart::Entire ) ) + { + + ImplControlValue aControlValue; + tools::Rectangle aContent, aBound; + tools::Rectangle aArea( Point(), rRenderContext.GetOutputSizePixel() ); + + if ( rRenderContext.GetNativeControlRegion(ControlType::Toolbar, + bHorz ? ControlPart::ThumbVert : ControlPart::ThumbHorz, + aArea, ControlState::NONE, aControlValue, aBound, aContent) ) + { + nWidth = bHorz ? aContent.GetWidth() : aContent.GetHeight(); + } + } + + // increase the hit area of the drag handle according to DPI scale factor + nWidth *= rRenderContext.GetDPIScaleFactor(); + + return nWidth; +} + +int ToolBox::ImplGetDragWidth() const +{ + return ToolBox::ImplGetDragWidth( *this, mbHorz ); +} + +static ButtonType determineButtonType( ImplToolItem const * pItem, ButtonType defaultType ) +{ + ButtonType tmpButtonType = defaultType; + ToolBoxItemBits nBits = pItem->mnBits & ( ToolBoxItemBits::TEXT_ONLY | ToolBoxItemBits::ICON_ONLY ); + if ( nBits != ToolBoxItemBits::NONE ) // item has custom setting + { + tmpButtonType = ButtonType::SYMBOLTEXT; + if ( nBits == ToolBoxItemBits::TEXT_ONLY ) + tmpButtonType = ButtonType::TEXT; + else if ( nBits == ToolBoxItemBits::ICON_ONLY ) + tmpButtonType = ButtonType::SYMBOLONLY; + } + return tmpButtonType; +} + +void ToolBox::ImplUpdateDragArea() const +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper ) + { + if ( ImplIsFloatingMode() || pWrapper->IsLocked() ) + pWrapper->SetDragArea( tools::Rectangle() ); + else + { + if( meAlign == WindowAlign::Top || meAlign == WindowAlign::Bottom ) + pWrapper->SetDragArea( tools::Rectangle( 0, 0, ImplGetDragWidth(), GetOutputSizePixel().Height() ) ); + else + pWrapper->SetDragArea( tools::Rectangle( 0, 0, GetOutputSizePixel().Width(), ImplGetDragWidth() ) ); + } + } +} + +void ToolBox::ImplCalcBorder( WindowAlign eAlign, tools::Long& rLeft, tools::Long& rTop, + tools::Long& rRight, tools::Long& rBottom ) const +{ + if( ImplIsFloatingMode() || !(mnWinStyle & WB_BORDER) ) + { + // no border in floating mode + rLeft = rTop = rRight = rBottom = 0; + return; + } + + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + + // reserve DragArea only for dockable toolbars + int dragwidth = ( pWrapper && !pWrapper->IsLocked() ) ? ImplGetDragWidth() : 0; + + // no shadow border for dockable toolbars and toolbars with WB_NOSHADOW bit set, e.g. Calc's formulabar + int borderwidth = ( pWrapper || mnWinStyle & WB_NOSHADOW ) ? 0 : 2; + + if ( eAlign == WindowAlign::Top ) + { + rLeft = borderwidth+dragwidth; + rTop = borderwidth; + rRight = borderwidth; + rBottom = 0; + } + else if ( eAlign == WindowAlign::Left ) + { + rLeft = borderwidth; + rTop = borderwidth+dragwidth; + rRight = 0; + rBottom = borderwidth; + } + else if ( eAlign == WindowAlign::Bottom ) + { + rLeft = borderwidth+dragwidth; + rTop = 0; + rRight = borderwidth; + rBottom = borderwidth; + } + else + { + rLeft = 0; + rTop = borderwidth+dragwidth; + rRight = borderwidth; + rBottom = borderwidth; + } +} + +void ToolBox::ImplCheckUpdate() +{ + // remove any pending invalidates to avoid + // have them triggered when paint is locked (see mpData->mbIsPaintLocked) + // which would result in erasing the background only and not painting any items + // this must not be done when we're already in Paint() + + // this is only required for transparent toolbars (see ImplDrawTransparentBackground() ) + if( !IsBackground() && HasPaintEvent() && !IsInPaint() ) + PaintImmediately(); +} + +void ToolBox::ImplDrawGrip(vcl::RenderContext& rRenderContext, + const tools::Rectangle &aDragArea, int nDragWidth, WindowAlign eAlign, bool bHorz) +{ + bool bNativeOk = false; + const ControlPart ePart = bHorz ? ControlPart::ThumbVert : ControlPart::ThumbHorz; + const Size aSz( rRenderContext.GetOutputSizePixel() ); + if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ePart)) + { + ToolbarValue aToolbarValue; + aToolbarValue.maGripRect = aDragArea; + + tools::Rectangle aCtrlRegion(Point(), aSz); + + bNativeOk = rRenderContext.DrawNativeControl( ControlType::Toolbar, ePart, + aCtrlRegion, ControlState::ENABLED, aToolbarValue, OUString() ); + } + + if( bNativeOk ) + return; + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + rRenderContext.SetFillColor(rStyleSettings.GetShadowColor()); + + float fScaleFactor = rRenderContext.GetDPIScaleFactor(); + + if (eAlign == WindowAlign::Top || eAlign == WindowAlign::Bottom) + { + int height = static_cast<int>(0.6 * aSz.Height() + 0.5); + int i = (aSz.Height() - height) / 2; + height += i; + while (i <= height) + { + int x = nDragWidth / 2; + rRenderContext.DrawEllipse(tools::Rectangle(Point(x, i), Size(2 * fScaleFactor, 2 * fScaleFactor))); + i += 4 * fScaleFactor; + } + } + else + { + int width = static_cast<int>(0.6 * aSz.Width() + 0.5); + int i = (aSz.Width() - width) / 2; + width += i; + while (i <= width) + { + int y = nDragWidth / 2; + rRenderContext.DrawEllipse(tools::Rectangle(Point(i, y), Size(2 * fScaleFactor, 2 * fScaleFactor))); + i += 4 * fScaleFactor; + } + } +} + +void ToolBox::ImplDrawGrip(vcl::RenderContext& rRenderContext) +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper(this); + if( pWrapper && !pWrapper->GetDragArea().IsEmpty() ) + { + // execute pending paint requests + ImplCheckUpdate(); + ImplDrawGrip( rRenderContext, pWrapper->GetDragArea(), + ImplGetDragWidth(), meAlign, mbHorz ); + } +} + +void ToolBox::ImplDrawGradientBackground(vcl::RenderContext& rRenderContext) +{ + // draw a nice gradient + + Color startCol, endCol; + const StyleSettings rSettings = rRenderContext.GetSettings().GetStyleSettings(); + + startCol = rSettings.GetFaceGradientColor(); + endCol = rSettings.GetFaceColor(); + if (rSettings.GetHighContrastMode()) + // no 'extreme' gradient when high contrast + startCol = endCol; + + Gradient g; + g.SetAngle(Degree10(mbHorz ? 0 : 900)); + g.SetStyle(GradientStyle::Linear); + + g.SetStartColor(startCol); + g.SetEndColor(endCol); + + bool bLineColor = rRenderContext.IsLineColor(); + Color aOldCol = rRenderContext.GetLineColor(); + rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetShadowColor()); + + Size aFullSz(GetOutputSizePixel()); + Size aLineSz(aFullSz); + + // use the linesize only when floating + // full window height is used when docked (single line) + if (ImplIsFloatingMode()) + { + tools::Long nLineSize; + if (mbHorz) + { + nLineSize = mnMaxItemHeight; + if (mnWinHeight > mnMaxItemHeight) + nLineSize = mnWinHeight; + + aLineSz.setHeight( nLineSize ); + } + else + { + nLineSize = mnMaxItemWidth; + aLineSz.setWidth( nLineSize ); + } + } + + tools::Long nLeft, nTop, nRight, nBottom; + ImplCalcBorder(meAlign, nLeft, nTop, nRight, nBottom); + + Size aTopLineSz(aLineSz); + Size aBottomLineSz(aLineSz); + + if (mnWinStyle & WB_BORDER) + { + if (mbHorz) + { + aTopLineSz.AdjustHeight(TB_BORDER_OFFSET2 + nTop ); + aBottomLineSz.AdjustHeight(TB_BORDER_OFFSET2 + nBottom ); + + if (mnCurLines == 1) + aTopLineSz.AdjustHeight(TB_BORDER_OFFSET2 + nBottom ); + } + else + { + aTopLineSz.AdjustWidth(TB_BORDER_OFFSET1 + nLeft ); + aBottomLineSz.AdjustWidth(TB_BORDER_OFFSET1 + nRight ); + + if (mnCurLines == 1) + aTopLineSz.AdjustWidth(TB_BORDER_OFFSET1 + nLeft ); + } + } + + if (mbLineSpacing) + { + if (mbHorz) + { + aLineSz.AdjustHeight(TB_LINESPACING ); + if (mnCurLines > 1) + aTopLineSz.AdjustHeight(TB_LINESPACING ); + } + else + { + aLineSz.AdjustWidth(TB_LINESPACING ); + if (mnCurLines > 1) + aTopLineSz.AdjustWidth(TB_LINESPACING ); + } + } + + if (mbHorz) + { + tools::Long y = 0; + + rRenderContext.DrawGradient(tools::Rectangle(0, y, aTopLineSz.Width(), y + aTopLineSz.Height()), g); + y += aTopLineSz.Height(); + + while (y < (mnDY - aBottomLineSz.Height())) + { + rRenderContext.DrawGradient(tools::Rectangle(0, y, aLineSz.Width(), y + aLineSz.Height()), g); + y += aLineSz.Height(); + } + + rRenderContext.DrawGradient(tools::Rectangle(0, y, aBottomLineSz.Width(), y + aBottomLineSz.Height()), g); + } + else + { + tools::Long x = 0; + + rRenderContext.DrawGradient(tools::Rectangle(x, 0, x + aTopLineSz.Width(), aTopLineSz.Height()), g); + x += aTopLineSz.Width(); + + while (x < (mnDX - aBottomLineSz.Width())) + { + rRenderContext.DrawGradient(tools::Rectangle(x, 0, x + aLineSz.Width(), aLineSz.Height()), g); + x += aLineSz.Width(); + } + + rRenderContext.DrawGradient(tools::Rectangle( x, 0, x + aBottomLineSz.Width(), aBottomLineSz.Height()), g); + } + + if( bLineColor ) + rRenderContext.SetLineColor( aOldCol ); + +} + +bool ToolBox::ImplDrawNativeBackground(vcl::RenderContext& rRenderContext) const +{ + // use NWF + tools::Rectangle aCtrlRegion(Point(), GetOutputSizePixel()); + + return rRenderContext.DrawNativeControl( ControlType::Toolbar, mbHorz ? ControlPart::DrawBackgroundHorz : ControlPart::DrawBackgroundVert, + aCtrlRegion, ControlState::ENABLED, ImplControlValue(), OUString() ); +} + +void ToolBox::ImplDrawTransparentBackground(const vcl::Region &rRegion) +{ + // just invalidate to trigger paint of the parent + const bool bOldPaintLock = mpData->mbIsPaintLocked; + mpData->mbIsPaintLocked = true; + + // send an invalidate to the first opaque parent and invalidate the whole hierarchy from there (noclipchildren) + Invalidate(rRegion, InvalidateFlags::Update | InvalidateFlags::NoClipChildren); + + mpData->mbIsPaintLocked = bOldPaintLock; +} + +void ToolBox::ImplDrawConstantBackground(vcl::RenderContext& rRenderContext, const vcl::Region &rRegion, bool bIsInPopupMode) +{ + // draw a constant color + if (!bIsInPopupMode) + { + // default background + rRenderContext.Erase(rRegion.GetBoundRect()); + } + else + { + // use different color in popupmode + const StyleSettings rSettings = rRenderContext.GetSettings().GetStyleSettings(); + Wallpaper aWallpaper(rSettings.GetFaceGradientColor()); + rRenderContext.DrawWallpaper(rRegion.GetBoundRect(), aWallpaper); + } +} + +void ToolBox::ImplDrawBackground(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + // execute pending paint requests + ImplCheckUpdate(); + + ImplDockingWindowWrapper* pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper(this); + bool bIsInPopupMode = ImplIsInPopupMode(); + + vcl::Region aPaintRegion(rRect); + + // make sure we do not invalidate/erase too much + if (IsInPaint()) + aPaintRegion.Intersect(GetOutDev()->GetActiveClipRegion()); + + rRenderContext.Push(vcl::PushFlags::CLIPREGION); + rRenderContext.IntersectClipRegion( aPaintRegion ); + + if (!pWrapper) + { + // no gradient for ordinary toolbars (not dockable) + if( !IsBackground() && !IsInPaint() ) + ImplDrawTransparentBackground(aPaintRegion); + else + ImplDrawConstantBackground(rRenderContext, aPaintRegion, bIsInPopupMode); + } + else + { + // toolbars known to the dockingmanager will be drawn using NWF or a gradient + // docked toolbars are transparent and NWF is already used in the docking area which is their common background + // so NWF is used here for floating toolbars only + bool bNativeOk = false; + if( ImplIsFloatingMode() && rRenderContext.IsNativeControlSupported( ControlType::Toolbar, ControlPart::Entire) ) + bNativeOk = ImplDrawNativeBackground(rRenderContext); + if (!bNativeOk) + { + const StyleSettings rSetting = Application::GetSettings().GetStyleSettings(); + const bool isHeader = GetAlign() == WindowAlign::Top && !rSetting.GetPersonaHeader().IsEmpty(); + const bool isFooter = GetAlign() == WindowAlign::Bottom && !rSetting.GetPersonaFooter().IsEmpty(); + if (!IsBackground() || isHeader || isFooter) + { + if (!IsInPaint()) + ImplDrawTransparentBackground(aPaintRegion); + } + else + ImplDrawGradientBackground(rRenderContext); + } + } + + // restore clip region + rRenderContext.Pop(); +} + +void ToolBox::ImplErase(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect, bool bHighlight, bool bHasOpenPopup) +{ + // the background of non NWF buttons is painted in a constant color + // to have the same highlight color (transparency in DrawSelectionBackground()) + // items with open popups will also painted using a constant color + if (!mpData->mbNativeButtons && + (bHighlight || !(GetStyle() & WB_3DLOOK))) + { + if (GetStyle() & WB_3DLOOK) + { + rRenderContext.Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); + rRenderContext.SetLineColor(); + if (bHasOpenPopup) + // choose the same color as the popup will use + rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetFaceGradientColor()); + else + rRenderContext.SetFillColor(COL_WHITE); + + rRenderContext.DrawRect(rRect); + rRenderContext.Pop(); + } + else + ImplDrawBackground(rRenderContext, rRect); + } + else + ImplDrawBackground(rRenderContext, rRect); +} + +void ToolBox::ImplDrawBorder(vcl::RenderContext& rRenderContext) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + tools::Long nDX = mnDX; + tools::Long nDY = mnDY; + + ImplDockingWindowWrapper* pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper(this); + + // draw borders for ordinary toolbars only (not dockable), do not draw borders for toolbars with WB_NOSHADOW bit set, + // e.g. Calc's formulabar + + if( pWrapper || mnWinStyle & WB_NOSHADOW ) + return; + + if (meAlign == WindowAlign::Bottom) + { + // draw bottom border + rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() ); + rRenderContext.DrawLine( Point( 0, nDY-2 ), Point( nDX-1, nDY-2 ) ); + rRenderContext.SetLineColor( rStyleSettings.GetLightColor() ); + rRenderContext.DrawLine( Point( 0, nDY-1 ), Point( nDX-1, nDY-1 ) ); + } + else + { + // draw top border + rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() ); + rRenderContext.DrawLine( Point( 0, 0 ), Point( nDX-1, 0 ) ); + rRenderContext.SetLineColor( rStyleSettings.GetLightColor() ); + rRenderContext.DrawLine( Point( 0, 1 ), Point( nDX-1, 1 ) ); + + if (meAlign == WindowAlign::Left || meAlign == WindowAlign::Right) + { + if (meAlign == WindowAlign::Left) + { + // draw left-bottom border + rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() ); + rRenderContext.DrawLine( Point( 0, 0 ), Point( 0, nDY-1 ) ); + rRenderContext.DrawLine( Point( 0, nDY-2 ), Point( nDX-1, nDY-2 ) ); + rRenderContext.SetLineColor( rStyleSettings.GetLightColor() ); + rRenderContext.DrawLine( Point( 1, 1 ), Point( 1, nDY-3 ) ); + rRenderContext.DrawLine( Point( 0, nDY-1 ), Point( nDX-1, nDY-1 ) ); + } + else + { + // draw right-bottom border + rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() ); + rRenderContext.DrawLine( Point( nDX-2, 0 ), Point( nDX-2, nDY-3 ) ); + rRenderContext.DrawLine( Point( 0, nDY-2 ), Point( nDX-2, nDY-2 ) ); + rRenderContext.SetLineColor( rStyleSettings.GetLightColor() ); + rRenderContext.DrawLine( Point( nDX-1, 0 ), Point( nDX-1, nDY-1 ) ); + rRenderContext.DrawLine( Point( 0, nDY-1 ), Point( nDX-1, nDY-1 ) ); + } + } + } + + if ( meAlign == WindowAlign::Bottom || meAlign == WindowAlign::Top ) + { + // draw right border + rRenderContext.SetLineColor( rStyleSettings.GetShadowColor() ); + rRenderContext.DrawLine( Point( nDX-2, 0 ), Point( nDX-2, nDY-1 ) ); + rRenderContext.SetLineColor( rStyleSettings.GetLightColor() ); + rRenderContext.DrawLine( Point( nDX-1, 0 ), Point( nDX-1, nDY-1 ) ); + } +} + +static bool ImplIsFixedControl( const ImplToolItem *pItem ) +{ + return ( pItem->mpWindow && + (pItem->mbNonInteractiveWindow || + pItem->mpWindow->GetType() == WindowType::FIXEDTEXT || + pItem->mpWindow->GetType() == WindowType::FIXEDLINE || + pItem->mpWindow->GetType() == WindowType::GROUPBOX) ); +} + +const ImplToolItem *ToolBox::ImplGetFirstClippedItem() const +{ + for (auto & item : mpData->m_aItems) + { + if( item.IsClipped() ) + return &item; + } + return nullptr; +} + +Size ToolBox::ImplCalcSize( ImplToolItems::size_type nCalcLines, sal_uInt16 nCalcMode ) +{ + sal_Int32 nMax; + tools::Long nLeft = 0; + tools::Long nTop = 0; + tools::Long nRight = 0; + tools::Long nBottom = 0; + Size aSize; + WindowAlign eOldAlign = meAlign; + bool bOldHorz = mbHorz; + bool bOldAssumeDocked = mpData->mbAssumeDocked; + bool bOldAssumeFloating = mpData->mbAssumeFloating; + + if ( nCalcMode ) + { + bool bOldFloatingMode = ImplIsFloatingMode(); + + mpData->mbAssumeDocked = false; + mpData->mbAssumeFloating = false; + + if ( nCalcMode == TB_CALCMODE_HORZ ) + { + mpData->mbAssumeDocked = true; // force non-floating mode during calculation + ImplCalcBorder( WindowAlign::Top, nLeft, nTop, nRight, nBottom ); + mbHorz = true; + if ( mbHorz != bOldHorz ) + meAlign = WindowAlign::Top; + } + else if ( nCalcMode == TB_CALCMODE_VERT ) + { + mpData->mbAssumeDocked = true; // force non-floating mode during calculation + ImplCalcBorder( WindowAlign::Left, nLeft, nTop, nRight, nBottom ); + mbHorz = false; + if ( mbHorz != bOldHorz ) + meAlign = WindowAlign::Left; + } + else if ( nCalcMode == TB_CALCMODE_FLOAT ) + { + mpData->mbAssumeFloating = true; // force non-floating mode during calculation + nLeft = nTop = nRight = nBottom = 0; + mbHorz = true; + if ( mbHorz != bOldHorz ) + meAlign = WindowAlign::Top; + } + + if ( (meAlign != eOldAlign) || (mbHorz != bOldHorz) || + (ImplIsFloatingMode() != bOldFloatingMode ) ) + mbCalc = true; + } + else + ImplCalcBorder( meAlign, nLeft, nTop, nRight, nBottom ); + + ImplCalcItem(); + + if( !nCalcMode && ImplIsFloatingMode() ) + { + aSize = ImplCalcFloatSize( nCalcLines ); + } + else + { + if ( mbHorz ) + { + if ( mnWinHeight > mnMaxItemHeight ) + aSize.setHeight( nCalcLines * mnWinHeight ); + else + aSize.setHeight( nCalcLines * mnMaxItemHeight ); + + if ( mbLineSpacing ) + aSize.AdjustHeight((nCalcLines-1)*TB_LINESPACING ); + + if ( mnWinStyle & WB_BORDER ) + aSize.AdjustHeight((TB_BORDER_OFFSET2*2) + nTop + nBottom ); + + nMax = 0; + ImplCalcBreaks( TB_MAXNOSCROLL, &nMax, mbHorz ); + if ( nMax ) + aSize.AdjustWidth(nMax ); + + if ( mnWinStyle & WB_BORDER ) + aSize.AdjustWidth((TB_BORDER_OFFSET1*2) + nLeft + nRight ); + } + else + { + aSize.setWidth( nCalcLines * mnMaxItemWidth ); + + if ( mbLineSpacing ) + aSize.AdjustWidth((nCalcLines-1)*TB_LINESPACING ); + + if ( mnWinStyle & WB_BORDER ) + aSize.AdjustWidth((TB_BORDER_OFFSET2*2) + nLeft + nRight ); + + nMax = 0; + ImplCalcBreaks( TB_MAXNOSCROLL, &nMax, mbHorz ); + if ( nMax ) + aSize.AdjustHeight(nMax ); + + if ( mnWinStyle & WB_BORDER ) + aSize.AdjustHeight((TB_BORDER_OFFSET1*2) + nTop + nBottom ); + } + } + // restore previous values + if ( nCalcMode ) + { + mpData->mbAssumeDocked = bOldAssumeDocked; + mpData->mbAssumeFloating = bOldAssumeFloating; + if ( (meAlign != eOldAlign) || (mbHorz != bOldHorz) ) + { + meAlign = eOldAlign; + mbHorz = bOldHorz; + mbCalc = true; + } + } + + return aSize; +} + +void ToolBox::ImplCalcFloatSizes() +{ + if ( !maFloatSizes.empty() ) + return; + + // calculate the minimal size, i.e. where the biggest item just fits + tools::Long nCalcSize = 0; + + for (auto const& item : mpData->m_aItems) + { + if ( item.mbVisible ) + { + if ( item.mpWindow ) + { + tools::Long nTempSize = item.mpWindow->GetSizePixel().Width(); + if ( nTempSize > nCalcSize ) + nCalcSize = nTempSize; + } + else + { + if( item.maItemSize.Width() > nCalcSize ) + nCalcSize = item.maItemSize.Width(); + } + } + } + + // calc an upper bound for ImplCalcBreaks below + tools::Long upperBoundWidth = nCalcSize * mpData->m_aItems.size(); + + ImplToolItems::size_type nLines; + ImplToolItems::size_type nCalcLines; + ImplToolItems::size_type nTempLines; + sal_Int32 nMaxLineWidth; + nCalcLines = ImplCalcBreaks( nCalcSize, &nMaxLineWidth, true ); + + maFloatSizes.reserve( nCalcLines ); + + nTempLines = nLines = nCalcLines; + while ( nLines ) + { + tools::Long nHeight = ImplCalcSize( nTempLines, TB_CALCMODE_FLOAT ).Height(); + + ImplToolSize aSize; + aSize.mnWidth = nMaxLineWidth+(TB_BORDER_OFFSET1*2); + aSize.mnHeight = nHeight; + aSize.mnLines = nTempLines; + maFloatSizes.push_back( aSize ); + nLines--; + if ( nLines ) + { + do + { + nCalcSize += mnMaxItemWidth; + nTempLines = ImplCalcBreaks( nCalcSize, &nMaxLineWidth, true ); + } + while ((nCalcSize < upperBoundWidth) && (nLines < nTempLines)); // implies nTempLines>1 + if ( nTempLines < nLines ) + nLines = nTempLines; + } + } +} + +Size ToolBox::ImplCalcFloatSize( ImplToolItems::size_type& rLines ) +{ + ImplCalcFloatSizes(); + + if ( !rLines ) + { + rLines = mnFloatLines; + if ( !rLines ) + rLines = mnLines; + } + + sal_uInt16 i = 0; + while ( i + 1u < maFloatSizes.size() && rLines < maFloatSizes[i].mnLines ) + { + i++; + } + + Size aSize( maFloatSizes[i].mnWidth, maFloatSizes[i].mnHeight ); + rLines = maFloatSizes[i].mnLines; + + return aSize; +} + +void ToolBox::ImplCalcMinMaxFloatSize( Size& rMinSize, Size& rMaxSize ) +{ + ImplCalcFloatSizes(); + + sal_uInt16 i = 0; + rMinSize = Size( maFloatSizes[i].mnWidth, maFloatSizes[i].mnHeight ); + rMaxSize = Size( maFloatSizes[i].mnWidth, maFloatSizes[i].mnHeight ); + while ( ++i < maFloatSizes.size() ) + { + if( maFloatSizes[i].mnWidth < rMinSize.Width() ) + rMinSize.setWidth( maFloatSizes[i].mnWidth ); + if( maFloatSizes[i].mnHeight < rMinSize.Height() ) + rMinSize.setHeight( maFloatSizes[i].mnHeight ); + + if( maFloatSizes[i].mnWidth > rMaxSize.Width() ) + rMaxSize.setWidth( maFloatSizes[i].mnWidth ); + if( maFloatSizes[i].mnHeight > rMaxSize.Height() ) + rMaxSize.setHeight( maFloatSizes[i].mnHeight ); + } +} + +void ToolBox::ImplSetMinMaxFloatSize() +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + Size aMinSize, aMaxSize; + ImplCalcMinMaxFloatSize( aMinSize, aMaxSize ); + if( pWrapper ) + { + pWrapper->SetMinOutputSizePixel( aMinSize ); + pWrapper->SetMaxOutputSizePixel( aMaxSize ); + pWrapper->ShowMenuTitleButton( bool( GetMenuType() & ToolBoxMenuType::Customize) ); + } + else + { + // TODO: change SetMinOutputSizePixel to be not inline + SetMinOutputSizePixel( aMinSize ); + SetMaxOutputSizePixel( aMaxSize ); + } +} + +ToolBox::ImplToolItems::size_type ToolBox::ImplCalcLines( tools::Long nToolSize ) const +{ + tools::Long nLineHeight; + + if ( mbHorz ) + { + if ( mnWinHeight > mnMaxItemHeight ) + nLineHeight = mnWinHeight; + else + nLineHeight = mnMaxItemHeight; + } + else + nLineHeight = mnMaxItemWidth; + + if ( mnWinStyle & WB_BORDER ) + nToolSize -= TB_BORDER_OFFSET2*2; + + if ( mbLineSpacing ) + { + nLineHeight += TB_LINESPACING; + nToolSize += TB_LINESPACING; + } + + // #i91917# always report at least one line + tools::Long nLines = nToolSize/nLineHeight; + if( nLines < 1 ) + nLines = 1; + + return nLines; +} + +sal_uInt16 ToolBox::ImplTestLineSize( const Point& rPos ) const +{ + if ( !ImplIsFloatingMode() && + (!mbScroll || (mnLines > 1) || (mnCurLines > mnVisLines)) ) + { + WindowAlign eAlign = GetAlign(); + + if ( eAlign == WindowAlign::Left ) + { + if ( rPos.X() > mnDX-DOCK_LINEOFFSET ) + return DOCK_LINEHSIZE | DOCK_LINERIGHT; + } + else if ( eAlign == WindowAlign::Top ) + { + if ( rPos.Y() > mnDY-DOCK_LINEOFFSET ) + return DOCK_LINEVSIZE | DOCK_LINEBOTTOM; + } + else if ( eAlign == WindowAlign::Right ) + { + if ( rPos.X() < DOCK_LINEOFFSET ) + return DOCK_LINEHSIZE | DOCK_LINELEFT; + } + else if ( eAlign == WindowAlign::Bottom ) + { + if ( rPos.Y() < DOCK_LINEOFFSET ) + return DOCK_LINEVSIZE | DOCK_LINETOP; + } + } + + return 0; +} + +void ToolBox::ImplLineSizing( const Point& rPos, tools::Rectangle& rRect, sal_uInt16 nLineMode ) +{ + bool bHorz; + tools::Long nOneLineSize; + tools::Long nCurSize; + tools::Long nMaxSize; + tools::Long nSize; + Size aSize; + + if ( nLineMode & DOCK_LINERIGHT ) + { + nCurSize = rPos.X() - rRect.Left(); + bHorz = false; + } + else if ( nLineMode & DOCK_LINEBOTTOM ) + { + nCurSize = rPos.Y() - rRect.Top(); + bHorz = true; + } + else if ( nLineMode & DOCK_LINELEFT ) + { + nCurSize = rRect.Right() - rPos.X(); + bHorz = false; + } + else if ( nLineMode & DOCK_LINETOP ) + { + nCurSize = rRect.Bottom() - rPos.Y(); + bHorz = true; + } + else { + OSL_FAIL( "ImplLineSizing: Trailing else" ); + nCurSize = 0; + bHorz = false; + } + + Size aWinSize = GetSizePixel(); + ImplToolItems::size_type nMaxLines = std::max(mnLines, mnCurLines); + if ( nMaxLines > TB_MAXLINES ) + nMaxLines = TB_MAXLINES; + if ( bHorz ) + { + nOneLineSize = ImplCalcSize( 1 ).Height(); + nMaxSize = - 20; + if ( nMaxSize < aWinSize.Height() ) + nMaxSize = aWinSize.Height(); + } + else + { + nOneLineSize = ImplCalcSize( 1 ).Width(); + nMaxSize = - 20; + if ( nMaxSize < aWinSize.Width() ) + nMaxSize = aWinSize.Width(); + } + + ImplToolItems::size_type i = 1; + if ( nCurSize <= nOneLineSize ) + nSize = nOneLineSize; + else + { + nSize = 0; + while ( (nSize < nCurSize) && (i < nMaxLines) ) + { + i++; + aSize = ImplCalcSize( i ); + if ( bHorz ) + nSize = aSize.Height(); + else + nSize = aSize.Width(); + if ( nSize > nMaxSize ) + { + i--; + aSize = ImplCalcSize( i ); + if ( bHorz ) + nSize = aSize.Height(); + else + nSize = aSize.Width(); + break; + } + } + } + + if ( nLineMode & DOCK_LINERIGHT ) + rRect.SetRight( rRect.Left()+nSize-1 ); + else if ( nLineMode & DOCK_LINEBOTTOM ) + rRect.SetBottom( rRect.Top()+nSize-1 ); + else if ( nLineMode & DOCK_LINELEFT ) + rRect.SetLeft( rRect.Right()-nSize ); + else + rRect.SetTop( rRect.Bottom()-nSize ); + + mnDockLines = i; +} + +ImplTBDragMgr::ImplTBDragMgr() + : mpDragBox(nullptr) + , mnLineMode(0) + , mnStartLines(0) +{ + maAccel.InsertItem( KEY_RETURN, vcl::KeyCode( KEY_RETURN ) ); + maAccel.InsertItem( KEY_ESCAPE, vcl::KeyCode( KEY_ESCAPE ) ); + maAccel.SetSelectHdl( LINK( this, ImplTBDragMgr, SelectHdl ) ); +} + +void ImplTBDragMgr::StartDragging( ToolBox* pToolBox, + const Point& rPos, const tools::Rectangle& rRect, + sal_uInt16 nDragLineMode ) +{ + mpDragBox = pToolBox; + pToolBox->CaptureMouse(); + pToolBox->mbDragging = true; + Application::InsertAccel( &maAccel ); + + mnLineMode = nDragLineMode; + mnStartLines = pToolBox->mnDockLines; + + // calculate MouseOffset + maMouseOff.setX( rRect.Left() - rPos.X() ); + maMouseOff.setY( rRect.Top() - rPos.Y() ); + maRect = rRect; + maStartRect = rRect; + pToolBox->ShowTracking( maRect ); +} + +void ImplTBDragMgr::Dragging( const Point& rPos ) +{ + mpDragBox->ImplLineSizing( rPos, maRect, mnLineMode ); + Point aOff = mpDragBox->OutputToScreenPixel( Point() ); + maRect.Move( aOff.X(), aOff.Y() ); + mpDragBox->Docking( rPos, maRect ); + maRect.Move( -aOff.X(), -aOff.Y() ); + mpDragBox->ShowTracking( maRect ); +} + +void ImplTBDragMgr::EndDragging( bool bOK ) +{ + mpDragBox->HideTracking(); + if (mpDragBox->IsMouseCaptured()) + mpDragBox->ReleaseMouse(); + mpDragBox->mbDragging = false; + Application::RemoveAccel( &maAccel ); + + if ( !bOK ) + { + mpDragBox->mnDockLines = mnStartLines; + mpDragBox->EndDocking( maStartRect, false ); + } + else + mpDragBox->EndDocking( maRect, false ); + mnStartLines = 0; + + mpDragBox = nullptr; +} + +IMPL_LINK( ImplTBDragMgr, SelectHdl, Accelerator&, rAccel, void ) +{ + if ( rAccel.GetCurItemId() == KEY_ESCAPE ) + EndDragging( false ); + else + EndDragging(); +} + +void ToolBox::ImplInitToolBoxData() +{ + // initialize variables + ImplGetWindowImpl()->mbToolBox = true; + mpData.reset(new ImplToolBoxPrivateData); + + mpFloatWin = nullptr; + mnDX = 0; + mnDY = 0; + mnMaxItemWidth = 0; + mnMaxItemHeight = 0; + mnWinHeight = 0; + mnLeftBorder = 0; + mnTopBorder = 0; + mnRightBorder = 0; + mnBottomBorder = 0; + mnLastResizeDY = 0; + mnHighItemId = ToolBoxItemId(0); + mnCurItemId = ToolBoxItemId(0); + mnDownItemId = ToolBoxItemId(0); + mnCurPos = ITEM_NOTFOUND; + mnLines = 1; + mnCurLine = 1; + mnCurLines = 1; + mnVisLines = 1; + mnFloatLines = 0; + mnDockLines = 0; + mnMouseModifier = 0; + mbDrag = false; + mbUpper = false; + mbLower = false; + mbIn = false; + mbCalc = true; + mbFormat = false; + mbFullPaint = false; + mbHorz = true; + mbScroll = false; + mbLastFloatMode = false; + mbCustomize = false; + mbDragging = false; + mbIsKeyEvent = false; + mbChangingHighlight = false; + mbImagesMirrored = false; + mbLineSpacing = false; + mbIsArranged = false; + meButtonType = ButtonType::SYMBOLONLY; + meAlign = WindowAlign::Top; + meDockAlign = WindowAlign::Top; + meLastStyle = PointerStyle::Arrow; + mnWinStyle = 0; + meLayoutMode = ToolBoxLayoutMode::Normal; + meTextPosition = ToolBoxTextPosition::Right; + mnLastFocusItemId = ToolBoxItemId(0); + mnActivateCount = 0; + mnImagesRotationAngle = 0_deg10; + + mpStatusListener = new VclStatusListener<ToolBox>(this, ".uno:ImageOrientation"); + mpStatusListener->startListening(); + + mpIdle.reset(new Idle("vcl::ToolBox maIdle update")); + mpIdle->SetPriority( TaskPriority::RESIZE ); + mpIdle->SetInvokeHandler( LINK( this, ToolBox, ImplUpdateHdl ) ); + + // set timeout and handler for dropdown items + mpData->maDropdownTimer.SetTimeout( 250 ); + mpData->maDropdownTimer.SetInvokeHandler( LINK( this, ToolBox, ImplDropdownLongClickHdl ) ); +} + +void ToolBox::ImplInit( vcl::Window* pParent, WinBits nStyle ) +{ + // initialize variables + mbScroll = (nStyle & WB_SCROLL) != 0; + mnWinStyle = nStyle; + + DockingWindow::ImplInit( pParent, nStyle & ~WB_BORDER ); + + // dockingwindow's ImplInit removes some bits, so restore them here to allow keyboard handling for toolbars + ImplGetWindowImpl()->mnStyle |= WB_TABSTOP|WB_NODIALOGCONTROL; // always set WB_TABSTOP for ToolBars + ImplGetWindowImpl()->mnStyle &= ~WB_DIALOGCONTROL; + + ImplInitSettings(true, true, true); +} + +void ToolBox::ApplyForegroundSettings(vcl::RenderContext& rRenderContext, const StyleSettings& rStyleSettings) +{ + Color aColor; + if (IsControlForeground()) + aColor = GetControlForeground(); + else if (Window::GetStyle() & WB_3DLOOK) + aColor = rStyleSettings.GetButtonTextColor(); + else + aColor = rStyleSettings.GetWindowTextColor(); + rRenderContext.SetTextColor(aColor); + rRenderContext.SetTextFillColor(); +} + +void ToolBox::ApplyBackgroundSettings(vcl::RenderContext& rRenderContext, const StyleSettings& rStyleSettings) +{ + if (IsControlBackground()) + { + rRenderContext.SetBackground(GetControlBackground()); + SetPaintTransparent(false); + SetParentClipMode(); + } + else + { + if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Entire) + || (GetAlign() == WindowAlign::Top && !Application::GetSettings().GetStyleSettings().GetPersonaHeader().IsEmpty()) + || (GetAlign() == WindowAlign::Bottom && !Application::GetSettings().GetStyleSettings().GetPersonaFooter().IsEmpty())) + { + rRenderContext.SetBackground(); + rRenderContext.SetTextColor(rStyleSettings.GetToolTextColor()); + SetPaintTransparent(true); + SetParentClipMode(ParentClipMode::NoClip); + mpData->maDisplayBackground = Wallpaper(rStyleSettings.GetFaceColor()); + } + else + { + Color aColor; + if (Window::GetStyle() & WB_3DLOOK) + aColor = rStyleSettings.GetFaceColor(); + else + aColor = rStyleSettings.GetWindowColor(); + rRenderContext.SetBackground(aColor); + SetPaintTransparent(false); + SetParentClipMode(); + } + } +} + +void ToolBox::ApplySettings(vcl::RenderContext& rRenderContext) +{ + mpData->mbNativeButtons = rRenderContext.IsNativeControlSupported(ControlType::Toolbar, ControlPart::Button); + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + ApplyControlFont(rRenderContext, rStyleSettings.GetToolFont()); + ApplyForegroundSettings(rRenderContext, rStyleSettings); + ApplyBackgroundSettings(rRenderContext, rStyleSettings); +} + +void ToolBox::ImplInitSettings(bool bFont, bool bForeground, bool bBackground) +{ + mpData->mbNativeButtons = IsNativeControlSupported( ControlType::Toolbar, ControlPart::Button ); + + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + + if (bFont) + ApplyControlFont(*GetOutDev(), rStyleSettings.GetToolFont()); + if (bForeground || bFont) + ApplyForegroundSettings(*GetOutDev(), rStyleSettings); + if (bBackground) + { + ApplyBackgroundSettings(*GetOutDev(), rStyleSettings); + EnableChildTransparentMode(IsPaintTransparent()); + } +} + +void ToolBox::doDeferredInit(WinBits nBits) +{ + VclPtr<vcl::Window> pParent = mpDialogParent; + mpDialogParent = nullptr; + ImplInit(pParent, nBits); + mbIsDeferredInit = false; +} + +void ToolBox::queue_resize(StateChangedType eReason) +{ + Window::queue_resize(eReason); +} + +ToolBox::ToolBox( vcl::Window* pParent, WinBits nStyle ) : + DockingWindow( WindowType::TOOLBOX, "vcl::ToolBox maLayoutIdle" ) +{ + ImplInitToolBoxData(); + ImplInit( pParent, nStyle ); +} + +ToolBox::ToolBox(vcl::Window* pParent, const OString& rID, + const OUString& rUIXMLDescription, const css::uno::Reference<css::frame::XFrame> &rFrame) + : DockingWindow(WindowType::TOOLBOX, "vcl::ToolBox maLayoutIdle") +{ + ImplInitToolBoxData(); + + loadUI(pParent, rID, rUIXMLDescription, rFrame); + + // calculate size of floating windows and switch if the + // toolbox is initially in floating mode + if ( ImplIsFloatingMode() ) + mbHorz = true; + else + Resize(); + + if (!(GetStyle() & WB_HIDE)) + Show(); +} + +ToolBox::~ToolBox() +{ + disposeOnce(); +} + +void ToolBox::dispose() +{ + // #103005# make sure our activate/deactivate balance is right + while( mnActivateCount > 0 ) + Deactivate(); + + // terminate popupmode if the floating window is + // still connected + if ( mpFloatWin ) + mpFloatWin->EndPopupMode( FloatWinPopupEndFlags::Cancel ); + mpFloatWin = nullptr; + + // delete private data + mpData.reset(); + + ImplSVData* pSVData = ImplGetSVData(); + delete pSVData->maCtrlData.mpTBDragMgr; + pSVData->maCtrlData.mpTBDragMgr = nullptr; + + if (mpStatusListener.is()) + mpStatusListener->dispose(); + + mpFloatWin.clear(); + + mpIdle.reset(); + + DockingWindow::dispose(); +} + +ImplToolItem* ToolBox::ImplGetItem( ToolBoxItemId nItemId ) const +{ + if (!mpData) + return nullptr; + + for (auto & item : mpData->m_aItems) + { + if ( item.mnId == nItemId ) + return &item; + } + + return nullptr; +} + +static void ImplAddButtonBorder( tools::Long &rWidth, tools::Long& rHeight, bool bNativeButtons ) +{ + rWidth += SMALLBUTTON_HSIZE; + rHeight += SMALLBUTTON_VSIZE; + + if( bNativeButtons ) + { + // give more border space for rounded buttons + rWidth += 2; + rHeight += 4; + } +} + +bool ToolBox::ImplCalcItem() +{ + // recalc required ? + if ( !mbCalc ) + return false; + + OutputDevice *pDefault = Application::GetDefaultDevice(); + float fScaleFactor = pDefault ? pDefault->GetDPIScaleFactor() : 1.0; + + tools::Long nDefWidth; + tools::Long nDefHeight; + tools::Long nMaxWidth = 0; + tools::Long nMaxHeight = 0; + tools::Long nMinWidth = 6; + tools::Long nMinHeight = 6; + tools::Long nDropDownArrowWidth = TB_DROPDOWNARROWWIDTH * fScaleFactor; +#ifdef IOS + nDropDownArrowWidth *= 3; +#endif + + // set defaults if image or text is needed but empty + nDefWidth = GetDefaultImageSize().Width(); + nDefHeight = GetDefaultImageSize().Height(); + + mnWinHeight = 0; + // determine minimum size necessary in NWF + { + tools::Rectangle aRect( Point( 0, 0 ), Size( nMinWidth, nMinHeight ) ); + tools::Rectangle aReg( aRect ); + ImplControlValue aVal; + tools::Rectangle aNativeBounds, aNativeContent; + if( IsNativeControlSupported( ControlType::Toolbar, ControlPart::Button ) ) + { + if( GetNativeControlRegion( ControlType::Toolbar, ControlPart::Button, + aReg, + ControlState::ENABLED | ControlState::ROLLOVER, + aVal, + aNativeBounds, aNativeContent ) ) + { + aRect = aNativeBounds; + if( aRect.GetWidth() > nMinWidth ) + nMinWidth = aRect.GetWidth(); + if( aRect.GetHeight() > nMinHeight ) + nMinHeight = aRect.GetHeight(); + if( nDropDownArrowWidth < nMinWidth ) + nDropDownArrowWidth = nMinWidth; + if( nMinWidth > mpData->mnMenuButtonWidth ) + mpData->mnMenuButtonWidth = nMinWidth; + else if( nMinWidth < TB_MENUBUTTON_SIZE ) + mpData->mnMenuButtonWidth = TB_MENUBUTTON_SIZE; + } + } + + // also calculate the area for comboboxes, drop down list boxes and spinfields + // as these are often inserted into toolboxes; set mnWinHeight to the + // greater of those values to prevent toolbar flickering (#i103385#) + aRect = tools::Rectangle( Point( 0, 0 ), Size( nMinWidth, nMinHeight ) ); + aReg = aRect; + if( GetNativeControlRegion( ControlType::Combobox, ControlPart::Entire, + aReg, + ControlState::ENABLED | ControlState::ROLLOVER, + aVal, + aNativeBounds, aNativeContent ) ) + { + aRect = aNativeBounds; + if( aRect.GetHeight() > mnWinHeight ) + mnWinHeight = aRect.GetHeight(); + } + aRect = tools::Rectangle( Point( 0, 0 ), Size( nMinWidth, nMinHeight ) ); + aReg = aRect; + if( GetNativeControlRegion( ControlType::Listbox, ControlPart::Entire, + aReg, + ControlState::ENABLED | ControlState::ROLLOVER, + aVal, + aNativeBounds, aNativeContent ) ) + { + aRect = aNativeBounds; + if( aRect.GetHeight() > mnWinHeight ) + mnWinHeight = aRect.GetHeight(); + } + aRect = tools::Rectangle( Point( 0, 0 ), Size( nMinWidth, nMinHeight ) ); + aReg = aRect; + if( GetNativeControlRegion( ControlType::Spinbox, ControlPart::Entire, + aReg, + ControlState::ENABLED | ControlState::ROLLOVER, + aVal, + aNativeBounds, aNativeContent ) ) + { + aRect = aNativeBounds; + if( aRect.GetHeight() > mnWinHeight ) + mnWinHeight = aRect.GetHeight(); + } + } + + if ( ! mpData->m_aItems.empty() ) + { + for (auto & item : mpData->m_aItems) + { + item.mbVisibleText = false; // indicates if text will definitely be drawn, influences dropdown pos + + if ( item.meType == ToolBoxItemType::BUTTON ) + { + bool bImage; + bool bText; + + // check if image and/or text exists + bImage = !!item.maImage; + bText = !item.maText.isEmpty(); + ButtonType tmpButtonType = determineButtonType( &item, meButtonType ); // default to toolbox setting + if ( bImage || bText ) + { + + item.mbEmptyBtn = false; + + if ( tmpButtonType == ButtonType::SYMBOLONLY ) + { + // we're drawing images only + if ( bImage || !bText ) + { + item.maItemSize = item.maImage.GetSizePixel(); + } + else + { + item.maItemSize = Size( GetOutDev()->GetCtrlTextWidth( item.maText )+TB_TEXTOFFSET, + GetTextHeight() ); + item.mbVisibleText = true; + } + } + else if ( tmpButtonType == ButtonType::TEXT ) + { + // we're drawing text only + if ( bText || !bImage ) + { + item.maItemSize = Size( GetOutDev()->GetCtrlTextWidth( item.maText )+TB_TEXTOFFSET, + GetTextHeight() ); + item.mbVisibleText = true; + } + else + { + item.maItemSize = item.maImage.GetSizePixel(); + } + } + else + { + // we're drawing images and text + item.maItemSize.setWidth( bText ? GetOutDev()->GetCtrlTextWidth( item.maText )+TB_TEXTOFFSET : 0 ); + item.maItemSize.setHeight( bText ? GetTextHeight() : 0 ); + + if ( meTextPosition == ToolBoxTextPosition::Right ) + { + // leave space between image and text + if( bText ) + item.maItemSize.AdjustWidth(TB_IMAGETEXTOFFSET ); + + // image and text side by side + item.maItemSize.AdjustWidth(item.maImage.GetSizePixel().Width() ); + if ( item.maImage.GetSizePixel().Height() > item.maItemSize.Height() ) + item.maItemSize.setHeight( item.maImage.GetSizePixel().Height() ); + } + else + { + // leave space between image and text + if( bText ) + item.maItemSize.AdjustHeight(TB_IMAGETEXTOFFSET ); + + // text below image + item.maItemSize.AdjustHeight(item.maImage.GetSizePixel().Height() ); + if ( item.maImage.GetSizePixel().Width() > item.maItemSize.Width() ) + item.maItemSize.setWidth( item.maImage.GetSizePixel().Width() ); + } + + item.mbVisibleText = bText; + } + } + else + { // no image and no text + item.maItemSize = Size( nDefWidth, nDefHeight ); + item.mbEmptyBtn = true; + } + + // save the content size + item.maContentSize = item.maItemSize; + + // if required, take window height into consideration + if ( item.mpWindow ) + { + tools::Long nHeight = item.mpWindow->GetSizePixel().Height(); + if ( nHeight > mnWinHeight ) + mnWinHeight = nHeight; + } + + // add in drop down arrow + if( item.mnBits & ToolBoxItemBits::DROPDOWN ) + { + item.maItemSize.AdjustWidth(nDropDownArrowWidth ); + item.mnDropDownArrowWidth = nDropDownArrowWidth; + } + + // text items will be rotated in vertical mode + // -> swap width and height + if( item.mbVisibleText && !mbHorz ) + { + tools::Long tmp = item.maItemSize.Width(); + item.maItemSize.setWidth( item.maItemSize.Height() ); + item.maItemSize.setHeight( tmp ); + + tmp = item.maContentSize.Width(); + item.maContentSize.setWidth( item.maContentSize.Height() ); + item.maContentSize.setHeight( tmp ); + } + } + else if ( item.meType == ToolBoxItemType::SPACE ) + { + item.maItemSize = Size( nDefWidth, nDefHeight ); + item.maContentSize = item.maItemSize; + } + + if ( item.meType == ToolBoxItemType::BUTTON || item.meType == ToolBoxItemType::SPACE ) + { + // add borders + tools::Long w = item.maItemSize.Width(); + tools::Long h = item.maItemSize.Height(); + ImplAddButtonBorder( w, h, mpData->mbNativeButtons ); + item.maItemSize.setWidth(w); + item.maItemSize.setHeight(h); + + if( item.meType == ToolBoxItemType::BUTTON ) + { + tools::Long nMinW = std::max(nMinWidth, item.maMinimalItemSize.Width()); + tools::Long nMinH = std::max(nMinHeight, item.maMinimalItemSize.Height()); + + tools::Long nGrowContentWidth = 0; + tools::Long nGrowContentHeight = 0; + + if( item.maItemSize.Width() < nMinW ) + { + nGrowContentWidth = nMinW - item.maItemSize.Width(); + item.maItemSize.setWidth( nMinW ); + } + if( item.maItemSize.Height() < nMinH ) + { + nGrowContentHeight = nMinH - item.maItemSize.Height(); + item.maItemSize.setHeight( nMinH ); + } + + // grow the content size by the additional available space + item.maContentSize.AdjustWidth(nGrowContentWidth ); + item.maContentSize.AdjustHeight(nGrowContentHeight ); + } + + // keep track of max item size + if ( item.maItemSize.Width() > nMaxWidth ) + nMaxWidth = item.maItemSize.Width(); + if ( item.maItemSize.Height() > nMaxHeight ) + nMaxHeight = item.maItemSize.Height(); + } + } + } + else + { + nMaxWidth = nDefWidth; + nMaxHeight = nDefHeight; + + ImplAddButtonBorder( nMaxWidth, nMaxHeight, mpData->mbNativeButtons ); + } + + if( !ImplIsFloatingMode() && GetToolboxButtonSize() != ToolBoxButtonSize::DontCare + && ( meTextPosition == ToolBoxTextPosition::Right ) ) + { + // make sure all vertical toolbars have the same width and horizontal have the same height + // this depends on the used button sizes + // as this is used for alignment of multiple toolbars + // it is only required for docked toolbars + + tools::Long nFixedWidth = nDefWidth+nDropDownArrowWidth; + tools::Long nFixedHeight = nDefHeight; + ImplAddButtonBorder( nFixedWidth, nFixedHeight, mpData->mbNativeButtons ); + + if( mbHorz ) + nMaxHeight = nFixedHeight; + else + nMaxWidth = nFixedWidth; + } + + mbCalc = false; + mbFormat = true; + + // do we have to recalc the sizes ? + if ( (nMaxWidth != mnMaxItemWidth) || (nMaxHeight != mnMaxItemHeight) ) + { + mnMaxItemWidth = nMaxWidth; + mnMaxItemHeight = nMaxHeight; + + return true; + } + else + return false; +} + +ToolBox::ImplToolItems::size_type ToolBox::ImplCalcBreaks( tools::Long nWidth, sal_Int32* pMaxLineWidth, bool bCalcHorz ) const +{ + sal_uLong nLineStart = 0; + sal_uLong nGroupStart = 0; + tools::Long nLineWidth = 0; + tools::Long nCurWidth; + tools::Long nLastGroupLineWidth = 0; + tools::Long nMaxLineWidth = 0; + ImplToolItems::size_type nLines = 1; + bool bWindow; + bool bBreak = false; + tools::Long nWidthTotal = nWidth; + tools::Long nMenuWidth = 0; + + // when docked the menubutton will be in the first line + if( IsMenuEnabled() && !ImplIsFloatingMode() ) + nMenuWidth = mpData->maMenubuttonItem.maItemSize.Width(); + + // we need to know which item is the last visible one to be able to add + // the menu width in case we are unable to show all the items + ImplToolItems::iterator it, lastVisible; + for ( it = mpData->m_aItems.begin(); it != mpData->m_aItems.end(); ++it ) + { + if ( it->mbVisible ) + lastVisible = it; + } + + it = mpData->m_aItems.begin(); + while ( it != mpData->m_aItems.end() ) + { + it->mbBreak = bBreak; + bBreak = false; + + if ( it->mbVisible ) + { + bWindow = false; + bBreak = false; + nCurWidth = 0; + + if ( it->meType == ToolBoxItemType::BUTTON || it->meType == ToolBoxItemType::SPACE ) + { + if ( bCalcHorz ) + nCurWidth = it->maItemSize.Width(); + else + nCurWidth = it->maItemSize.Height(); + + if ( it->mpWindow && bCalcHorz ) + { + tools::Long nWinItemWidth = it->mpWindow->GetSizePixel().Width(); + if ( !mbScroll || (nWinItemWidth <= nWidthTotal) ) + { + nCurWidth = nWinItemWidth; + bWindow = true; + } + else + { + if ( it->mbEmptyBtn ) + { + nCurWidth = 0; + } + } + } + + // in case we are able to show all the items, we do not want + // to show the toolbar's menu; otherwise yes + if ( ( ( it == lastVisible ) && (nLineWidth+nCurWidth > nWidthTotal) && mbScroll ) || + ( ( it != lastVisible ) && (nLineWidth+nCurWidth+nMenuWidth > nWidthTotal) && mbScroll ) ) + bBreak = true; + } + else if ( it->meType == ToolBoxItemType::SEPARATOR ) + { + nCurWidth = it->mnSepSize; + if ( !ImplIsFloatingMode() && ( it != lastVisible ) && (nLineWidth+nCurWidth+nMenuWidth > nWidthTotal) ) + bBreak = true; + } + // treat breaks as separators, except when using old style toolbars (ie. no menu button) + else if ( (it->meType == ToolBoxItemType::BREAK) && !IsMenuEnabled() ) + bBreak = true; + + if ( bBreak ) + { + nLines++; + + // Add break before the entire group or take group apart? + if ( (it->meType == ToolBoxItemType::BREAK) || + (nLineStart == nGroupStart) ) + { + if ( nLineWidth > nMaxLineWidth ) + nMaxLineWidth = nLineWidth; + + nLineWidth = 0; + nLineStart = it - mpData->m_aItems.begin(); + nGroupStart = nLineStart; + it->mbBreak = true; + bBreak = false; + } + else + { + if ( nLastGroupLineWidth > nMaxLineWidth ) + nMaxLineWidth = nLastGroupLineWidth; + + // if the break is added before the group, set it to + // beginning of line and re-calculate + nLineWidth = 0; + nLineStart = nGroupStart; + it = mpData->m_aItems.begin() + nGroupStart; + continue; + } + } + else + { + if( ImplIsFloatingMode() || !IsMenuEnabled() ) // no group breaking when being docked single-line + { + if ( (it->meType != ToolBoxItemType::BUTTON) || bWindow ) + { + // found separator or break + nLastGroupLineWidth = nLineWidth; + nGroupStart = it - mpData->m_aItems.begin(); + if ( !bWindow ) + nGroupStart++; + } + } + } + + nLineWidth += nCurWidth; + } + + ++it; + } + + if ( pMaxLineWidth ) + { + if ( nLineWidth > nMaxLineWidth ) + nMaxLineWidth = nLineWidth; + + if( ImplIsFloatingMode() && !ImplIsInPopupMode() ) + { + // leave enough space to display buttons in the decoration + tools::Long aMinWidth = 2 * GetSettings().GetStyleSettings().GetFloatTitleHeight(); + if( nMaxLineWidth < aMinWidth ) + nMaxLineWidth = aMinWidth; + } + *pMaxLineWidth = nMaxLineWidth; + } + + return nLines; +} + +Size ToolBox::ImplGetOptimalFloatingSize() +{ + if( !ImplIsFloatingMode() ) + return Size(); + + Size aCurrentSize( mnDX, mnDY ); + Size aSize1( aCurrentSize ); + Size aSize2( aCurrentSize ); + + // try to preserve current height + + // calc number of floating lines for current window height + ImplToolItems::size_type nFloatLinesHeight = ImplCalcLines( mnDY ); + // calc window size according to this number + aSize1 = ImplCalcFloatSize( nFloatLinesHeight ); + + if( aCurrentSize == aSize1 ) + return aSize1; + + // try to preserve current width + + tools::Long nLineHeight = std::max( mnWinHeight, mnMaxItemHeight ); + int nBorderX = 2*TB_BORDER_OFFSET1 + mnLeftBorder + mnRightBorder; + int nBorderY = 2*TB_BORDER_OFFSET2 + mnTopBorder + mnBottomBorder; + Size aSz( aCurrentSize ); + sal_Int32 maxX; + ImplToolItems::size_type nLines = ImplCalcBreaks( aSz.Width()-nBorderX, &maxX, mbHorz ); + + ImplToolItems::size_type manyLines = 1000; + Size aMinimalFloatSize = ImplCalcFloatSize( manyLines ); + + aSz.setHeight( nBorderY + nLineHeight * nLines ); + // line space when more than one line + if ( mbLineSpacing ) + aSz.AdjustHeight((nLines-1)*TB_LINESPACING ); + + aSz.setWidth( nBorderX + maxX ); + + // avoid clipping of any items + if( aSz.Width() < aMinimalFloatSize.Width() ) + aSize2 = ImplCalcFloatSize( nLines ); + else + aSize2 = aSz; + + if( aCurrentSize == aSize2 ) + return aSize2; + + // set the size with the smallest delta as the current size + tools::Long dx1 = std::abs( mnDX - aSize1.Width() ); + tools::Long dy1 = std::abs( mnDY - aSize1.Height() ); + + tools::Long dx2 = std::abs( mnDX - aSize2.Width() ); + tools::Long dy2 = std::abs( mnDY - aSize2.Height() ); + + if( dx1*dy1 < dx2*dy2 ) + aCurrentSize = aSize1; + else + aCurrentSize = aSize2; + + return aCurrentSize; +} + +namespace +{ +void lcl_hideDoubleSeparators( ToolBox::ImplToolItems& rItems ) +{ + bool bLastSep( true ); + ToolBox::ImplToolItems::iterator it; + for ( it = rItems.begin(); it != rItems.end(); ++it ) + { + if ( it->meType == ToolBoxItemType::SEPARATOR ) + { + it->mbVisible = false; + if ( !bLastSep ) + { + // check if any visible items have to appear behind it + if (std::any_of(it + 1, rItems.end(), [](const ImplToolItem& rItem) { + return (rItem.meType == ToolBoxItemType::BUTTON) && rItem.mbVisible; })) + it->mbVisible = true; + } + bLastSep = true; + } + else if ( it->mbVisible ) + bLastSep = false; + } +} +} + +void ToolBox::ImplFormat( bool bResize ) +{ + // Has to re-formatted + if ( !mbFormat ) + return; + + mpData->ImplClearLayoutData(); + + // recalculate positions and sizes + tools::Rectangle aEmptyRect; + tools::Long nLineSize; + tools::Long nLeft; + tools::Long nTop; + tools::Long nMax; // width of layoutarea in pixels + ImplToolItems::size_type nFormatLine; + bool bMustFullPaint; + + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + bool bIsInPopupMode = ImplIsInPopupMode(); + + maFloatSizes.clear(); + + // compute border sizes + ImplCalcBorder( meAlign, mnLeftBorder, mnTopBorder, mnRightBorder, mnBottomBorder ); + + // update drag area (where the 'grip' will be placed) + tools::Rectangle aOldDragRect; + if( pWrapper ) + aOldDragRect = pWrapper->GetDragArea(); + ImplUpdateDragArea(); + + bMustFullPaint = ImplCalcItem(); + + // calculate new size during interactive resize or + // set computed size when formatting only + if ( ImplIsFloatingMode() ) + { + if ( bResize ) + mnFloatLines = ImplCalcLines( mnDY ); + else + SetOutputSizePixel( ImplGetOptimalFloatingSize() ); + } + + // Horizontal + if ( mbHorz ) + { + tools::Long nBottom; + // nLineSize: height of a single line, will fit highest item + nLineSize = mnMaxItemHeight; + + if ( mnWinHeight > mnMaxItemHeight ) + nLineSize = mnWinHeight; + + if ( mbScroll ) + { + nMax = mnDX; + mnVisLines = ImplCalcLines( mnDY ); + } + else + { + // layout over all lines + mnVisLines = mnLines; + nMax = TB_MAXNOSCROLL; + } + + // add in all border offsets + if ( mnWinStyle & WB_BORDER ) + { + nLeft = TB_BORDER_OFFSET1 + mnLeftBorder; + nTop = TB_BORDER_OFFSET2 + mnTopBorder; + nBottom = TB_BORDER_OFFSET1 + mnBottomBorder; + nMax -= nLeft + TB_BORDER_OFFSET1 + mnRightBorder; + } + else + { + nLeft = 0; + nTop = 0; + nBottom = 0; + } + + // adjust linesize if docked in single-line mode (i.e. when using a clipped item menu) + // we have to center all items in the window height + if( IsMenuEnabled() && !ImplIsFloatingMode() ) + { + tools::Long nWinHeight = mnDY - nTop - nBottom; + if( nWinHeight > nLineSize ) + nLineSize = nWinHeight; + } + } + else + { + tools::Long nRight; + nLineSize = mnMaxItemWidth; + + if ( mbScroll ) + { + mnVisLines = ImplCalcLines( mnDX ); + nMax = mnDY; + } + else + { + mnVisLines = mnLines; + nMax = TB_MAXNOSCROLL; + } + + if ( mnWinStyle & WB_BORDER ) + { + nTop = TB_BORDER_OFFSET1 + mnTopBorder; + nLeft = TB_BORDER_OFFSET2 + mnLeftBorder; + nRight = TB_BORDER_OFFSET2 + mnRightBorder; + nMax -= nTop + TB_BORDER_OFFSET1 + mnBottomBorder; + } + else + { + nLeft = 0; + nTop = 0; + nRight = 0; + } + + // adjust linesize if docked in single-line mode (i.e. when using a clipped item menu) + // we have to center all items in the window height + if( !ImplIsFloatingMode() && IsMenuEnabled() ) + { + tools::Long nWinWidth = mnDX - nLeft - nRight; + if( nWinWidth > nLineSize ) + nLineSize = nWinWidth; + } + } + + // no calculation if the window has no size (nMax=0) + // non scrolling toolboxes must be computed though + if ( (nMax <= 0) && mbScroll ) + { + mnVisLines = 1; + mnCurLine = 1; + mnCurLines = 1; + + for (auto & item : mpData->m_aItems) + { + item.maRect = aEmptyRect; + } + + maLowerRect = aEmptyRect; + maUpperRect = aEmptyRect; + } + else + { + // init start values + tools::Long nX = nLeft; // top-left offset + tools::Long nY = nTop; + nFormatLine = 1; + + // save old scroll rectangles and reset them + tools::Rectangle aOldLowerRect = maLowerRect; + tools::Rectangle aOldUpperRect = maUpperRect; + tools::Rectangle aOldMenubuttonRect = mpData->maMenubuttonItem.maRect; + maUpperRect = aEmptyRect; + maLowerRect = aEmptyRect; + mpData->maMenubuttonItem.maRect = aEmptyRect; + + // do we have any toolbox items at all ? + if ( !mpData->m_aItems.empty() || IsMenuEnabled() ) + { + lcl_hideDoubleSeparators( mpData->m_aItems ); + + // compute line breaks and visible lines give the current window width (nMax) + // the break indicators will be stored within each item (it->mbBreak) + mnCurLines = ImplCalcBreaks( nMax, nullptr, mbHorz ); + + // check for scrollbar buttons or dropdown menu + // (if a menu is enabled, this will be used to store clipped + // items and no scroll buttons will appear) + if ( (!ImplIsFloatingMode() && (mnCurLines > mnVisLines) && mbScroll ) || + IsMenuEnabled() ) + { + // compute linebreaks again, incorporating scrollbar buttons + if( !IsMenuEnabled() ) + { + nMax -= TB_SPIN_SIZE+TB_SPIN_OFFSET; + mnCurLines = ImplCalcBreaks( nMax, nullptr, mbHorz ); + } + + // compute scroll rectangles or menu button + if ( mbHorz ) + { + if( IsMenuEnabled() && !ImplHasExternalMenubutton() && !bIsInPopupMode ) + { + if( !ImplIsFloatingMode() ) + { + mpData->maMenubuttonItem.maRect.SetRight( mnDX - 2 ); + mpData->maMenubuttonItem.maRect.SetTop( nTop ); + mpData->maMenubuttonItem.maRect.SetBottom( mnDY-mnBottomBorder-TB_BORDER_OFFSET2-1 ); + } + else + { + mpData->maMenubuttonItem.maRect.SetRight( mnDX - mnRightBorder-TB_BORDER_OFFSET1-1 ); + mpData->maMenubuttonItem.maRect.SetTop( nTop ); + mpData->maMenubuttonItem.maRect.SetBottom( mnDY-mnBottomBorder-TB_BORDER_OFFSET2-1 ); + } + mpData->maMenubuttonItem.maRect.SetLeft( mpData->maMenubuttonItem.maRect.Right() - mpData->mnMenuButtonWidth ); + } + else + { + maUpperRect.SetLeft( nLeft+nMax+TB_SPIN_OFFSET ); + maUpperRect.SetRight( maUpperRect.Left()+TB_SPIN_SIZE-1 ); + maUpperRect.SetTop( nTop ); + maLowerRect.SetBottom( mnDY-mnBottomBorder-TB_BORDER_OFFSET2-1 ); + maLowerRect.SetLeft( maUpperRect.Left() ); + maLowerRect.SetRight( maUpperRect.Right() ); + maUpperRect.SetBottom( maUpperRect.Top() + + (maLowerRect.Bottom()-maUpperRect.Top())/2 ); + maLowerRect.SetTop( maUpperRect.Bottom() ); + } + } + else + { + if( IsMenuEnabled() && !ImplHasExternalMenubutton() && !bIsInPopupMode ) + { + if( !ImplIsFloatingMode() ) + { + mpData->maMenubuttonItem.maRect.SetBottom( mnDY - 2 ); + mpData->maMenubuttonItem.maRect.SetLeft( nLeft ); + mpData->maMenubuttonItem.maRect.SetRight( mnDX-mnRightBorder-TB_BORDER_OFFSET2-1 ); + } + else + { + mpData->maMenubuttonItem.maRect.SetBottom( mnDY - mnBottomBorder-TB_BORDER_OFFSET1-1 ); + mpData->maMenubuttonItem.maRect.SetLeft( nLeft ); + mpData->maMenubuttonItem.maRect.SetRight( mnDX-mnRightBorder-TB_BORDER_OFFSET2-1 ); + } + mpData->maMenubuttonItem.maRect.SetTop( mpData->maMenubuttonItem.maRect.Bottom() - mpData->mnMenuButtonWidth ); + } + else + { + maUpperRect.SetTop( nTop+nMax+TB_SPIN_OFFSET ); + maUpperRect.SetBottom( maUpperRect.Top()+TB_SPIN_SIZE-1 ); + maUpperRect.SetLeft( nLeft ); + maLowerRect.SetRight( mnDX-mnRightBorder-TB_BORDER_OFFSET2-1 ); + maLowerRect.SetTop( maUpperRect.Top() ); + maLowerRect.SetBottom( maUpperRect.Bottom() ); + maUpperRect.SetRight( maUpperRect.Left() + + (maLowerRect.Right()-maUpperRect.Left())/2 ); + maLowerRect.SetLeft( maUpperRect.Right() ); + } + } + } + + // no scrolling when there is a "more"-menu + // anything will "fit" in a single line then + if( IsMenuEnabled() ) + mnCurLines = 1; + + // determine the currently visible line + if ( mnVisLines >= mnCurLines ) + mnCurLine = 1; + else if ( mnCurLine+mnVisLines-1 > mnCurLines ) + mnCurLine = mnCurLines - (mnVisLines-1); + + tools::Long firstItemCenter = 0; + for (auto & item : mpData->m_aItems) + { + item.mbShowWindow = false; + + // check for line break and advance nX/nY accordingly + if ( item.mbBreak ) + { + nFormatLine++; + + // increment starting with the second line + if ( nFormatLine > mnCurLine ) + { + if ( mbHorz ) + { + nX = nLeft; + if ( mbLineSpacing ) + nY += nLineSize+TB_LINESPACING; + else + nY += nLineSize; + } + else + { + nY = nTop; + if ( mbLineSpacing ) + nX += nLineSize+TB_LINESPACING; + else + nX += nLineSize; + } + } + } + + if ( !item.mbVisible || (nFormatLine < mnCurLine) || + (nFormatLine > mnCurLine+mnVisLines-1) ) + // item is not visible + item.maCalcRect = aEmptyRect; + else + { + // 1. determine current item width/height + // take window size and orientation into account, because this affects the size of item windows + + Size aCurrentItemSize( item.GetSize( mbHorz, mbScroll, nMax, Size(mnMaxItemWidth, mnMaxItemHeight) ) ); + + // 2. position item rect and use size from step 1 + // items will be centered horizontally (if mbHorz) or vertically + // advance nX and nY accordingly + + if ( mbHorz ) + { + // In special mode Locked horizontal positions of all items remain unchanged. + + if ( mbIsArranged && meLayoutMode == ToolBoxLayoutMode::Locked && mnLines == 1 && item.maRect.Left() > 0 ) + nX = item.maRect.Left(); + item.maCalcRect.SetLeft( nX ); + + // In special mode Locked first item's vertical position remains unchanged. Consecutive items vertical + // positions are centered around first item's vertical position. If an item's height exceeds available + // space, item's vertical position remains unchanged too. + + if ( mbIsArranged && meLayoutMode == ToolBoxLayoutMode::Locked && mnLines == 1 ) + if ( firstItemCenter > 0 ) + if ( firstItemCenter-aCurrentItemSize.Height()/2 > nY ) + item.maCalcRect.SetTop( firstItemCenter-aCurrentItemSize.Height()/2 ); + else + item.maCalcRect.SetTop( item.maRect.Top() ); + else + { + item.maCalcRect.SetTop( item.maRect.Top() ); + firstItemCenter = item.maRect.Top()+aCurrentItemSize.Height()/2; + } + else + item.maCalcRect.SetTop( nY+(nLineSize-aCurrentItemSize.Height())/2 ); + item.maCalcRect.SetRight( nX+aCurrentItemSize.Width()-1 ); + item.maCalcRect.SetBottom( item.maCalcRect.Top()+aCurrentItemSize.Height()-1 ); + nX += aCurrentItemSize.Width(); + } + else + { + item.maCalcRect.SetLeft( nX+(nLineSize-aCurrentItemSize.Width())/2 ); + item.maCalcRect.SetTop( nY ); + item.maCalcRect.SetRight( item.maCalcRect.Left()+aCurrentItemSize.Width()-1 ); + item.maCalcRect.SetBottom( nY+aCurrentItemSize.Height()-1 ); + nY += aCurrentItemSize.Height(); + } + } + + // position window items into calculated item rect + if ( item.mpWindow ) + { + if ( item.mbShowWindow ) + { + Point aPos( item.maCalcRect.Left(), item.maCalcRect.Top() ); + + assert( item.maCalcRect.Top() >= 0 ); + + item.mpWindow->SetPosPixel( aPos ); + item.mpWindow->Show(); + } + else + item.mpWindow->Hide(); + } + } // end of loop over all items + mbIsArranged = true; + } + else + // we have no toolbox items + mnCurLines = 1; + + if( IsMenuEnabled() && ImplIsFloatingMode() && !ImplHasExternalMenubutton() && !bIsInPopupMode ) + { + // custom menu will be the last button in floating mode + ImplToolItem &rIt = mpData->maMenubuttonItem; + + if ( mbHorz ) + { + rIt.maRect.SetLeft( nX+TB_MENUBUTTON_OFFSET ); + rIt.maRect.SetTop( nY ); + rIt.maRect.SetRight( rIt.maRect.Left() + mpData->mnMenuButtonWidth ); + rIt.maRect.SetBottom( nY+nLineSize-1 ); + nX += rIt.maItemSize.Width(); + } + else + { + rIt.maRect.SetLeft( nX ); + rIt.maRect.SetTop( nY+TB_MENUBUTTON_OFFSET ); + rIt.maRect.SetRight( nX+nLineSize-1 ); + rIt.maRect.SetBottom( rIt.maRect.Top() + mpData->mnMenuButtonWidth ); + nY += rIt.maItemSize.Height(); + } + } + + // if toolbox visible trigger paint for changed regions + if ( IsVisible() && !mbFullPaint ) + { + if ( bMustFullPaint ) + { + maPaintRect = tools::Rectangle( mnLeftBorder, mnTopBorder, + mnDX-mnRightBorder, mnDY-mnBottomBorder ); + } + else + { + if ( aOldLowerRect != maLowerRect ) + { + maPaintRect.Union( maLowerRect ); + maPaintRect.Union( aOldLowerRect ); + } + if ( aOldUpperRect != maUpperRect ) + { + maPaintRect.Union( maUpperRect ); + maPaintRect.Union( aOldUpperRect ); + } + if ( aOldMenubuttonRect != mpData->maMenubuttonItem.maRect ) + { + maPaintRect.Union( mpData->maMenubuttonItem.maRect ); + maPaintRect.Union( aOldMenubuttonRect ); + } + if ( pWrapper && aOldDragRect != pWrapper->GetDragArea() ) + { + maPaintRect.Union( pWrapper->GetDragArea() ); + maPaintRect.Union( aOldDragRect ); + } + + for (auto const& item : mpData->m_aItems) + { + if ( item.maRect != item.maCalcRect ) + { + maPaintRect.Union( item.maRect ); + maPaintRect.Union( item.maCalcRect ); + } + } + } + + Invalidate( maPaintRect ); + } + + // store the new calculated item rects + maPaintRect = aEmptyRect; + for (auto & item : mpData->m_aItems) + item.maRect = item.maCalcRect; + } + + // indicate formatting is done + mbFormat = false; +} + +IMPL_LINK_NOARG(ToolBox, ImplDropdownLongClickHdl, Timer *, void) +{ + if (mnCurPos == ITEM_NOTFOUND || + !(mpData->m_aItems[ mnCurPos ].mnBits & ToolBoxItemBits::DROPDOWN)) + return; + + mpData->mbDropDownByKeyboard = false; + mpData->maDropdownClickHdl.Call( this ); + + // do not reset data if the dropdown handler opened a floating window + // see ImplFloatControl() + if( !mpFloatWin ) + { + // no floater was opened + Deactivate(); + InvalidateItem(mnCurPos); + + mnCurPos = ITEM_NOTFOUND; + mnCurItemId = ToolBoxItemId(0); + mnDownItemId = ToolBoxItemId(0); + mnMouseModifier = 0; + mnHighItemId = ToolBoxItemId(0); + } +} + +IMPL_LINK_NOARG(ToolBox, ImplUpdateHdl, Timer *, void) +{ + + if( mbFormat && mpData ) + ImplFormat(); +} + +static void ImplDrawMoreIndicator(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + const Image pImage(StockImage::Yes, CHEVRON); + Size aImageSize = pImage.GetSizePixel(); + tools::Long x = rRect.Left() + (rRect.getWidth() - aImageSize.Width())/2; + tools::Long y = rRect.Top() + (rRect.getHeight() - aImageSize.Height())/2; + DrawImageFlags nImageStyle = DrawImageFlags::NONE; + + rRenderContext.DrawImage(Point(x,y), pImage, nImageStyle); +} + +static void ImplDrawDropdownArrow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rDropDownRect, bool bSetColor, bool bRotate ) +{ + bool bLineColor = rRenderContext.IsLineColor(); + bool bFillColor = rRenderContext.IsFillColor(); + Color aOldFillColor = rRenderContext.GetFillColor(); + Color aOldLineColor = rRenderContext.GetLineColor(); + rRenderContext.SetLineColor(); + + if ( bSetColor ) + { + if (rRenderContext.GetSettings().GetStyleSettings().GetFaceColor().IsDark()) + rRenderContext.SetFillColor(COL_WHITE); + else + rRenderContext.SetFillColor(COL_BLACK); + } + + tools::Polygon aPoly(4); + + // the assumption is, that the width always specifies the size of the expected arrow. + const tools::Long nMargin = round(2 * rRenderContext.GetDPIScaleFactor()); + const tools::Long nSize = rDropDownRect.getWidth() - 2 * nMargin; + const tools::Long nHalfSize = (nSize + 1) / 2; + const tools::Long x = rDropDownRect.Left() + nMargin + (bRotate ? (rDropDownRect.getWidth() - nHalfSize) / 2 : 0); + const tools::Long y = rDropDownRect.Top() + nMargin + (rDropDownRect.getHeight() - (bRotate ? nSize : nHalfSize)) / 2; + + aPoly.SetPoint(Point(x, y), 0); + if (bRotate) // > + { + aPoly.SetPoint(Point(x, y + nSize), 1); + aPoly.SetPoint(Point(x + nHalfSize, y + nHalfSize), 2); + } + else // v + { + aPoly.SetPoint(Point(x + nHalfSize, y + nHalfSize), 1); + aPoly.SetPoint(Point(x + nSize, y), 2); + } + aPoly.SetPoint(Point(x, y), 3); + + auto aaflags = rRenderContext.GetAntialiasing(); + rRenderContext.SetAntialiasing(AntialiasingFlags::Enable); + rRenderContext.DrawPolygon( aPoly ); + rRenderContext.SetAntialiasing(aaflags); + + if( bFillColor ) + rRenderContext.SetFillColor(aOldFillColor); + else + rRenderContext.SetFillColor(); + if( bLineColor ) + rRenderContext.SetLineColor(aOldLineColor); + else + rRenderContext.SetLineColor(); +} + +void ToolBox::ImplDrawMenuButton(vcl::RenderContext& rRenderContext, bool bHighlight) +{ + if (mpData->maMenubuttonItem.maRect.IsEmpty()) + return; + + // #i53937# paint menu button only if necessary + if (!ImplHasClippedItems()) + return; + + // execute pending paint requests + ImplCheckUpdate(); + + rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR); + + // draw the 'more' indicator / button (>>) + ImplErase(rRenderContext, mpData->maMenubuttonItem.maRect, bHighlight); + + if (bHighlight) + ImplDrawButton(rRenderContext, mpData->maMenubuttonItem.maRect, 2, false, true, false ); + + if (ImplHasClippedItems()) + ImplDrawMoreIndicator(rRenderContext, mpData->maMenubuttonItem.maRect); + + // store highlight state + mpData->mbMenubuttonSelected = bHighlight; + + // restore colors + rRenderContext.Pop(); +} + +void ToolBox::ImplDrawSpin(vcl::RenderContext& rRenderContext) +{ + bool bTmpUpper; + bool bTmpLower; + + if ( maUpperRect.IsEmpty() || maLowerRect.IsEmpty() ) + return; + + bTmpUpper = mnCurLine > 1; + + bTmpLower = mnCurLine+mnVisLines-1 < mnCurLines; + + if ( !IsEnabled() ) + { + bTmpUpper = false; + bTmpLower = false; + } + + ImplDrawUpDownButtons(rRenderContext, maUpperRect, maLowerRect, + false/*bUpperIn*/, false/*bLowerIn*/, bTmpUpper, bTmpLower, !mbHorz); +} + +void ToolBox::ImplDrawSeparator(vcl::RenderContext& rRenderContext, ImplToolItems::size_type nPos, const tools::Rectangle& rRect) +{ + if ( nPos >= mpData->m_aItems.size() - 1 ) + // no separator if it's the last item + return; + + ImplToolItem* pItem = &mpData->m_aItems[nPos]; + ImplToolItem* pPreviousItem = &mpData->m_aItems[nPos-1]; + ImplToolItem* pNextItem = &mpData->m_aItems[nPos+1]; + + if ( ( pPreviousItem->mbShowWindow && pNextItem->mbShowWindow ) || pNextItem->mbBreak ) + // no separator between two windows or before a break + return; + + bool bNativeOk = false; + ControlPart nPart = IsHorizontal() ? ControlPart::SeparatorVert : ControlPart::SeparatorHorz; + if (rRenderContext.IsNativeControlSupported(ControlType::Toolbar, nPart)) + { + ImplControlValue aControlValue; + bNativeOk = rRenderContext.DrawNativeControl(ControlType::Toolbar, nPart, rRect, ControlState::NONE, aControlValue, OUString()); + } + + /* Draw the widget only if it can't be drawn natively. */ + if (bNativeOk) + return; + + tools::Long nCenterPos, nSlim; + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + rRenderContext.SetLineColor(rStyleSettings.GetSeparatorColor()); + if (IsHorizontal()) + { + nSlim = (pItem->maRect.Bottom() - pItem->maRect.Top ()) / 4; + nCenterPos = pItem->maRect.Center().X(); + rRenderContext.DrawLine(Point(nCenterPos, pItem->maRect.Top() + nSlim), + Point(nCenterPos, pItem->maRect.Bottom() - nSlim)); + } + else + { + nSlim = (pItem->maRect.Right() - pItem->maRect.Left ()) / 4; + nCenterPos = pItem->maRect.Center().Y(); + rRenderContext.DrawLine(Point(pItem->maRect.Left() + nSlim, nCenterPos), + Point(pItem->maRect.Right() - nSlim, nCenterPos)); + } +} + +void ToolBox::ImplDrawButton(vcl::RenderContext& rRenderContext, const tools::Rectangle &rRect, sal_uInt16 highlight, + bool bChecked, bool bEnabled, bool bIsWindow ) +{ + // draws toolbar button background either native or using a coloured selection + // if bIsWindow is true, the corresponding item is a control and only a selection border will be drawn + + bool bNativeOk = false; + if( !bIsWindow && rRenderContext.IsNativeControlSupported( ControlType::Toolbar, ControlPart::Button ) ) + { + ImplControlValue aControlValue; + ControlState nState = ControlState::NONE; + + if ( highlight == 1 ) nState |= ControlState::PRESSED; + if ( highlight == 2 ) nState |= ControlState::ROLLOVER; + if ( bEnabled ) nState |= ControlState::ENABLED; + + aControlValue.setTristateVal( bChecked ? ButtonValue::On : ButtonValue::Off ); + + bNativeOk = rRenderContext.DrawNativeControl( ControlType::Toolbar, ControlPart::Button, + rRect, nState, aControlValue, OUString() ); + } + + if (!bNativeOk) + vcl::RenderTools::DrawSelectionBackground(rRenderContext, *this, rRect, bIsWindow ? 3 : highlight, + bChecked, true, bIsWindow, nullptr, 2); +} + +void ToolBox::ImplDrawItem(vcl::RenderContext& rRenderContext, ImplToolItems::size_type nPos, sal_uInt16 nHighlight) +{ + if (nPos >= mpData->m_aItems.size()) + return; + + // execute pending paint requests + ImplCheckUpdate(); + + rRenderContext.SetFillColor(); + + ImplToolItem* pItem = &mpData->m_aItems[nPos]; + + if (!pItem->mbEnabled) + nHighlight = 0; + + // if the rectangle is outside visible area + if (pItem->maRect.IsEmpty()) + return; + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + // no gradient background for items that have a popup open + bool bHasOpenPopup = mpFloatWin && (mnDownItemId==pItem->mnId); + + bool bHighContrastWhite = false; + // check the face color as highcontrast indicator + // because the toolbox itself might have a gradient + if (rStyleSettings.GetFaceColor() == COL_WHITE) + bHighContrastWhite = true; + + // Compute buttons area. + Size aBtnSize = pItem->maRect.GetSize(); + + /* Compute the button/separator rectangle here, we'll need it for + * both the buttons and the separators. */ + tools::Rectangle aButtonRect( pItem->maRect.TopLeft(), aBtnSize ); + tools::Long nOffX = SMALLBUTTON_OFF_NORMAL_X; + tools::Long nOffY = SMALLBUTTON_OFF_NORMAL_Y; + tools::Long nImageOffX = 0; + tools::Long nImageOffY = 0; + DrawButtonFlags nStyle = DrawButtonFlags::NONE; + + // draw separators + if ( (pItem->meType == ToolBoxItemType::SEPARATOR) && nPos > 0 ) + { + ImplDrawSeparator(rRenderContext, nPos, aButtonRect); + } + + // do nothing if item is no button or will be displayed as window + if ( (pItem->meType != ToolBoxItemType::BUTTON) || pItem->mbShowWindow ) + return; + + if ( pItem->meState == TRISTATE_TRUE ) + { + nStyle |= DrawButtonFlags::Checked; + } + else if ( pItem->meState == TRISTATE_INDET ) + { + nStyle |= DrawButtonFlags::DontKnow; + } + if ( nHighlight == 1 ) + { + nStyle |= DrawButtonFlags::Pressed; + } + + ImplErase(rRenderContext, pItem->maRect, nHighlight != 0, bHasOpenPopup ); + + nOffX += pItem->maRect.Left(); + nOffY += pItem->maRect.Top(); + + // determine what has to be drawn on the button: image, text or both + bool bImage; + bool bText; + ButtonType tmpButtonType = determineButtonType( pItem, meButtonType ); // default to toolbox setting + pItem->DetermineButtonDrawStyle( tmpButtonType, bImage, bText ); + + // compute output values + tools::Long nBtnWidth = aBtnSize.Width()-SMALLBUTTON_HSIZE; + tools::Long nBtnHeight = aBtnSize.Height()-SMALLBUTTON_VSIZE; + Size aImageSize; + + const bool bDropDown = (pItem->mnBits & ToolBoxItemBits::DROPDOWN) == ToolBoxItemBits::DROPDOWN; + tools::Rectangle aDropDownRect; + if (bDropDown) + aDropDownRect = pItem->GetDropDownRect(mbHorz); + + if ( bImage ) + { + const Image* pImage = &(pItem->maImage); + aImageSize = pImage->GetSizePixel(); + + // determine drawing flags + DrawImageFlags nImageStyle = DrawImageFlags::NONE; + + if ( !pItem->mbEnabled || !IsEnabled() ) + nImageStyle |= DrawImageFlags::Disable; + + // #i35563# the dontknow state indicates different states at the same time + // which should not be rendered disabled but normal + + // draw the image + nImageOffX = nOffX; + nImageOffY = nOffY; + if ( ( (pItem->mnBits & (ToolBoxItemBits::LEFT|ToolBoxItemBits::DROPDOWN)) || bText ) + && ( meTextPosition == ToolBoxTextPosition::Right ) ) + { + // left align also to leave space for drop down arrow + // and when drawing text+image + // just center in y, except for vertical (ie rotated text) + if( mbHorz || !bText ) + nImageOffY += (nBtnHeight-aImageSize.Height())/2; + } + else + { + nImageOffX += (nBtnWidth-(bDropDown ? aDropDownRect.getWidth() : 0)+SMALLBUTTON_OFF_NORMAL_X-aImageSize.Width())/2; + if ( meTextPosition == ToolBoxTextPosition::Right ) + nImageOffY += (nBtnHeight-aImageSize.Height())/2; + } + if ( nHighlight != 0 || (pItem->meState == TRISTATE_TRUE) ) + { + if( bHasOpenPopup ) + ImplDrawFloatwinBorder(rRenderContext, pItem); + else + ImplDrawButton(rRenderContext, aButtonRect, nHighlight, pItem->meState == TRISTATE_TRUE, + pItem->mbEnabled && IsEnabled(), pItem->mbShowWindow); + + if( nHighlight != 0 ) + { + if( bHighContrastWhite ) + nImageStyle |= DrawImageFlags::ColorTransform; + } + } + rRenderContext.DrawImage(Point( nImageOffX, nImageOffY ), *pImage, nImageStyle); + } + + // draw the text + bool bRotate = false; + if ( bText ) + { + const Size aTxtSize(GetOutDev()->GetCtrlTextWidth(pItem->maText), GetTextHeight()); + tools::Long nTextOffX = nOffX; + tools::Long nTextOffY = nOffY; + + // rotate text when vertically docked + vcl::Font aOldFont = rRenderContext.GetFont(); + if( pItem->mbVisibleText && !ImplIsFloatingMode() && + ((meAlign == WindowAlign::Left) || (meAlign == WindowAlign::Right)) ) + { + bRotate = true; + + vcl::Font aRotateFont = aOldFont; + aRotateFont.SetOrientation( 2700_deg10 ); + + // center horizontally + nTextOffX += aTxtSize.Height(); + nTextOffX += (nBtnWidth-aTxtSize.Height())/2; + + // add in image offset + if( bImage ) + nTextOffY = nImageOffY + aImageSize.Height() + TB_IMAGETEXTOFFSET; + + rRenderContext.SetFont(aRotateFont); + } + else + { + if ( meTextPosition == ToolBoxTextPosition::Right ) + { + // center vertically + nTextOffY += (nBtnHeight-aTxtSize.Height())/2; + + // add in image offset + if( bImage ) + nTextOffX = nImageOffX + aImageSize.Width() + TB_IMAGETEXTOFFSET; + } + else + { + // center horizontally + nTextOffX += (nBtnWidth-(bDropDown ? aDropDownRect.getWidth() : 0)+SMALLBUTTON_OFF_NORMAL_X-aTxtSize.Width() - TB_IMAGETEXTOFFSET)/2; + // set vertical position + nTextOffY += nBtnHeight - aTxtSize.Height(); + } + } + + // draw selection only if not already drawn during image output (see above) + if ( !bImage && (nHighlight != 0 || (pItem->meState == TRISTATE_TRUE) ) ) + { + if( bHasOpenPopup ) + ImplDrawFloatwinBorder(rRenderContext, pItem); + else + ImplDrawButton(rRenderContext, pItem->maRect, nHighlight, pItem->meState == TRISTATE_TRUE, + pItem->mbEnabled && IsEnabled(), pItem->mbShowWindow ); + } + + DrawTextFlags nTextStyle = DrawTextFlags::NONE; + if ( !pItem->mbEnabled ) + nTextStyle |= DrawTextFlags::Disable; + rRenderContext.DrawCtrlText( Point( nTextOffX, nTextOffY ), pItem->maText, + 0, pItem->maText.getLength(), nTextStyle ); + if ( bRotate ) + SetFont( aOldFont ); + } + + // paint optional drop down arrow + if (!bDropDown) + return; + + bool bSetColor = true; + if ( !pItem->mbEnabled || !IsEnabled() ) + { + bSetColor = false; + rRenderContext.SetFillColor(rStyleSettings.GetShadowColor()); + } + + // dropdown only will be painted without inner border + if( (pItem->mnBits & ToolBoxItemBits::DROPDOWNONLY) != ToolBoxItemBits::DROPDOWNONLY ) + { + ImplErase(rRenderContext, aDropDownRect, nHighlight != 0, bHasOpenPopup); + + if( nHighlight != 0 || (pItem->meState == TRISTATE_TRUE) ) + { + if( bHasOpenPopup ) + ImplDrawFloatwinBorder(rRenderContext, pItem); + else + ImplDrawButton(rRenderContext, aDropDownRect, nHighlight, pItem->meState == TRISTATE_TRUE, + pItem->mbEnabled && IsEnabled(), false); + } + } + ImplDrawDropdownArrow(rRenderContext, aDropDownRect, bSetColor, bRotate); +} + +void ToolBox::ImplDrawFloatwinBorder(vcl::RenderContext& rRenderContext, ImplToolItem const * pItem) +{ + if ( pItem->maRect.IsEmpty() ) + return; + + tools::Rectangle aRect( mpFloatWin->ImplGetItemEdgeClipRect() ); + aRect.SetPos( AbsoluteScreenToOutputPixel( aRect.TopLeft() ) ); + rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetShadowColor()); + Point p1, p2; + + p1 = pItem->maRect.TopLeft(); + p1.AdjustX( 1 ); + p2 = pItem->maRect.TopRight(); + p2.AdjustX( -1 ); + rRenderContext.DrawLine( p1, p2); + p1 = pItem->maRect.BottomLeft(); + p1.AdjustX( 1 ); + p2 = pItem->maRect.BottomRight(); + p2.AdjustX( -1 ); + rRenderContext.DrawLine( p1, p2); + + p1 = pItem->maRect.TopLeft(); + p1.AdjustY( 1 ); + p2 = pItem->maRect.BottomLeft(); + p2.AdjustY( -1 ); + rRenderContext.DrawLine( p1, p2); + p1 = pItem->maRect.TopRight(); + p1.AdjustY( 1 ); + p2 = pItem->maRect.BottomRight(); + p2.AdjustY( -1 ); + rRenderContext.DrawLine( p1, p2); + +} + +void ToolBox::ImplFloatControl( bool bStart, FloatingWindow* pFloatWindow ) +{ + + if ( bStart ) + { + mpFloatWin = pFloatWindow; + + // redraw item, to trigger drawing of a special border + InvalidateItem(mnCurPos); + + mbDrag = false; + EndTracking(); + if (IsMouseCaptured()) + ReleaseMouse(); + } + else + { + mpFloatWin = nullptr; + + // if focus is still in this toolbox, then the floater was opened by keyboard + // draw current item with highlight and keep old state + bool bWasKeyboardActivate = mpData->mbDropDownByKeyboard; + + if ( mnCurPos != ITEM_NOTFOUND ) + InvalidateItem(mnCurPos); + Deactivate(); + + if( !bWasKeyboardActivate ) + { + mnCurPos = ITEM_NOTFOUND; + mnCurItemId = ToolBoxItemId(0); + mnHighItemId = ToolBoxItemId(0); + } + mnDownItemId = ToolBoxItemId(0); + + } +} + +void ToolBox::ShowLine( bool bNext ) +{ + mbFormat = true; + + if ( bNext ) + mnCurLine++; + else + mnCurLine--; + + ImplFormat(); +} + +bool ToolBox::ImplHandleMouseMove( const MouseEvent& rMEvt, bool bRepeat ) +{ + Point aMousePos = rMEvt.GetPosPixel(); + + if ( !mpData ) + return false; + + // ToolBox active? + if ( mbDrag && mnCurPos != ITEM_NOTFOUND ) + { + // is the cursor over the item? + ImplToolItem* pItem = &mpData->m_aItems[mnCurPos]; + if ( pItem->maRect.Contains( aMousePos ) ) + { + if ( !mnCurItemId ) + { + InvalidateItem(mnCurPos); + mnCurItemId = pItem->mnId; + Highlight(); + } + + if ( (pItem->mnBits & ToolBoxItemBits::REPEAT) && bRepeat ) + Select(); + } + else + { + if ( mnCurItemId ) + { + InvalidateItem(mnCurPos); + mnCurItemId = ToolBoxItemId(0); + InvalidateItem(mnCurPos); + Highlight(); + } + } + + return true; + } + + if ( mbUpper ) + { + bool bNewIn = maUpperRect.Contains( aMousePos ); + if ( bNewIn != mbIn ) + { + mbIn = bNewIn; + InvalidateSpin(true, false); + } + return true; + } + + if ( mbLower ) + { + bool bNewIn = maLowerRect.Contains( aMousePos ); + if ( bNewIn != mbIn ) + { + mbIn = bNewIn; + InvalidateSpin(false); + } + return true; + } + + return false; +} + +bool ToolBox::ImplHandleMouseButtonUp( const MouseEvent& rMEvt, bool bCancel ) +{ + if ( !mpData ) + return false; + + // stop eventual running dropdown timer + if( mnCurPos < mpData->m_aItems.size() && + (mpData->m_aItems[mnCurPos].mnBits & ToolBoxItemBits::DROPDOWN ) ) + { + mpData->maDropdownTimer.Stop(); + } + + if ( mbDrag ) + { + Deactivate(); + + if ( mbDrag ) + mbDrag = false; + else + { + if ( mnCurPos == ITEM_NOTFOUND ) + return true; + } + + // has mouse been released on top of item? + if( mnCurPos < mpData->m_aItems.size() ) + { + ImplToolItem* pItem = &mpData->m_aItems[mnCurPos]; + if ( pItem->maRect.Contains( rMEvt.GetPosPixel() ) ) + { + mnCurItemId = pItem->mnId; + if ( !bCancel ) + { + // execute AutoCheck if required + if ( pItem->mnBits & ToolBoxItemBits::AUTOCHECK ) + { + if ( pItem->mnBits & ToolBoxItemBits::RADIOCHECK ) + { + if ( pItem->meState != TRISTATE_TRUE ) + SetItemState( pItem->mnId, TRISTATE_TRUE ); + } + else + { + if ( pItem->meState != TRISTATE_TRUE ) + pItem->meState = TRISTATE_TRUE; + else + pItem->meState = TRISTATE_FALSE; + } + } + + // do not call Select when Repeat is active, as in this + // case that was triggered already in MouseButtonDown + if ( !(pItem->mnBits & ToolBoxItemBits::REPEAT) ) + { + // prevent from being destroyed in the select handler + VclPtr<vcl::Window> xWindow = this; + Select(); + if ( xWindow->isDisposed() ) + return true; + } + } + + { + } + + // Items not destroyed, in Select handler + if ( mnCurItemId ) + { + // Get current pos for the case that items are inserted/removed + // in the toolBox + mnCurPos = GetItemPos( mnCurItemId ); + if ( mnCurPos != ITEM_NOTFOUND ) + { + InvalidateItem(mnCurPos); + GetOutDev()->Flush(); + } + } + } + } + + mnCurPos = ITEM_NOTFOUND; + mnCurItemId = ToolBoxItemId(0); + mnDownItemId = ToolBoxItemId(0); + mnMouseModifier = 0; + return true; + } + else if ( mbUpper || mbLower ) + { + if ( mbIn ) + ShowLine( !mbUpper ); + mbUpper = false; + mbLower = false; + mbIn = false; + InvalidateSpin(); + return true; + } + + return false; +} + +void ToolBox::MouseMove( const MouseEvent& rMEvt ) +{ + // pressing a modifier generates synthetic mouse moves + // ignore it if keyboard selection is active + if( HasFocus() && ( rMEvt.GetMode() & MouseEventModifiers::MODIFIERCHANGED ) ) + return; + + if ( ImplHandleMouseMove( rMEvt ) ) + return; + + Point aMousePos = rMEvt.GetPosPixel(); + + // only highlight when the focus is not inside a child window of a toolbox + // eg, in an edit control + // and do not highlight when focus is in a different toolbox + bool bDrawHotSpot = true; + vcl::Window *pFocusWin = Application::GetFocusWindow(); + + bool bFocusWindowIsAToolBoxChild = false; + if (pFocusWin) + { + vcl::Window *pWin = pFocusWin->GetParent(); + while (pWin) + { + if(pWin->ImplGetWindowImpl() && pWin->ImplGetWindowImpl()->mbToolBox) + { + bFocusWindowIsAToolBoxChild = true; + break; + } + pWin = pWin->GetParent(); + } + } + + if( bFocusWindowIsAToolBoxChild || (pFocusWin && pFocusWin->ImplGetWindowImpl() && pFocusWin->ImplGetWindowImpl()->mbToolBox && pFocusWin != this) ) + bDrawHotSpot = false; + + if ( mbDragging ) + { + ImplTBDragMgr* pMgr = ImplGetTBDragMgr(); + pMgr->Dragging( aMousePos ); + return; + } + + PointerStyle eStyle = PointerStyle::Arrow; + + // change mouse cursor over drag area + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper && pWrapper->GetDragArea().Contains( rMEvt.GetPosPixel() ) ) + eStyle = PointerStyle::Move; + + if ( (mnWinStyle & TB_WBLINESIZING) == TB_WBLINESIZING ) + { + if ( rMEvt.GetMode() & MouseEventModifiers::SIMPLEMOVE ) + { + sal_uInt16 nLinePtr = ImplTestLineSize( rMEvt.GetPosPixel() ); + if ( nLinePtr & DOCK_LINEHSIZE ) + { + if ( meAlign == WindowAlign::Left ) + eStyle = PointerStyle::WindowESize; + else + eStyle = PointerStyle::WindowWSize; + } + else if ( nLinePtr & DOCK_LINEVSIZE ) + { + if ( meAlign == WindowAlign::Top ) + eStyle = PointerStyle::WindowSSize; + else + eStyle = PointerStyle::WindowNSize; + } + } + } + + if ( bDrawHotSpot ) + { + bool bClearHigh = true; + if ( !rMEvt.IsLeaveWindow() && (mnCurPos == ITEM_NOTFOUND) ) + { + ImplToolItems::size_type nTempPos = 0; + for (auto const& item : mpData->m_aItems) + { + if ( item.maRect.Contains( aMousePos ) ) + { + if ( (item.meType == ToolBoxItemType::BUTTON) && item.mbEnabled ) + { + bClearHigh = false; + if ( mnHighItemId != item.mnId ) + { + if ( mnHighItemId ) + { + ImplHideFocus(); + ImplToolItems::size_type nPos = GetItemPos( mnHighItemId ); + InvalidateItem(nPos); + CallEventListeners( VclEventId::ToolboxHighlightOff, reinterpret_cast< void* >( nPos ) ); + } + if ( mpData->mbMenubuttonSelected ) + { + // remove highlight from menubutton + InvalidateMenuButton(); + } + mnHighItemId = item.mnId; + InvalidateItem(nTempPos); + ImplShowFocus(); + CallEventListeners( VclEventId::ToolboxHighlight ); + } + } + break; + } + ++nTempPos; + } + } + + // only clear highlight when focus is not in toolbar + bool bMenuButtonHit = mpData->maMenubuttonItem.maRect.Contains( aMousePos ) && ImplHasClippedItems(); + if ( !HasFocus() && (bClearHigh || bMenuButtonHit) ) + { + if ( !bMenuButtonHit && mpData->mbMenubuttonSelected ) + { + // remove highlight from menubutton + InvalidateMenuButton(); + } + + if( mnHighItemId ) + { + ImplToolItems::size_type nClearPos = GetItemPos( mnHighItemId ); + if ( nClearPos != ITEM_NOTFOUND ) + { + InvalidateItem(nClearPos); + if( nClearPos != mnCurPos ) + CallEventListeners( VclEventId::ToolboxHighlightOff, reinterpret_cast< void* >( nClearPos ) ); + } + ImplHideFocus(); + mnHighItemId = ToolBoxItemId(0); + } + + if( bMenuButtonHit ) + { + InvalidateMenuButton(); + } + } + } + + if ( meLastStyle != eStyle ) + { + meLastStyle = eStyle; + SetPointer( eStyle ); + } + + DockingWindow::MouseMove( rMEvt ); +} + +void ToolBox::MouseButtonDown( const MouseEvent& rMEvt ) +{ + // only trigger toolbox for left mouse button and when + // we're not in normal operation + if ( rMEvt.IsLeft() && !mbDrag && (mnCurPos == ITEM_NOTFOUND) ) + { + // call activate already here, as items could + // be exchanged + Activate(); + + // update ToolBox here, such that user knows it + if ( mbFormat ) + { + ImplFormat(); + PaintImmediately(); + } + + Point aMousePos = rMEvt.GetPosPixel(); + ImplToolItems::size_type i = 0; + ImplToolItems::size_type nNewPos = ITEM_NOTFOUND; + + // search for item that was clicked + for (auto const& item : mpData->m_aItems) + { + // is this the item? + if ( item.maRect.Contains( aMousePos ) ) + { + // do nothing if it is a separator or + // if the item has been disabled + if ( (item.meType == ToolBoxItemType::BUTTON) && + !item.mbShowWindow ) + nNewPos = i; + + break; + } + + i++; + } + + // item found + if ( nNewPos != ITEM_NOTFOUND ) + { + if ( !mpData->m_aItems[nNewPos].mbEnabled ) + { + Deactivate(); + return; + } + + // update actual data + StartTrackingFlags nTrackFlags = StartTrackingFlags::NONE; + mnCurPos = i; + mnCurItemId = mpData->m_aItems[nNewPos].mnId; + mnDownItemId = mnCurItemId; + mnMouseModifier = rMEvt.GetModifier(); + if ( mpData->m_aItems[nNewPos].mnBits & ToolBoxItemBits::REPEAT ) + nTrackFlags |= StartTrackingFlags::ButtonRepeat; + + // update bDrag here, as it is evaluated in the EndSelection + mbDrag = true; + + // on double-click: only call the handler, but do so before the button + // is hit, as in the handler dragging + // can be terminated + if ( rMEvt.GetClicks() == 2 ) + DoubleClick(); + + if ( mbDrag ) + { + InvalidateItem(mnCurPos); + Highlight(); + } + + // was dropdown arrow pressed + if( mpData->m_aItems[nNewPos].mnBits & ToolBoxItemBits::DROPDOWN ) + { + if( ( (mpData->m_aItems[nNewPos].mnBits & ToolBoxItemBits::DROPDOWNONLY) == ToolBoxItemBits::DROPDOWNONLY) + || mpData->m_aItems[nNewPos].GetDropDownRect( mbHorz ).Contains( aMousePos )) + { + // dropdownonly always triggers the dropdown handler, over the whole button area + + // the drop down arrow should not trigger the item action + mpData->mbDropDownByKeyboard = false; + mpData->maDropdownClickHdl.Call( this ); + + // do not reset data if the dropdown handler opened a floating window + // see ImplFloatControl() + if( !mpFloatWin ) + { + // no floater was opened + Deactivate(); + InvalidateItem(mnCurPos); + + mnCurPos = ITEM_NOTFOUND; + mnCurItemId = ToolBoxItemId(0); + mnDownItemId = ToolBoxItemId(0); + mnMouseModifier = 0; + mnHighItemId = ToolBoxItemId(0); + } + return; + } + else // activate long click timer + mpData->maDropdownTimer.Start(); + } + + // call Click handler + if ( rMEvt.GetClicks() != 2 ) + Click(); + + // also call Select handler at repeat + if ( nTrackFlags & StartTrackingFlags::ButtonRepeat ) + Select(); + + // if the actions was not aborted in Click handler + if ( mbDrag ) + StartTracking( nTrackFlags ); + + // if mouse was clicked over an item we + // can abort here + return; + } + + Deactivate(); + + // menu button hit ? + if( mpData->maMenubuttonItem.maRect.Contains( aMousePos ) && ImplHasClippedItems() ) + { + if ( maMenuButtonHdl.IsSet() ) + maMenuButtonHdl.Call( this ); + else + ExecuteCustomMenu( mpData->maMenubuttonItem.maRect ); + return; + } + + // check scroll- and next-buttons here + if ( maUpperRect.Contains( aMousePos ) ) + { + if ( mnCurLine > 1 ) + { + StartTracking(); + mbUpper = true; + mbIn = true; + InvalidateSpin(true, false); + } + return; + } + if ( maLowerRect.Contains( aMousePos ) ) + { + if ( mnCurLine+mnVisLines-1 < mnCurLines ) + { + StartTracking(); + mbLower = true; + mbIn = true; + InvalidateSpin(false); + } + return; + } + + // Linesizing testen + if ( (mnWinStyle & TB_WBLINESIZING) == TB_WBLINESIZING ) + { + sal_uInt16 nLineMode = ImplTestLineSize( aMousePos ); + if ( nLineMode ) + { + ImplTBDragMgr* pMgr = ImplGetTBDragMgr(); + + // call handler, such that we can set the + // dock rectangles + StartDocking(); + + Point aPos = GetParent()->OutputToScreenPixel( GetPosPixel() ); + Size aSize = GetSizePixel(); + aPos = ScreenToOutputPixel( aPos ); + + // start dragging + pMgr->StartDragging( this, aMousePos, tools::Rectangle( aPos, aSize ), + nLineMode ); + return; + } + } + + // no item, then only click or double click + if ( rMEvt.GetClicks() == 2 ) + DoubleClick(); + else + Click(); + } + + if ( !mbDrag && (mnCurPos == ITEM_NOTFOUND) ) + DockingWindow::MouseButtonDown( rMEvt ); +} + +void ToolBox::MouseButtonUp( const MouseEvent& rMEvt ) +{ + if ( ImplHandleMouseButtonUp( rMEvt ) ) + return; + + if ( mbDragging && rMEvt.IsLeft() ) + { + ImplTBDragMgr* pMgr = ImplGetTBDragMgr(); + pMgr->EndDragging(); + return; + } + + DockingWindow::MouseButtonUp( rMEvt ); +} + +void ToolBox::Tracking( const TrackingEvent& rTEvt ) +{ + VclPtr<vcl::Window> xWindow = this; + + if ( rTEvt.IsTrackingEnded() ) + ImplHandleMouseButtonUp( rTEvt.GetMouseEvent(), rTEvt.IsTrackingCanceled() ); + else + ImplHandleMouseMove( rTEvt.GetMouseEvent(), rTEvt.IsTrackingRepeat() ); + + if ( xWindow->isDisposed() ) + // toolbox was deleted + return; + DockingWindow::Tracking( rTEvt ); +} + +void ToolBox::InvalidateItem(ImplToolItems::size_type nPosition) +{ + if (mpData && nPosition < mpData->m_aItems.size()) + { + ImplToolItem* pItem = &mpData->m_aItems[nPosition]; + Invalidate(pItem->maRect); + } +} + +void ToolBox::InvalidateMenuButton() +{ + if (!mpData->maMenubuttonItem.maRect.IsEmpty()) + Invalidate(mpData->maMenubuttonItem.maRect); +} + +void ToolBox::InvalidateSpin(bool bUpperIn, bool bLowerIn) +{ + if (bUpperIn && !maUpperRect.IsEmpty()) + Invalidate(maUpperRect); + + if (bLowerIn && !maLowerRect.IsEmpty()) + Invalidate(maLowerRect); +} + +void ToolBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rPaintRect) +{ + if( mpData->mbIsPaintLocked ) + return; + + if (rPaintRect == tools::Rectangle(0, 0, mnDX-1, mnDY-1)) + mbFullPaint = true; + ImplFormat(); + mbFullPaint = false; + + ImplDrawBackground(rRenderContext, rPaintRect); + + if ( (mnWinStyle & WB_BORDER) && !ImplIsFloatingMode() ) + ImplDrawBorder(rRenderContext); + + if( !ImplIsFloatingMode() ) + ImplDrawGrip(rRenderContext); + + ImplDrawMenuButton(rRenderContext, mpData->mbMenubuttonSelected); + + // draw SpinButtons + if (mnWinStyle & WB_SCROLL) + { + if (mnCurLines > mnLines) + ImplDrawSpin(rRenderContext); + } + + // draw buttons + ImplToolItems::size_type nHighPos; + if ( mnHighItemId ) + nHighPos = GetItemPos( mnHighItemId ); + else + nHighPos = ITEM_NOTFOUND; + + ImplToolItems::size_type nCount = mpData->m_aItems.size(); + for( ImplToolItems::size_type i = 0; i < nCount; i++ ) + { + ImplToolItem* pItem = &mpData->m_aItems[i]; + + // only draw when the rectangle is in the draw rectangle + if ( !pItem->maRect.IsEmpty() && rPaintRect.Overlaps( pItem->maRect ) ) + { + sal_uInt16 nHighlight = 0; + if ( i == mnCurPos ) + nHighlight = 1; + else if ( i == nHighPos ) + nHighlight = 2; + ImplDrawItem(rRenderContext, i, nHighlight); + } + } + ImplShowFocus(); +} + +void ToolBox::Resize() +{ + Size aSize = GetOutputSizePixel(); + // #i31422# some WindowManagers send (0,0) sizes when + // switching virtual desktops - ignore this and avoid reformatting + if( !aSize.Width() && !aSize.Height() ) + return; + + tools::Long nOldDX = mnDX; + tools::Long nOldDY = mnDY; + mnDX = aSize.Width(); + mnDY = aSize.Height(); + + mnLastResizeDY = 0; + + // invalidate everything to have gradient backgrounds properly drawn + Invalidate(); + + // If we have any expandable entries, then force a reformat first using + // their optimal sizes, then share out the excess space evenly across those + // expandables and reformat again + std::vector<size_t> aExpandables; + for (size_t i = 0; i < mpData->m_aItems.size(); ++i) + { + if (mpData->m_aItems[i].mbExpand) + { + vcl::Window *pWindow = mpData->m_aItems[i].mpWindow; + SAL_INFO_IF(!pWindow, "vcl.layout", "only tabitems with window supported at the moment"); + if (!pWindow) + continue; + Size aWinSize(pWindow->GetSizePixel()); + Size aPrefSize(pWindow->get_preferred_size()); + aWinSize.setWidth( aPrefSize.Width() ); + pWindow->SetSizePixel(aWinSize); + aExpandables.push_back(i); + } + } + + // re-format or re-draw + if ( mbScroll || !aExpandables.empty() ) + { + if ( !mbFormat || !aExpandables.empty() ) + { + mbFormat = true; + if( IsReallyVisible() || !aExpandables.empty() ) + { + ImplFormat(true); + + if (!aExpandables.empty()) + { + //Get how big the optimal size is + tools::Rectangle aBounds; + for (const ImplToolItem & rItem : mpData->m_aItems) + { + aBounds.Union( rItem.maRect ); + } + + auto nOptimalWidth = aBounds.GetWidth(); + auto nDiff = aSize.Width() - nOptimalWidth; + decltype(nDiff) nExpandablesSize = aExpandables.size(); + nDiff /= nExpandablesSize; + + //share out the diff from optimal to real across + //expandable entries + for (size_t nIndex : aExpandables) + { + vcl::Window *pWindow = mpData->m_aItems[nIndex].mpWindow; + Size aWinSize(pWindow->GetSizePixel()); + Size aPrefSize(pWindow->get_preferred_size()); + aWinSize.setWidth( aPrefSize.Width() + nDiff ); + pWindow->SetSizePixel(aWinSize); + } + + //now reformat with final sizes + mbFormat = true; + ImplFormat(true); + } + } + } + } + + // redraw border + if ( !(mnWinStyle & WB_BORDER) ) + return; + + // as otherwise, when painting we might think we have to re-draw everything + if ( mbFormat && IsReallyVisible() ) + Invalidate(); + else + { + if ( mnRightBorder ) + { + if ( nOldDX > mnDX ) + Invalidate( tools::Rectangle( mnDX-mnRightBorder-1, 0, mnDX, mnDY ) ); + else + Invalidate( tools::Rectangle( nOldDX-mnRightBorder-1, 0, nOldDX, nOldDY ) ); + } + + if ( mnBottomBorder ) + { + if ( nOldDY > mnDY ) + Invalidate( tools::Rectangle( 0, mnDY-mnBottomBorder-1, mnDX, mnDY ) ); + else + Invalidate( tools::Rectangle( 0, nOldDY-mnBottomBorder-1, nOldDX, nOldDY ) ); + } + } +} + +namespace +{ + bool DispatchableCommand(std::u16string_view rName) + { + return o3tl::starts_with(rName, u".uno") || + o3tl::starts_with(rName, u"slot:") || + o3tl::starts_with(rName, u"macro:") || + o3tl::starts_with(rName, u"vnd.sun.star.script"); + } +} + +const OUString& ToolBox::ImplGetHelpText( ToolBoxItemId nItemId ) const +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + assert( pItem ); + + if ( pItem->maHelpText.isEmpty() && ( !pItem->maHelpId.isEmpty() || pItem->maCommandStr.getLength() )) + { + Help* pHelp = Application::GetHelp(); + if ( pHelp ) + { + if (DispatchableCommand(pItem->maCommandStr)) + pItem->maHelpText = pHelp->GetHelpText( pItem->maCommandStr, this ); + if ( pItem->maHelpText.isEmpty() && !pItem->maHelpId.isEmpty() ) + pItem->maHelpText = pHelp->GetHelpText( OStringToOUString( pItem->maHelpId, RTL_TEXTENCODING_UTF8 ), this ); + } + } + + return pItem->maHelpText; +} + +void ToolBox::RequestHelp( const HelpEvent& rHEvt ) +{ + ToolBoxItemId nItemId; + Point aHelpPos; + + if( !rHEvt.KeyboardActivated() ) + { + nItemId = GetItemId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) ); + aHelpPos = rHEvt.GetMousePosPixel(); + } + else + { + if( !mnHighItemId ) + return; + else + nItemId = mnHighItemId; + tools::Rectangle aRect( GetItemRect( nItemId ) ); + if( aRect.IsEmpty() ) + return; + else + aHelpPos = OutputToScreenPixel( aRect.Center() ); + } + + if ( nItemId ) + { + if ( rHEvt.GetMode() & (HelpEventMode::BALLOON | HelpEventMode::QUICK) ) + { + // get rectangle + tools::Rectangle aTempRect = GetItemRect( nItemId ); + Point aPt = OutputToScreenPixel( aTempRect.TopLeft() ); + aTempRect.SetLeft( aPt.X() ); + aTempRect.SetTop( aPt.Y() ); + aPt = OutputToScreenPixel( aTempRect.BottomRight() ); + aTempRect.SetRight( aPt.X() ); + aTempRect.SetBottom( aPt.Y() ); + + // get text and display it + OUString aStr = GetQuickHelpText( nItemId ); + if (aStr.isEmpty()) + aStr = MnemonicGenerator::EraseAllMnemonicChars( GetItemText( nItemId ) ); + if ( rHEvt.GetMode() & HelpEventMode::BALLOON ) + { + const OUString& rHelpStr = GetHelpText( nItemId ); + if (!rHelpStr.isEmpty()) + aStr = rHelpStr; + Help::ShowBalloon( this, aHelpPos, aTempRect, aStr ); + } + else + Help::ShowQuickHelp( this, aTempRect, aStr, QuickHelpFlags::CtrlText ); + return; + } + } + + DockingWindow::RequestHelp( rHEvt ); +} + +bool ToolBox::EventNotify( NotifyEvent& rNEvt ) +{ + if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) + { + KeyEvent aKEvt = *rNEvt.GetKeyEvent(); + vcl::KeyCode aKeyCode = aKEvt.GetKeyCode(); + sal_uInt16 nKeyCode = aKeyCode.GetCode(); + switch( nKeyCode ) + { + case KEY_TAB: + { + // internal TAB cycling only if parent is not a dialog or if we are the only child + // otherwise the dialog control will take over + vcl::Window *pParent = ImplGetParent(); + bool bOldSchoolContainer = + ((pParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL && + pParent->GetChildCount() != 1); + bool bNoTabCycling = bOldSchoolContainer || isContainerWindow(pParent); + + if( bNoTabCycling ) + return DockingWindow::EventNotify( rNEvt ); + else if( ImplChangeHighlightUpDn( aKeyCode.IsShift() , bNoTabCycling ) ) + return true; + else + return DockingWindow::EventNotify( rNEvt ); + } + default: + break; + } + } + else if( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS ) + { + if( rNEvt.GetWindow() == this ) + { + // the toolbar itself got the focus + if( mnLastFocusItemId != ToolBoxItemId(0) || mpData->mbMenubuttonWasLastSelected ) + { + // restore last item + if( mpData->mbMenubuttonWasLastSelected ) + { + ImplChangeHighlight( nullptr ); + mpData->mbMenubuttonSelected = true; + InvalidateMenuButton(); + } + else + { + ImplChangeHighlight( ImplGetItem( mnLastFocusItemId ) ); + mnLastFocusItemId = ToolBoxItemId(0); + } + } + else if( (GetGetFocusFlags() & (GetFocusFlags::Backward|GetFocusFlags::Tab) ) == (GetFocusFlags::Backward|GetFocusFlags::Tab)) + // Shift-TAB was pressed in the parent + ImplChangeHighlightUpDn( false ); + else + ImplChangeHighlightUpDn( true ); + + mnLastFocusItemId = ToolBoxItemId(0); + + return true; + } + else + { + // a child window got the focus so update current item to + // allow for proper lose focus handling in keyboard navigation + for (auto const& item : mpData->m_aItems) + { + if ( item.mbVisible ) + { + if ( item.mpWindow && item.mpWindow->ImplIsWindowOrChild( rNEvt.GetWindow() ) ) + { + mnHighItemId = item.mnId; + break; + } + } + } + return DockingWindow::EventNotify( rNEvt ); + } + } + else if( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS ) + { + // deselect + ImplHideFocus(); + mpData->mbMenubuttonWasLastSelected = false; + mnHighItemId = ToolBoxItemId(0); + mnCurPos = ITEM_NOTFOUND; + } + + return DockingWindow::EventNotify( rNEvt ); +} + +void ToolBox::Command( const CommandEvent& rCEvt ) +{ + if ( rCEvt.GetCommand() == CommandEventId::Wheel ) + { + if ( (mnCurLine > 1) || (mnCurLine+mnVisLines-1 < mnCurLines) ) + { + const CommandWheelData* pData = rCEvt.GetWheelData(); + if ( pData->GetMode() == CommandWheelMode::SCROLL ) + { + if ( (mnCurLine > 1) && (pData->GetDelta() > 0) ) + ShowLine( false ); + else if ( (mnCurLine+mnVisLines-1 < mnCurLines) && (pData->GetDelta() < 0) ) + ShowLine( true ); + InvalidateSpin(); + return; + } + } + } + else if ( rCEvt.GetCommand() == CommandEventId::ContextMenu ) + { + ExecuteCustomMenu( tools::Rectangle( rCEvt.GetMousePosPixel(), rCEvt.GetMousePosPixel() ) ); + return; + } + + DockingWindow::Command( rCEvt ); +} + +void ToolBox::StateChanged( StateChangedType nType ) +{ + DockingWindow::StateChanged( nType ); + + if ( nType == StateChangedType::InitShow ) + ImplFormat(); + else if ( nType == StateChangedType::Enable ) + ImplUpdateItem(); + else if ( nType == StateChangedType::UpdateMode ) + { + if ( IsUpdateMode() ) + Invalidate(); + } + else if ( (nType == StateChangedType::Zoom) || + (nType == StateChangedType::ControlFont) ) + { + mbCalc = true; + mbFormat = true; + ImplInitSettings( true, false, false ); + Invalidate(); + } + else if ( nType == StateChangedType::ControlForeground ) + { + ImplInitSettings( false, true, false ); + Invalidate(); + } + else if ( nType == StateChangedType::ControlBackground ) + { + ImplInitSettings( false, false, true ); // font, foreground, background + Invalidate(); + } + + maStateChangedHandler.Call( &nType ); +} + +void ToolBox::DataChanged( const DataChangedEvent& rDCEvt ) +{ + DockingWindow::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::DISPLAY) || + (rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) ) + { + mbCalc = true; + mbFormat = true; + ImplInitSettings( true, true, true ); + Invalidate(); + } + + maDataChangedHandler.Call( &rDCEvt ); +} + +void ToolBox::statusChanged( const css::frame::FeatureStateEvent& Event ) +{ + // Update image mirroring/rotation + if ( Event.FeatureURL.Complete != ".uno:ImageOrientation" ) + return; + + SfxImageItem aItem( 1 ); + aItem.PutValue( Event.State, 0 ); + + mbImagesMirrored = aItem.IsMirrored(); + mnImagesRotationAngle = aItem.GetRotation(); + + // update image orientation + OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(mpStatusListener->getFrame())); + for (auto const& item : mpData->m_aItems) + { + if (vcl::CommandInfoProvider::IsMirrored(item.maCommandStr, aModuleName)) + SetItemImageMirrorMode(item.mnId, mbImagesMirrored); + if (vcl::CommandInfoProvider::IsRotated(item.maCommandStr, aModuleName)) + SetItemImageAngle(item.mnId, mnImagesRotationAngle); + } +} + +void ToolBox::SetStyle(WinBits nNewStyle) +{ + mnWinStyle = nNewStyle; + if (!ImplIsFloatingMode()) + { + bool bOldScroll = mbScroll; + mbScroll = (mnWinStyle & WB_SCROLL) != 0; + if (mbScroll != bOldScroll) + { + mbFormat = true; + ImplFormat(); + } + } +} + +void ToolBox::ToggleFloatingMode() +{ + DockingWindow::ToggleFloatingMode(); + + if (!mpData) + return; + + bool bOldHorz = mbHorz; + + if ( ImplIsFloatingMode() ) + { + mbHorz = true; + meAlign = WindowAlign::Top; + mbScroll = true; + + if( bOldHorz != mbHorz ) + mbCalc = true; // orientation was changed ! + + ImplSetMinMaxFloatSize(); + SetOutputSizePixel( ImplCalcFloatSize( mnFloatLines ) ); + } + else + { + mbScroll = (mnWinStyle & WB_SCROLL) != 0; + if ( (meAlign == WindowAlign::Top) || (meAlign == WindowAlign::Bottom) ) + mbHorz = true; + else + mbHorz = false; + + // set focus back to document + ImplGetFrameWindow()->GetWindow( GetWindowType::Client )->GrabFocus(); + } + + if( bOldHorz != mbHorz ) + { + // if orientation changes, the toolbox has to be initialized again + // to update the direction of the gradient + mbCalc = true; + ImplInitSettings( true, true, true ); + } + + mbFormat = true; + ImplFormat(); +} + +void ToolBox::StartDocking() +{ + meDockAlign = meAlign; + mnDockLines = mnLines; + mbLastFloatMode = ImplIsFloatingMode(); + DockingWindow::StartDocking(); +} + +bool ToolBox::Docking( const Point& rPos, tools::Rectangle& rRect ) +{ + // do nothing during dragging, it was calculated before + if ( mbDragging ) + return false; + + bool bFloatMode = false; + + DockingWindow::Docking( rPos, rRect ); + + // if the mouse is outside the area, it can only become a floating window + tools::Rectangle aDockingRect( rRect ); + if ( !ImplIsFloatingMode() ) + { + // don't use tracking rectangle for alignment check, because it will be too large + // to get a floating mode as result - switch to floating size + // so the calculation only depends on the position of the rectangle, not the current + // docking state of the window + ImplToolItems::size_type nTemp = 0; + aDockingRect.SetSize( ImplCalcFloatSize( nTemp ) ); + + // in this mode docking is never done by keyboard, so it's OK to use the mouse position + aDockingRect.SetPos( ImplGetFrameWindow()->GetPointerPosPixel() ); + } + + bFloatMode = true; + + meDockAlign = meAlign; + if ( !mbLastFloatMode ) + { + ImplToolItems::size_type nTemp = 0; + aDockingRect.SetSize( ImplCalcFloatSize( nTemp ) ); + } + + rRect = aDockingRect; + mbLastFloatMode = bFloatMode; + + return bFloatMode; +} + +void ToolBox::EndDocking( const tools::Rectangle& rRect, bool bFloatMode ) +{ + if ( !IsDockingCanceled() ) + { + if ( mnLines != mnDockLines ) + SetLineCount( mnDockLines ); + if ( meAlign != meDockAlign ) + SetAlign( meDockAlign ); + } + if ( bFloatMode || (bFloatMode != ImplIsFloatingMode()) ) + DockingWindow::EndDocking( rRect, bFloatMode ); +} + +void ToolBox::Resizing( Size& rSize ) +{ + ImplToolItems::size_type nCalcLines; + ImplToolItems::size_type nTemp; + + // calculate all floating sizes + ImplCalcFloatSizes(); + + if ( !mnLastResizeDY ) + mnLastResizeDY = mnDY; + + // is vertical resizing needed + if ( (mnLastResizeDY != rSize.Height()) && (mnDY != rSize.Height()) ) + { + nCalcLines = ImplCalcLines( rSize.Height() ); + if ( nCalcLines < 1 ) + nCalcLines = 1; + rSize = ImplCalcFloatSize( nCalcLines ); + } + else + { + nCalcLines = 1; + nTemp = nCalcLines; + Size aTempSize = ImplCalcFloatSize( nTemp ); + while ( (aTempSize.Width() > rSize.Width()) && + (nCalcLines <= maFloatSizes[0].mnLines) ) + { + nCalcLines++; + nTemp = nCalcLines; + aTempSize = ImplCalcFloatSize( nTemp ); + } + rSize = aTempSize; + } + + mnLastResizeDY = rSize.Height(); +} + +Size ToolBox::GetOptimalSize() const +{ + // If we have any expandable entries, then force them to their + // optimal sizes, then reset them afterwards + std::map<vcl::Window*, Size> aExpandables; + for (const ImplToolItem & rItem : mpData->m_aItems) + { + if (rItem.mbExpand) + { + vcl::Window *pWindow = rItem.mpWindow; + SAL_INFO_IF(!pWindow, "vcl.layout", "only tabitems with window supported at the moment"); + if (!pWindow) + continue; + Size aWinSize(pWindow->GetSizePixel()); + aExpandables[pWindow] = aWinSize; + Size aPrefSize(pWindow->get_preferred_size()); + aWinSize.setWidth( aPrefSize.Width() ); + pWindow->SetSizePixel(aWinSize); + } + } + + Size aSize(const_cast<ToolBox *>(this)->ImplCalcSize( mnLines )); + + for (auto const& [pWindow, aWinSize] : aExpandables) + pWindow->SetSizePixel(aWinSize); + + return aSize; +} + +Size ToolBox::CalcWindowSizePixel( ImplToolItems::size_type nCalcLines ) +{ + return ImplCalcSize( nCalcLines ); +} + +Size ToolBox::CalcWindowSizePixel( ImplToolItems::size_type nCalcLines, WindowAlign eAlign ) +{ + return ImplCalcSize( nCalcLines, + (eAlign == WindowAlign::Top || eAlign == WindowAlign::Bottom) ? TB_CALCMODE_HORZ : TB_CALCMODE_VERT ); +} + +ToolBox::ImplToolItems::size_type ToolBox::ImplCountLineBreaks() const +{ + ImplToolItems::size_type nLines = 0; + + for (auto const& item : mpData->m_aItems) + { + if( item.meType == ToolBoxItemType::BREAK ) + ++nLines; + } + return nLines; +} + +Size ToolBox::CalcPopupWindowSizePixel() +{ + // count number of breaks and calc corresponding floating window size + ImplToolItems::size_type nLines = ImplCountLineBreaks(); + + if( nLines ) + ++nLines; // add the first line + else + { + // no breaks found: use quadratic layout + nLines = static_cast<ImplToolItems::size_type>(ceil( sqrt( static_cast<double>(GetItemCount()) ) )); + } + + bool bPopup = mpData->mbAssumePopupMode; + mpData->mbAssumePopupMode = true; + + Size aSize = CalcFloatingWindowSizePixel( nLines ); + + mpData->mbAssumePopupMode = bPopup; + return aSize; +} + +Size ToolBox::CalcFloatingWindowSizePixel() +{ + ImplToolItems::size_type nLines = ImplCountLineBreaks(); + ++nLines; // add the first line + return CalcFloatingWindowSizePixel( nLines ); +} + +Size ToolBox::CalcFloatingWindowSizePixel( ImplToolItems::size_type nCalcLines ) +{ + bool bFloat = mpData->mbAssumeFloating; + bool bDocking = mpData->mbAssumeDocked; + + // simulate floating mode and force reformat before calculating + mpData->mbAssumeFloating = true; + mpData->mbAssumeDocked = false; + + Size aSize = ImplCalcFloatSize( nCalcLines ); + + mbFormat = true; + mpData->mbAssumeFloating = bFloat; + mpData->mbAssumeDocked = bDocking; + + return aSize; +} + +Size ToolBox::CalcMinimumWindowSizePixel() +{ + if( ImplIsFloatingMode() ) + return ImplCalcSize( mnFloatLines ); + else + { + // create dummy toolbox for measurements + VclPtrInstance< ToolBox > pToolBox( GetParent(), GetStyle() ); + + // copy until first useful item + for (auto const& item : mpData->m_aItems) + { + pToolBox->CopyItem( *this, item.mnId ); + if( (item.meType == ToolBoxItemType::BUTTON) && + item.mbVisible && !ImplIsFixedControl( &item ) ) + break; + } + + // add to docking manager if required to obtain a drag area + // (which is accounted for in calcwindowsizepixel) + if( ImplGetDockingManager()->GetDockingWindowWrapper( this ) ) + ImplGetDockingManager()->AddWindow( pToolBox ); + + // account for menu + if( IsMenuEnabled() ) + pToolBox->SetMenuType( GetMenuType() ); + + pToolBox->SetAlign( GetAlign() ); + Size aSize = pToolBox->CalcWindowSizePixel( 1 ); + + ImplGetDockingManager()->RemoveWindow( pToolBox ); + pToolBox->Clear(); + + pToolBox.disposeAndClear(); + + return aSize; + } +} + +void ToolBox::EnableCustomize( bool bEnable ) +{ + mbCustomize = bEnable; +} + +void ToolBox::LoseFocus() +{ + ImplChangeHighlight( nullptr, true ); + + DockingWindow::LoseFocus(); +} + +// performs the action associated with an item, ie simulates clicking the item +void ToolBox::TriggerItem( ToolBoxItemId nItemId ) +{ + mnHighItemId = nItemId; + vcl::KeyCode aKeyCode( 0, 0 ); + ImplActivateItem( aKeyCode ); +} + +// calls the button's action handler +// returns true if action was called +bool ToolBox::ImplActivateItem( vcl::KeyCode aKeyCode ) +{ + bool bRet = true; + if( mnHighItemId ) + { + ImplToolItem *pToolItem = ImplGetItem( mnHighItemId ); + + // #107712#, activate can also be called for disabled entries + if( pToolItem && !pToolItem->mbEnabled ) + return true; + + if( pToolItem && pToolItem->mpWindow && HasFocus() ) + { + ImplHideFocus(); + mbChangingHighlight = true; // avoid focus change due to loss of focus + pToolItem->mpWindow->ImplControlFocus( GetFocusFlags::Tab ); + mbChangingHighlight = false; + } + else + { + mnDownItemId = mnCurItemId = mnHighItemId; + if (pToolItem && (pToolItem->mnBits & ToolBoxItemBits::AUTOCHECK)) + { + if ( pToolItem->mnBits & ToolBoxItemBits::RADIOCHECK ) + { + if ( pToolItem->meState != TRISTATE_TRUE ) + SetItemState( pToolItem->mnId, TRISTATE_TRUE ); + } + else + { + if ( pToolItem->meState != TRISTATE_TRUE ) + pToolItem->meState = TRISTATE_TRUE; + else + pToolItem->meState = TRISTATE_FALSE; + } + } + mnMouseModifier = aKeyCode.GetModifier(); + mbIsKeyEvent = true; + Activate(); + Click(); + + // #107776# we might be destroyed in the selecthandler + VclPtr<vcl::Window> xWindow = this; + Select(); + if ( xWindow->isDisposed() ) + return bRet; + + Deactivate(); + mbIsKeyEvent = false; + mnMouseModifier = 0; + } + } + else + bRet = false; + return bRet; +} + +static bool ImplCloseLastPopup( vcl::Window const *pParent ) +{ + // close last popup toolbox (see also: + // ImplHandleMouseFloatMode(...) in winproc.cxx ) + + if (ImplGetSVData()->mpWinData->mpFirstFloat) + { + FloatingWindow* pLastLevelFloat = ImplGetSVData()->mpWinData->mpFirstFloat->ImplFindLastLevelFloat(); + // only close the floater if it is not our direct parent, which would kill ourself + if( pLastLevelFloat && pLastLevelFloat != pParent ) + { + pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll ); + return true; + } + } + return false; +} + +// opens a drop down toolbox item +// returns true if item was opened +bool ToolBox::ImplOpenItem( vcl::KeyCode aKeyCode ) +{ + sal_uInt16 nCode = aKeyCode.GetCode(); + bool bRet = true; + + // arrow keys should work only in the opposite direction of alignment (to not break cursor travelling) + if ( ((nCode == KEY_LEFT || nCode == KEY_RIGHT) && IsHorizontal()) + || ((nCode == KEY_UP || nCode == KEY_DOWN) && !IsHorizontal()) ) + return false; + + if( mpData->mbMenubuttonSelected ) + { + if( ImplCloseLastPopup( GetParent() ) ) + return bRet; + mbIsKeyEvent = true; + if ( maMenuButtonHdl.IsSet() ) + maMenuButtonHdl.Call( this ); + else + ExecuteCustomMenu( mpData->maMenubuttonItem.maRect ); + mpData->mbMenubuttonWasLastSelected = true; + mbIsKeyEvent = false; + } + else if( mnHighItemId && ImplGetItem( mnHighItemId ) && + (ImplGetItem( mnHighItemId )->mnBits & ToolBoxItemBits::DROPDOWN) ) + { + mnDownItemId = mnCurItemId = mnHighItemId; + mnCurPos = GetItemPos( mnCurItemId ); + mnLastFocusItemId = mnCurItemId; // save item id for possible later focus restore + mnMouseModifier = aKeyCode.GetModifier(); + mbIsKeyEvent = true; + Activate(); + + mpData->mbDropDownByKeyboard = true; + mpData->maDropdownClickHdl.Call( this ); + + mbIsKeyEvent = false; + mnMouseModifier = 0; + } + else + bRet = false; + + return bRet; +} + +void ToolBox::KeyInput( const KeyEvent& rKEvt ) +{ + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + sal_uInt16 nCode = aKeyCode.GetCode(); + + vcl::Window *pParent = ImplGetParent(); + bool bOldSchoolContainer = ((pParent->GetStyle() & (WB_DIALOGCONTROL | WB_NODIALOGCONTROL)) == WB_DIALOGCONTROL); + bool bParentIsContainer = bOldSchoolContainer || isContainerWindow(pParent); + + bool bForwardKey = false; + bool bGrabFocusToDocument = false; + + // #107776# we might be destroyed in the keyhandler + VclPtr<vcl::Window> xWindow = this; + + switch ( nCode ) + { + case KEY_UP: + { + // Ctrl-Cursor activates next toolbox, indicated by a blue arrow pointing to the left/up + if( aKeyCode.GetModifier() ) // allow only pure cursor keys + break; + if( !IsHorizontal() ) + ImplChangeHighlightUpDn( true ); + else + ImplOpenItem( aKeyCode ); + } + break; + case KEY_LEFT: + { + if( aKeyCode.GetModifier() ) // allow only pure cursor keys + break; + if( IsHorizontal() ) + ImplChangeHighlightUpDn( true ); + else + ImplOpenItem( aKeyCode ); + } + break; + case KEY_DOWN: + { + if( aKeyCode.GetModifier() ) // allow only pure cursor keys + break; + if( !IsHorizontal() ) + ImplChangeHighlightUpDn( false ); + else + ImplOpenItem( aKeyCode ); + } + break; + case KEY_RIGHT: + { + if( aKeyCode.GetModifier() ) // allow only pure cursor keys + break; + if( IsHorizontal() ) + ImplChangeHighlightUpDn( false ); + else + ImplOpenItem( aKeyCode ); + } + break; + case KEY_PAGEUP: + if ( mnCurLine > 1 ) + { + if( mnCurLine > mnVisLines ) + mnCurLine = mnCurLine - mnVisLines; + else + mnCurLine = 1; + mbFormat = true; + ImplFormat(); + InvalidateSpin(); + ImplChangeHighlight( ImplGetFirstValidItem( mnCurLine ) ); + } + break; + case KEY_PAGEDOWN: + if ( mnCurLine+mnVisLines-1 < mnCurLines ) + { + if( mnCurLine + 2*mnVisLines-1 < mnCurLines ) + mnCurLine = mnCurLine + mnVisLines; + else + mnCurLine = mnCurLines; + mbFormat = true; + ImplFormat(); + InvalidateSpin(); + ImplChangeHighlight( ImplGetFirstValidItem( mnCurLine ) ); + } + break; + case KEY_END: + { + ImplChangeHighlight( nullptr ); + ImplChangeHighlightUpDn( false ); + } + break; + case KEY_HOME: + { + ImplChangeHighlight( nullptr ); + ImplChangeHighlightUpDn( true ); + } + break; + case KEY_ESCAPE: + { + if( !ImplIsFloatingMode() && bParentIsContainer ) + DockingWindow::KeyInput( rKEvt ); + else + { + // send focus to document pane + vcl::Window *pWin = this; + while( pWin ) + { + if( !pWin->GetParent() ) + { + pWin->ImplGetFrameWindow()->GetWindow( GetWindowType::Client )->GrabFocus(); + break; + } + pWin = pWin->GetParent(); + } + } + } + break; + case KEY_RETURN: + { + // #107712#, disabled entries are selectable now + // leave toolbox and move focus to document + if( mnHighItemId ) + { + ImplToolItem *pItem = ImplGetItem(mnHighItemId); + if (!pItem || !pItem->mbEnabled) + { + bGrabFocusToDocument = true; + } + } + if( !bGrabFocusToDocument ) + bForwardKey = !ImplActivateItem( aKeyCode ); + } + break; + case KEY_SPACE: + { + ImplOpenItem( aKeyCode ); + } + break; + default: + { + sal_uInt16 aKeyGroup = aKeyCode.GetGroup(); + ImplToolItem *pItem = nullptr; + if( mnHighItemId ) + pItem = ImplGetItem( mnHighItemId ); + // #i13931# forward alphanum keyinput into embedded control + if( (aKeyGroup == KEYGROUP_NUM || aKeyGroup == KEYGROUP_ALPHA ) && pItem && pItem->mpWindow && pItem->mbEnabled ) + { + vcl::Window *pFocusWindow = Application::GetFocusWindow(); + ImplHideFocus(); + mbChangingHighlight = true; // avoid focus change due to loss of focus + pItem->mpWindow->ImplControlFocus( GetFocusFlags::Tab ); + mbChangingHighlight = false; + if( pFocusWindow != Application::GetFocusWindow() ) + Application::GetFocusWindow()->KeyInput( rKEvt ); + } + else + { + // do nothing to avoid key presses going into the document + // while the toolbox has the focus + // just forward function and special keys and combinations with Alt-key + if( aKeyGroup == KEYGROUP_FKEYS || aKeyGroup == KEYGROUP_MISC || aKeyCode.IsMod2() ) + bForwardKey = true; + } + } + } + + if ( xWindow->isDisposed() ) + return; + + // #107251# move focus away if this toolbox was disabled during keyinput + if (HasFocus() && mpData->mbKeyInputDisabled && bParentIsContainer) + { + vcl::Window *pFocusControl = pParent->ImplGetDlgWindow( 0, GetDlgWindowType::First ); + if ( pFocusControl && pFocusControl != this ) + pFocusControl->ImplControlFocus( GetFocusFlags::Init ); + } + + // #107712#, leave toolbox + if( bGrabFocusToDocument ) + { + GrabFocusToDocument(); + return; + } + + if( bForwardKey ) + DockingWindow::KeyInput( rKEvt ); +} + +// returns the current toolbox line of the item +ToolBox::ImplToolItems::size_type ToolBox::ImplGetItemLine( ImplToolItem const * pCurrentItem ) +{ + ImplToolItems::size_type nLine = 1; + for (auto const& item : mpData->m_aItems) + { + if ( item.mbBreak ) + ++nLine; + if( &item == pCurrentItem) + break; + } + return nLine; +} + +// returns the first displayable item in the given line +ImplToolItem* ToolBox::ImplGetFirstValidItem( ImplToolItems::size_type nLine ) +{ + if( !nLine || nLine > mnCurLines ) + return nullptr; + + nLine--; + + ImplToolItems::iterator it = mpData->m_aItems.begin(); + while( it != mpData->m_aItems.end() ) + { + // find correct line + if ( it->mbBreak ) + nLine--; + if( !nLine ) + { + // find first useful item + while( it != mpData->m_aItems.end() && ((it->meType != ToolBoxItemType::BUTTON) || + /*!it->mbEnabled ||*/ !it->mbVisible || ImplIsFixedControl( &(*it) )) ) + { + ++it; + if( it == mpData->m_aItems.end() || it->mbBreak ) + return nullptr; // no valid items in this line + } + return &(*it); + } + ++it; + } + + return (it == mpData->m_aItems.end()) ? nullptr : &(*it); +} + +ToolBox::ImplToolItems::size_type ToolBox::ImplFindItemPos( const ImplToolItem* pItem, const ImplToolItems& rList ) +{ + if( pItem ) + { + for( ImplToolItems::size_type nPos = 0; nPos < rList.size(); ++nPos ) + if( &rList[ nPos ] == pItem ) + return nPos; + } + return ITEM_NOTFOUND; +} + +void ToolBox::ChangeHighlight( ImplToolItems::size_type nPos ) +{ + if ( nPos < GetItemCount() ) { + ImplGrabFocus( GetFocusFlags::NONE ); + ImplChangeHighlight ( ImplGetItem ( GetItemId ( nPos ) ) ); + } +} + +void ToolBox::ImplChangeHighlight( ImplToolItem const * pItem, bool bNoGrabFocus ) +{ + // avoid recursion due to focus change + if( mbChangingHighlight ) + return; + + mbChangingHighlight = true; + + ImplToolItem* pOldItem = nullptr; + + if ( mnHighItemId ) + { + ImplHideFocus(); + ImplToolItems::size_type nPos = GetItemPos( mnHighItemId ); + pOldItem = ImplGetItem( mnHighItemId ); + // #i89962# ImplDrawItem can cause Invalidate/Update + // which will in turn ImplShowFocus again + // set mnHighItemId to 0 already to prevent this hen/egg problem + mnHighItemId = ToolBoxItemId(0); + InvalidateItem(nPos); + CallEventListeners( VclEventId::ToolboxHighlightOff, reinterpret_cast< void* >( nPos ) ); + } + + if( !bNoGrabFocus && pItem != pOldItem && pOldItem && pOldItem->mpWindow ) + { + // move focus into toolbox + GrabFocus(); + } + + if( pItem ) + { + ImplToolItems::size_type aPos = ToolBox::ImplFindItemPos( pItem, mpData->m_aItems ); + if( aPos != ITEM_NOTFOUND) + { + // check for line breaks + ImplToolItems::size_type nLine = ImplGetItemLine( pItem ); + + if( nLine >= mnCurLine + mnVisLines ) + { + mnCurLine = nLine - mnVisLines + 1; + mbFormat = true; + } + else if ( nLine < mnCurLine ) + { + mnCurLine = nLine; + mbFormat = true; + } + + if( mbFormat ) + { + ImplFormat(); + } + + mnHighItemId = pItem->mnId; + InvalidateItem(aPos); + + ImplShowFocus(); + + if( pItem->mpWindow ) + pItem->mpWindow->GrabFocus(); + if( pItem != pOldItem ) + CallEventListeners( VclEventId::ToolboxHighlight ); + } + } + else + { + ImplHideFocus(); + mnHighItemId = ToolBoxItemId(0); + mnCurPos = ITEM_NOTFOUND; + } + + mbChangingHighlight = false; +} + +// check for keyboard accessible items +static bool ImplIsValidItem( const ImplToolItem* pItem, bool bNotClipped ) +{ + bool bValid = (pItem && pItem->meType == ToolBoxItemType::BUTTON && pItem->mbVisible && !ImplIsFixedControl( pItem ) + && pItem->mbEnabled); + if( bValid && bNotClipped && pItem->IsClipped() ) + bValid = false; + return bValid; +} + +bool ToolBox::ImplChangeHighlightUpDn( bool bUp, bool bNoCycle ) +{ + ImplToolItem* pToolItem = ImplGetItem( mnHighItemId ); + + if( !pToolItem || !mnHighItemId ) + { + // menubutton highlighted ? + if( mpData->mbMenubuttonSelected ) + { + mpData->mbMenubuttonSelected = false; + if( bUp ) + { + // select last valid non-clipped item + ImplToolItem* pItem = nullptr; + auto it = std::find_if(mpData->m_aItems.rbegin(), mpData->m_aItems.rend(), + [](const ImplToolItem& rItem) { return ImplIsValidItem( &rItem, true ); }); + if( it != mpData->m_aItems.rend() ) + pItem = &(*it); + + InvalidateMenuButton(); + ImplChangeHighlight( pItem ); + } + else + { + // select first valid non-clipped item + ImplToolItems::iterator it = std::find_if(mpData->m_aItems.begin(), mpData->m_aItems.end(), + [](const ImplToolItem& rItem) { return ImplIsValidItem( &rItem, true ); }); + if( it != mpData->m_aItems.end() ) + { + InvalidateMenuButton(); + ImplChangeHighlight( &(*it) ); + } + } + return true; + } + + if( bUp ) + { + // Select first valid item + ImplToolItems::iterator it = std::find_if(mpData->m_aItems.begin(), mpData->m_aItems.end(), + [](const ImplToolItem& rItem) { return ImplIsValidItem( &rItem, false ); }); + + // select the menu button if a clipped item would be selected + if( (it != mpData->m_aItems.end() && &(*it) == ImplGetFirstClippedItem()) && IsMenuEnabled() ) + { + ImplChangeHighlight( nullptr ); + mpData->mbMenubuttonSelected = true; + InvalidateMenuButton(); + } + else + ImplChangeHighlight( (it != mpData->m_aItems.end()) ? &(*it) : nullptr ); + return true; + } + else + { + // Select last valid item + + // docked toolbars have the menubutton as last item - if this button is enabled + if( ImplHasClippedItems() && IsMenuEnabled() && !ImplIsFloatingMode() ) + { + ImplChangeHighlight( nullptr ); + mpData->mbMenubuttonSelected = true; + InvalidateMenuButton(); + } + else + { + ImplToolItem* pItem = nullptr; + auto it = std::find_if(mpData->m_aItems.rbegin(), mpData->m_aItems.rend(), + [](const ImplToolItem& rItem) { return ImplIsValidItem( &rItem, false ); }); + if( it != mpData->m_aItems.rend() ) + pItem = &(*it); + + ImplChangeHighlight( pItem ); + } + return true; + } + } + + assert(pToolItem); + + ImplToolItems::size_type pos = ToolBox::ImplFindItemPos( pToolItem, mpData->m_aItems ); + ImplToolItems::size_type nCount = mpData->m_aItems.size(); + + ImplToolItems::size_type i=0; + do + { + if( bUp ) + { + if( !pos-- ) + { + if( bNoCycle ) + return false; + + // highlight the menu button if it is the last item + if( ImplHasClippedItems() && IsMenuEnabled() && !ImplIsFloatingMode() ) + { + ImplChangeHighlight( nullptr ); + mpData->mbMenubuttonSelected = true; + InvalidateMenuButton(); + return true; + } + else + pos = nCount-1; + } + } + else + { + if( ++pos >= nCount ) + { + if( bNoCycle ) + return false; + + // highlight the menu button if it is the last item + if( ImplHasClippedItems() && IsMenuEnabled() && !ImplIsFloatingMode() ) + { + ImplChangeHighlight( nullptr ); + mpData->mbMenubuttonSelected = true; + InvalidateMenuButton(); + return true; + } + else + pos = 0; + } + } + + pToolItem = &mpData->m_aItems[pos]; + + if ( ImplIsValidItem( pToolItem, false ) ) + break; + + } while( ++i < nCount); + + if( pToolItem->IsClipped() && IsMenuEnabled() ) + { + // select the menu button if a clipped item would be selected + ImplChangeHighlight( nullptr ); + mpData->mbMenubuttonSelected = true; + InvalidateMenuButton(); + } + else if( i != nCount ) + ImplChangeHighlight( pToolItem ); + else + return false; + + return true; +} + +void ToolBox::ImplShowFocus() +{ + if( mnHighItemId && HasFocus() ) + { + ImplToolItem* pItem = ImplGetItem( mnHighItemId ); + if (pItem && pItem->mpWindow && !pItem->mpWindow->isDisposed()) + { + vcl::Window *pWin = pItem->mpWindow->ImplGetWindowImpl()->mpBorderWindow ? pItem->mpWindow->ImplGetWindowImpl()->mpBorderWindow.get() : pItem->mpWindow.get(); + pWin->ImplGetWindowImpl()->mbDrawSelectionBackground = true; + pWin->Invalidate(); + } + } +} + +void ToolBox::ImplHideFocus() +{ + if( mnHighItemId ) + { + mpData->mbMenubuttonWasLastSelected = false; + ImplToolItem* pItem = ImplGetItem( mnHighItemId ); + if( pItem && pItem->mpWindow ) + { + vcl::Window *pWin = pItem->mpWindow->ImplGetWindowImpl()->mpBorderWindow ? pItem->mpWindow->ImplGetWindowImpl()->mpBorderWindow.get() : pItem->mpWindow.get(); + pWin->ImplGetWindowImpl()->mbDrawSelectionBackground = false; + pWin->Invalidate(); + } + } + + if ( mpData && mpData->mbMenubuttonSelected ) + { + mpData->mbMenubuttonWasLastSelected = true; + // remove highlight from menubutton + mpData->mbMenubuttonSelected = false; + InvalidateMenuButton(); + } +} + +void ToolBox::SetToolbarLayoutMode( ToolBoxLayoutMode eLayout ) +{ + if ( meLayoutMode != eLayout ) + meLayoutMode = eLayout; +} + +void ToolBox::SetToolBoxTextPosition( ToolBoxTextPosition ePosition ) +{ + meTextPosition = ePosition; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/toolbox2.cxx b/vcl/source/window/toolbox2.cxx new file mode 100644 index 000000000..5a1112da5 --- /dev/null +++ b/vcl/source/window/toolbox2.cxx @@ -0,0 +1,1781 @@ +/* -*- 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 <vcl/uitest/logger.hxx> +#include <sal/log.hxx> + +#include <comphelper/base64.hxx> +#include <comphelper/processfactory.hxx> +#include <boost/property_tree/ptree.hpp> + +#include <vcl/cvtgrf.hxx> +#include <vcl/svapp.hxx> +#include <vcl/idle.hxx> +#include <vcl/bitmap.hxx> +#include <vcl/toolkit/floatwin.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/mnemonic.hxx> +#include <vcl/menu.hxx> +#include <vcl/settings.hxx> +#include <vcl/IconThemeInfo.hxx> +#include <vcl/commandinfoprovider.hxx> + +#include <svdata.hxx> +#include <brdwin.hxx> +#include <toolbox.h> + +#include <unotools/confignode.hxx> +#include <tools/json_writer.hxx> + +#include <vcl/uitest/uiobject.hxx> + +#include "impldockingwrapper.hxx" + +using namespace vcl; + +#define TB_SEP_SIZE 8 // Separator size + + +ImplToolBoxPrivateData::ImplToolBoxPrivateData() +{ + meButtonSize = ToolBoxButtonSize::DontCare; + mpMenu = VclPtr<PopupMenu>::Create(); + + maMenuType = ToolBoxMenuType::NONE; + maMenubuttonItem.maItemSize = Size( TB_MENUBUTTON_SIZE+TB_MENUBUTTON_OFFSET, TB_MENUBUTTON_SIZE+TB_MENUBUTTON_OFFSET ); + maMenubuttonItem.meState = TRISTATE_FALSE; + mnMenuButtonWidth = TB_MENUBUTTON_SIZE; + + mbIsLocked = false; + mbNativeButtons = false; + mbIsPaintLocked = false; + mbAssumeDocked = false; + mbAssumePopupMode = false; + mbAssumeFloating = false; + mbKeyInputDisabled = false; + mbMenubuttonSelected = false; + mbMenubuttonWasLastSelected = false; + mbWillUsePopupMode = false; + mbDropDownByKeyboard = false; +} + +ImplToolBoxPrivateData::~ImplToolBoxPrivateData() +{ + m_pLayoutData.reset(); + mpMenu.disposeAndClear(); +} + +void ImplToolItem::init(ToolBoxItemId nItemId, ToolBoxItemBits nItemBits, + bool bEmptyBtn) +{ + mnId = nItemId; + mpWindow = nullptr; + mbNonInteractiveWindow = false; + mpUserData = nullptr; + meType = ToolBoxItemType::BUTTON; + mnBits = nItemBits; + meState = TRISTATE_FALSE; + mbEnabled = true; + mbVisible = true; + mbEmptyBtn = bEmptyBtn; + mbShowWindow = false; + mbBreak = false; + mnSepSize = TB_SEP_SIZE; + mnDropDownArrowWidth = TB_DROPDOWNARROWWIDTH; + mnImageAngle = 0_deg10; + mbMirrorMode = false; + mbVisibleText = false; + mbExpand = false; +} + +ImplToolItem::ImplToolItem() +{ + init(ToolBoxItemId(0), ToolBoxItemBits::NONE, true); +} + +ImplToolItem::ImplToolItem( ToolBoxItemId nItemId, const Image& rImage, + ToolBoxItemBits nItemBits ) : + maImage( rImage ) +{ + init(nItemId, nItemBits, false); +} + +ImplToolItem::ImplToolItem( ToolBoxItemId nItemId, const OUString& rText, + ToolBoxItemBits nItemBits ) : + maText( rText ) +{ + init(nItemId, nItemBits, false); +} + +ImplToolItem::ImplToolItem( ToolBoxItemId nItemId, const Image& rImage, + const OUString& rText, ToolBoxItemBits nItemBits ) : + maImage( rImage ), + maText( rText ) +{ + init(nItemId, nItemBits, false); +} + +Size ImplToolItem::GetSize( bool bHorz, bool bCheckMaxWidth, tools::Long maxWidth, const Size& rDefaultSize ) +{ + Size aSize( rDefaultSize ); // the size of 'standard' toolbox items + // non-standard items are eg windows or buttons with text + + if ( (meType == ToolBoxItemType::BUTTON) || (meType == ToolBoxItemType::SPACE) ) + { + aSize = maItemSize; + + if ( mpWindow && bHorz ) + { + // get size of item window and check if it fits + // no windows in vertical toolbars (the default is mbShowWindow=false) + Size aWinSize = mpWindow->GetSizePixel(); + + if (mpWindow->GetStyle() & WB_NOLABEL) + // Window wants no label? Then don't check width, it'll be just + // clipped. + bCheckMaxWidth = false; + + if ( !bCheckMaxWidth || (aWinSize.Width() <= maxWidth) ) + { + aSize.setWidth( aWinSize.Width() ); + aSize.setHeight( aWinSize.Height() ); + mbShowWindow = true; + } + else + { + if ( mbEmptyBtn ) + { + aSize.setWidth( 0 ); + aSize.setHeight( 0 ); + } + } + } + } + else if ( meType == ToolBoxItemType::SEPARATOR ) + { + if ( bHorz ) + { + aSize.setWidth( mnSepSize ); + aSize.setHeight( rDefaultSize.Height() ); + } + else + { + aSize.setWidth( rDefaultSize.Width() ); + aSize.setHeight( mnSepSize ); + } + } + else if ( meType == ToolBoxItemType::BREAK ) + { + aSize.setWidth( 0 ); + aSize.setHeight( 0 ); + } + + return aSize; +} + +void ImplToolItem::DetermineButtonDrawStyle( ButtonType eButtonType, bool& rbImage, bool& rbText ) const +{ + if ( meType != ToolBoxItemType::BUTTON ) + { + // no button -> draw nothing + rbImage = rbText = false; + return; + } + + bool bHasImage; + bool bHasText; + + // check for image and/or text + bHasImage = !!maImage; + bHasText = !maText.isEmpty(); + + // prefer images if symbolonly buttons are drawn + // prefer texts if textonly buttons are drawn + + if ( eButtonType == ButtonType::SYMBOLONLY ) // drawing icons only + { + if( bHasImage || !bHasText ) + { + rbImage = true; + rbText = false; + } + else + { + rbImage = false; + rbText = true; + } + } + else if ( eButtonType == ButtonType::TEXT ) // drawing text only + { + if( bHasText || !bHasImage ) + { + rbImage = false; + rbText = true; + } + else + { + rbImage = true; + rbText = false; + } + } + else // drawing icons and text both + { + rbImage = true; + rbText = true; + } +} + +tools::Rectangle ImplToolItem::GetDropDownRect( bool bHorz ) const +{ + tools::Rectangle aRect; + if( (mnBits & ToolBoxItemBits::DROPDOWN) && !maRect.IsEmpty() ) + { + aRect = maRect; + if( mbVisibleText && !bHorz ) + // item will be rotated -> place dropdown to the bottom + aRect.SetTop( aRect.Bottom() - mnDropDownArrowWidth ); + else + // place dropdown to the right + aRect.SetLeft( aRect.Right() - mnDropDownArrowWidth ); + } + return aRect; +} + +bool ImplToolItem::IsClipped() const +{ + return ( meType == ToolBoxItemType::BUTTON && mbVisible && maRect.IsEmpty() ); +} + +bool ImplToolItem::IsItemHidden() const +{ + return ( meType == ToolBoxItemType::BUTTON && !mbVisible ); +} + +void ToolBox::ImplInvalidate( bool bNewCalc, bool bFullPaint ) +{ + ImplUpdateInputEnable(); + + if ( bNewCalc ) + mbCalc = true; + + if ( bFullPaint ) + { + mbFormat = true; + + // do we need to redraw? + if ( IsReallyVisible() && IsUpdateMode() ) + { + Invalidate( tools::Rectangle( mnLeftBorder, mnTopBorder, + mnDX-mnRightBorder-1, mnDY-mnBottomBorder-1 ) ); + mpIdle->Stop(); + } + } + else + { + if ( !mbFormat ) + { + mbFormat = true; + + // do we need to redraw? + if ( IsReallyVisible() && IsUpdateMode() ) + mpIdle->Start(); + } + } + + // request new layout by layoutmanager + CallEventListeners( VclEventId::ToolboxFormatChanged ); +} + +void ToolBox::ImplUpdateItem( ImplToolItems::size_type nIndex ) +{ + // do we need to redraw? + if ( !(IsReallyVisible() && IsUpdateMode()) ) + return; + + if ( nIndex == ITEM_NOTFOUND ) + { + // #i52217# no immediate draw as this might lead to paint problems + Invalidate( tools::Rectangle( mnLeftBorder, mnTopBorder, mnDX-mnRightBorder-1, mnDY-mnBottomBorder-1 ) ); + } + else + { + if ( !mbFormat ) + { + // #i52217# no immediate draw as this might lead to paint problems + Invalidate( mpData->m_aItems[nIndex].maRect ); + } + else + maPaintRect.Union( mpData->m_aItems[nIndex].maRect ); + } +} + +void ToolBox::Click() +{ + CallEventListeners( VclEventId::ToolboxClick ); + maClickHdl.Call( this ); + UITestLogger::getInstance().logAction( this, VclEventId::ToolboxClick); +} + +void ToolBox::DoubleClick() +{ + CallEventListeners( VclEventId::ToolboxDoubleClick ); + maDoubleClickHdl.Call( this ); +} + +void ToolBox::Activate() +{ + mnActivateCount++; + CallEventListeners( VclEventId::ToolboxActivate ); + maActivateHdl.Call( this ); +} + +void ToolBox::Deactivate() +{ + mnActivateCount--; + CallEventListeners( VclEventId::ToolboxDeactivate ); + maDeactivateHdl.Call( this ); +} + +void ToolBox::Highlight() +{ + CallEventListeners( VclEventId::ToolboxHighlight ); +} + +FactoryFunction ToolBox::GetUITestFactory() const +{ + return ToolBoxUIObject::create; +} + +void ToolBox::Select() +{ + VclPtr<vcl::Window> xWindow = this; + + CallEventListeners( VclEventId::ToolboxSelect ); + maSelectHdl.Call( this ); + + if ( xWindow->isDisposed() ) + return; + + // TODO: GetFloatingWindow in DockingWindow is currently inline, change it to check dockingwrapper + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper && pWrapper->GetFloatingWindow() && static_cast<FloatingWindow*>(pWrapper->GetFloatingWindow())->IsInPopupMode() ) + static_cast<FloatingWindow*>(pWrapper->GetFloatingWindow())->EndPopupMode(); +} + +void ToolBox::InsertItem( ToolBoxItemId nItemId, const Image& rImage, ToolBoxItemBits nBits, ImplToolItems::size_type nPos ) +{ + SAL_WARN_IF( !nItemId, "vcl", "ToolBox::InsertItem(): ItemId == 0" ); + SAL_WARN_IF( GetItemPos( nItemId ) != ITEM_NOTFOUND, "vcl", + "ToolBox::InsertItem(): ItemId already exists" ); + + // create item and add to list + mpData->m_aItems.insert( (nPos < mpData->m_aItems.size()) ? mpData->m_aItems.begin()+nPos : mpData->m_aItems.end(), + ImplToolItem( nItemId, rImage, nBits ) ); + mpData->ImplClearLayoutData(); + + ImplInvalidate( true ); + + // Notify + ImplToolItems::size_type nNewPos = ( nPos == APPEND ) ? ( mpData->m_aItems.size() - 1 ) : nPos; + CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >(nNewPos ) ); +} + +void ToolBox::InsertItem( ToolBoxItemId nItemId, const Image& rImage, const OUString& rText, ToolBoxItemBits nBits, + ImplToolItems::size_type nPos ) +{ + SAL_WARN_IF( !nItemId, "vcl", "ToolBox::InsertItem(): ItemId == 0" ); + SAL_WARN_IF( GetItemPos( nItemId ) != ITEM_NOTFOUND, "vcl", + "ToolBox::InsertItem(): ItemId already exists" ); + + // create item and add to list + mpData->m_aItems.insert( (nPos < mpData->m_aItems.size()) ? mpData->m_aItems.begin()+nPos : mpData->m_aItems.end(), + ImplToolItem( nItemId, rImage, MnemonicGenerator::EraseAllMnemonicChars(rText), nBits ) ); + mpData->ImplClearLayoutData(); + + ImplInvalidate( true ); + + // Notify + ImplToolItems::size_type nNewPos = ( nPos == APPEND ) ? ( mpData->m_aItems.size() - 1 ) : nPos; + CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos ) ); +} + +void ToolBox::InsertItem( ToolBoxItemId nItemId, const OUString& rText, ToolBoxItemBits nBits, ImplToolItems::size_type nPos ) +{ + SAL_WARN_IF( !nItemId, "vcl", "ToolBox::InsertItem(): ItemId == 0" ); + SAL_WARN_IF( GetItemPos( nItemId ) != ITEM_NOTFOUND, "vcl", + "ToolBox::InsertItem(): ItemId already exists" ); + + // create item and add to list + mpData->m_aItems.insert( (nPos < mpData->m_aItems.size()) ? mpData->m_aItems.begin()+nPos : mpData->m_aItems.end(), + ImplToolItem( nItemId, MnemonicGenerator::EraseAllMnemonicChars(rText), nBits ) ); + mpData->ImplClearLayoutData(); + + ImplInvalidate( true ); + + // Notify + ImplToolItems::size_type nNewPos = ( nPos == APPEND ) ? ( mpData->m_aItems.size() - 1 ) : nPos; + CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos ) ); +} + +void ToolBox::InsertItem(const OUString& rCommand, const css::uno::Reference<css::frame::XFrame>& rFrame, ToolBoxItemBits nBits, + const Size& rRequestedSize, ImplToolItems::size_type nPos) +{ + OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(rFrame)); + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rCommand, aModuleName); + OUString aLabel(vcl::CommandInfoProvider::GetLabelForCommand(aProperties)); + OUString aTooltip(vcl::CommandInfoProvider::GetTooltipForCommand(rCommand, aProperties, rFrame)); + Image aImage(CommandInfoProvider::GetImageForCommand(rCommand, rFrame, GetImageSize())); + + ToolBoxItemId nItemId(GetItemCount() + 1); + //TODO: ImplToolItems::size_type -> sal_uInt16! + InsertItem(nItemId, aImage, aLabel, nBits, nPos); + SetItemCommand(nItemId, rCommand); + SetQuickHelpText(nItemId, aTooltip); + + // set the minimal size + ImplToolItem* pItem = ImplGetItem( nItemId ); + if ( pItem ) + pItem->maMinimalItemSize = rRequestedSize; +} + +void ToolBox::InsertWindow( ToolBoxItemId nItemId, vcl::Window* pWindow, + ToolBoxItemBits nBits, ImplToolItems::size_type nPos ) +{ + SAL_WARN_IF( !nItemId, "vcl", "ToolBox::InsertWindow(): ItemId == 0" ); + SAL_WARN_IF( GetItemPos( nItemId ) != ITEM_NOTFOUND, "vcl", + "ToolBox::InsertWindow(): ItemId already exists" ); + + // create item and add to list + ImplToolItem aItem; + aItem.mnId = nItemId; + aItem.meType = ToolBoxItemType::BUTTON; + aItem.mnBits = nBits; + aItem.mpWindow = pWindow; + mpData->m_aItems.insert( (nPos < mpData->m_aItems.size()) ? mpData->m_aItems.begin()+nPos : mpData->m_aItems.end(), aItem ); + mpData->ImplClearLayoutData(); + + if ( pWindow ) + pWindow->Hide(); + + ImplInvalidate( true ); + + // Notify + ImplToolItems::size_type nNewPos = ( nPos == APPEND ) ? ( mpData->m_aItems.size() - 1 ) : nPos; + CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos ) ); +} + +void ToolBox::InsertSpace() +{ + // create item and add to list + ImplToolItem aItem; + aItem.meType = ToolBoxItemType::SPACE; + aItem.mbEnabled = false; + mpData->m_aItems.push_back( aItem ); + mpData->ImplClearLayoutData(); + + ImplInvalidate(); + + // Notify + ImplToolItems::size_type nNewPos = mpData->m_aItems.size() - 1; + CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos ) ); +} + +void ToolBox::InsertSeparator( ImplToolItems::size_type nPos, sal_uInt16 nPixSize ) +{ + // create item and add to list + ImplToolItem aItem; + aItem.meType = ToolBoxItemType::SEPARATOR; + aItem.mbEnabled = false; + if ( nPixSize ) + aItem.mnSepSize = nPixSize; + mpData->m_aItems.insert( (nPos < mpData->m_aItems.size()) ? mpData->m_aItems.begin()+nPos : mpData->m_aItems.end(), aItem ); + mpData->ImplClearLayoutData(); + + ImplInvalidate(); + + // Notify + ImplToolItems::size_type nNewPos = ( nPos == APPEND ) ? ( mpData->m_aItems.size() - 1 ) : nPos; + CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos ) ); +} + +void ToolBox::InsertBreak( ImplToolItems::size_type nPos ) +{ + // create item and add to list + ImplToolItem aItem; + aItem.meType = ToolBoxItemType::BREAK; + aItem.mbEnabled = false; + mpData->m_aItems.insert( (nPos < mpData->m_aItems.size()) ? mpData->m_aItems.begin()+nPos : mpData->m_aItems.end(), aItem ); + mpData->ImplClearLayoutData(); + + ImplInvalidate(); + + // Notify + ImplToolItems::size_type nNewPos = ( nPos == APPEND ) ? ( mpData->m_aItems.size() - 1 ) : nPos; + CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos ) ); +} + +void ToolBox::RemoveItem( ImplToolItems::size_type nPos ) +{ + if( nPos >= mpData->m_aItems.size() ) + return; + + bool bMustCalc; + bMustCalc = mpData->m_aItems[nPos].meType == ToolBoxItemType::BUTTON; + + if ( mpData->m_aItems[nPos].mpWindow ) + mpData->m_aItems[nPos].mpWindow->Hide(); + + // add the removed item to PaintRect + maPaintRect.Union( mpData->m_aItems[nPos].maRect ); + + // ensure not to delete in the Select-Handler + if ( mpData->m_aItems[nPos].mnId == mnCurItemId ) + mnCurItemId = ToolBoxItemId(0); + if ( mpData->m_aItems[nPos].mnId == mnHighItemId ) + mnHighItemId = ToolBoxItemId(0); + + ImplInvalidate( bMustCalc ); + + mpData->m_aItems.erase( mpData->m_aItems.begin()+nPos ); + mpData->ImplClearLayoutData(); + + // Notify + CallEventListeners( VclEventId::ToolboxItemRemoved, reinterpret_cast< void* >( nPos ) ); +} + +void ToolBox::CopyItem( const ToolBox& rToolBox, ToolBoxItemId nItemId ) +{ + SAL_WARN_IF( GetItemPos( nItemId ) != ITEM_NOTFOUND, "vcl", + "ToolBox::CopyItem(): ItemId already exists" ); + + ImplToolItems::size_type nPos = rToolBox.GetItemPos( nItemId ); + + // found item + if ( nPos == ITEM_NOTFOUND ) + return; + + // push ToolBox item onto the list + ImplToolItem aNewItem = rToolBox.mpData->m_aItems[nPos]; + // reset state + aNewItem.mpWindow = nullptr; + aNewItem.mbShowWindow = false; + + mpData->m_aItems.push_back( aNewItem ); + mpData->ImplClearLayoutData(); + // redraw ToolBox + ImplInvalidate(); + + // Notify + ImplToolItems::size_type nNewPos2 = mpData->m_aItems.size() - 1; + CallEventListeners( VclEventId::ToolboxItemAdded, reinterpret_cast< void* >( nNewPos2 ) ); +} + +void ToolBox::Clear() +{ + mpData->m_aItems.clear(); + mpData->ImplClearLayoutData(); + + // ensure not to delete in the Select-Handler + mnCurItemId = ToolBoxItemId(0); + mnHighItemId = ToolBoxItemId(0); + + ImplInvalidate( true, true ); + + // Notify + CallEventListeners( VclEventId::ToolboxAllItemsChanged ); +} + +void ToolBox::SetButtonType( ButtonType eNewType ) +{ + if ( meButtonType != eNewType ) + { + meButtonType = eNewType; + + // better redraw everything, as otherwise there might be problems + // with regions that were copied with CopyBits + ImplInvalidate( true ); + } +} + +void ToolBox::SetToolboxButtonSize( ToolBoxButtonSize eSize ) +{ + if( mpData->meButtonSize != eSize ) + { + mpData->meButtonSize = eSize; + mbCalc = true; + mbFormat = true; + } +} + +ToolBoxButtonSize ToolBox::GetToolboxButtonSize() const +{ + return mpData->meButtonSize; +} + +ImageType ToolBox::GetImageSize() const +{ + ImageType eImageType = ImageType::Size16; + if (mpData->meButtonSize == ToolBoxButtonSize::Large) + eImageType = ImageType::Size26; + else if (mpData->meButtonSize == ToolBoxButtonSize::Size32) + eImageType = ImageType::Size32; + + return eImageType; +} + +/*static*/ Size ToolBox::GetDefaultImageSize(ToolBoxButtonSize eToolBoxButtonSize) +{ + OutputDevice *pDefault = Application::GetDefaultDevice(); + float fScaleFactor = pDefault ? pDefault->GetDPIScaleFactor() : 1.0; + + Size aUnscaledSize(16, 16); + + if (eToolBoxButtonSize == ToolBoxButtonSize::Large) + { + OUString iconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme(); + aUnscaledSize = vcl::IconThemeInfo::SizeByThemeName(iconTheme); + } + else if (eToolBoxButtonSize == ToolBoxButtonSize::Size32) + { + aUnscaledSize = Size(32, 32); + } + return Size(aUnscaledSize.Width() * fScaleFactor, + aUnscaledSize.Height() * fScaleFactor); +} + +Size ToolBox::GetDefaultImageSize() const +{ + return GetDefaultImageSize(GetToolboxButtonSize()); +} + +void ToolBox::SetAlign( WindowAlign eNewAlign ) +{ + if ( meAlign == eNewAlign ) + return; + + meAlign = eNewAlign; + + if ( ImplIsFloatingMode() ) + return; + + // set horizontal/vertical alignment + if ( (eNewAlign == WindowAlign::Left) || (eNewAlign == WindowAlign::Right) ) + mbHorz = false; + else + mbHorz = true; + + // Update the background according to Persona if necessary + ImplInitSettings( false, false, true ); + + // redraw everything, as the border has changed + mbCalc = true; + mbFormat = true; + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(); +} + +void ToolBox::SetLineCount( ImplToolItems::size_type nNewLines ) +{ + if ( !nNewLines ) + nNewLines = 1; + + if ( mnLines != nNewLines ) + { + mnLines = nNewLines; + + // better redraw everything, as otherwise there might be problems + // with regions that were copied with CopyBits + Invalidate(); + } +} + +ToolBox::ImplToolItems::size_type ToolBox::GetItemCount() const +{ + return mpData ? mpData->m_aItems.size() : 0; +} + +ToolBoxItemType ToolBox::GetItemType( ImplToolItems::size_type nPos ) const +{ + return (nPos < mpData->m_aItems.size()) ? mpData->m_aItems[nPos].meType : ToolBoxItemType::DONTKNOW; +} + +ToolBox::ImplToolItems::size_type ToolBox::GetItemPos( ToolBoxItemId nItemId ) const +{ + if (mpData) + { + ImplToolItems::size_type nCount = mpData->m_aItems.size(); + for( ImplToolItems::size_type nPos = 0; nPos < nCount; nPos++ ) + if( mpData->m_aItems[nPos].mnId == nItemId ) + return nPos; + } + return ITEM_NOTFOUND; +} + +ToolBox::ImplToolItems::size_type ToolBox::GetItemPos( const Point& rPos ) const +{ + // search the item position on the given point + auto it = std::find_if(mpData->m_aItems.begin(), mpData->m_aItems.end(), + [&rPos](const ImplToolItem& rItem) { return rItem.maRect.Contains( rPos ); }); + + if( it != mpData->m_aItems.end() ) + return std::distance(mpData->m_aItems.begin(), it); + + return ITEM_NOTFOUND; +} + +ToolBoxItemId ToolBox::GetItemId( ImplToolItems::size_type nPos ) const +{ + return (nPos < mpData->m_aItems.size()) ? mpData->m_aItems[nPos].mnId : ToolBoxItemId(0); +} + +ToolBoxItemId ToolBox::GetItemId( const Point& rPos ) const +{ + // find item that was clicked + auto it = std::find_if(mpData->m_aItems.begin(), mpData->m_aItems.end(), + [&rPos](const ImplToolItem& rItem) { return rItem.maRect.Contains( rPos ); }); + + if( (it != mpData->m_aItems.end()) && (it->meType == ToolBoxItemType::BUTTON) ) + return it->mnId; + + return ToolBoxItemId(0); +} + +Size ToolBox::GetItemContentSize( ToolBoxItemId nItemId ) +{ + if ( mbCalc || mbFormat ) + ImplFormat(); + + ImplToolItems::size_type nPos = GetItemPos( nItemId ); + if ( nPos < mpData->m_aItems.size() ) + return mpData->m_aItems[nPos].maContentSize; + else + return Size(); +} + +ToolBoxItemId ToolBox::GetItemId(const OUString &rCommand) const +{ + if (!mpData) + return ToolBoxItemId(0); + + auto it = std::find_if(mpData->m_aItems.begin(), mpData->m_aItems.end(), + [&rCommand](const ImplToolItem& rItem) { return rItem.maCommandStr == rCommand; }); + if (it != mpData->m_aItems.end()) + return it->mnId; + + return ToolBoxItemId(0); +} + +Point ToolBox::ImplGetPopupPosition( const tools::Rectangle& rRect ) const +{ + Point aPos; + if( !rRect.IsEmpty() ) + { + tools::Rectangle aScreen = GetDesktopRectPixel(); + + // the popup should be positioned so that it will not cover + // the item rect and that it fits the desktop + // the preferred direction is always towards the center of + // the application window + + Point devPos; // the position in device coordinates for screen comparison + switch( meAlign ) + { + case WindowAlign::Top: + aPos = rRect.BottomLeft(); + aPos.AdjustY( 1 ); + devPos = OutputToAbsoluteScreenPixel( aPos ); + if( devPos.Y() >= aScreen.Bottom() ) + aPos.setY( rRect.Top() ); + break; + case WindowAlign::Bottom: + aPos = rRect.TopLeft(); + aPos.AdjustY( -1 ); + devPos = OutputToAbsoluteScreenPixel( aPos ); + if( devPos.Y() <= aScreen.Top() ) + aPos.setY( rRect.Bottom() ); + break; + case WindowAlign::Left: + aPos = rRect.TopRight(); + aPos.AdjustX( 1 ); + devPos = OutputToAbsoluteScreenPixel( aPos ); + if( devPos.X() >= aScreen.Right() ) + aPos.setX( rRect.Left() ); + break; + case WindowAlign::Right: + aPos = rRect.TopLeft(); + aPos.AdjustX( -1 ); + devPos = OutputToAbsoluteScreenPixel( aPos ); + if( devPos.X() <= aScreen.Left() ) + aPos.setX( rRect.Right() ); + break; + default: + break; + } + } + return aPos; +} + +tools::Rectangle ToolBox::GetItemRect( ToolBoxItemId nItemId ) +{ + if ( mbCalc || mbFormat ) + ImplFormat(); + + ImplToolItems::size_type nPos = GetItemPos( nItemId ); + return GetItemPosRect( nPos ); +} + +tools::Rectangle ToolBox::GetItemPosRect( ImplToolItems::size_type nPos ) +{ + if ( mbCalc || mbFormat ) + ImplFormat(); + + if ( nPos < mpData->m_aItems.size() ) + return mpData->m_aItems[nPos].maRect; + else + return tools::Rectangle(); +} + +tools::Rectangle const & ToolBox::GetOverflowRect() const +{ + return mpData->maMenubuttonItem.maRect; +} + +bool ToolBox::ImplHasExternalMenubutton() const +{ + // check if the borderwindow (i.e. the decoration) provides the menu button + bool bRet = false; + if( ImplIsFloatingMode() ) + { + // custom menu is placed in the decoration + ImplBorderWindow *pBorderWin = dynamic_cast<ImplBorderWindow*>( GetWindow( GetWindowType::Border ) ); + if( pBorderWin && !pBorderWin->GetMenuRect().IsEmpty() ) + bRet = true; + } + return bRet; +} + +void ToolBox::SetItemBits( ToolBoxItemId nItemId, ToolBoxItemBits nBits ) +{ + ImplToolItems::size_type nPos = GetItemPos( nItemId ); + + if ( nPos < GetItemCount() ) + { + ToolBoxItemBits nOldBits = mpData->m_aItems[nPos].mnBits; + mpData->m_aItems[nPos].mnBits = nBits; + nBits &= ToolBoxItemBits::LEFT | ToolBoxItemBits::AUTOSIZE | ToolBoxItemBits::DROPDOWN; + nOldBits &= ToolBoxItemBits::LEFT | ToolBoxItemBits::AUTOSIZE | ToolBoxItemBits::DROPDOWN; + // trigger reformat when the item width has changed (dropdown arrow) + bool bFormat = ToolBoxItemBits(nBits & ToolBoxItemBits::DROPDOWN) != ToolBoxItemBits(nOldBits & ToolBoxItemBits::DROPDOWN); + if ( nBits != nOldBits ) + ImplInvalidate( true, bFormat ); + } +} + +void ToolBox::SetItemWindowNonInteractive(ToolBoxItemId nItemId, bool bNonInteractive) +{ + ImplToolItems::size_type nPos = GetItemPos( nItemId ); + + if ( nPos < GetItemCount() ) + { + mpData->m_aItems[nPos].mbNonInteractiveWindow = bNonInteractive; + } +} + +ToolBoxItemBits ToolBox::GetItemBits( ToolBoxItemId nItemId ) const +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + if ( pItem ) + return pItem->mnBits; + else + return ToolBoxItemBits::NONE; +} + +void ToolBox::SetItemExpand( ToolBoxItemId nItemId, bool bExpand ) +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + if (!pItem) + return; + + if (pItem->mbExpand != bExpand) + { + pItem->mbExpand = bExpand; + ImplInvalidate(true, true); + } +} + +void ToolBox::SetItemData( ToolBoxItemId nItemId, void* pNewData ) +{ + ImplToolItems::size_type nPos = GetItemPos( nItemId ); + + if ( nPos < mpData->m_aItems.size() ) + { + mpData->m_aItems[nPos].mpUserData = pNewData; + ImplUpdateItem( nPos ); + } +} + +void* ToolBox::GetItemData( ToolBoxItemId nItemId ) const +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + if ( pItem ) + return pItem->mpUserData; + else + return nullptr; +} + +void ToolBox::SetItemImage( ToolBoxItemId nItemId, const Image& rImage ) +{ + ImplToolItems::size_type nPos = GetItemPos( nItemId ); + + if ( nPos == ITEM_NOTFOUND ) + return; + + ImplToolItem* pItem = &mpData->m_aItems[nPos]; + Size aOldSize = pItem->maImage.GetSizePixel(); + + pItem->maImage = rImage; + + // only once all is calculated, do extra work + if (!mbCalc) + { + if (aOldSize != pItem->maImage.GetSizePixel()) + ImplInvalidate( true ); + else + ImplUpdateItem( nPos ); + } +} + +static Image ImplRotImage( const Image& rImage, Degree10 nAngle10 ) +{ + BitmapEx aRotBitmapEx( rImage.GetBitmapEx() ); + + aRotBitmapEx.Rotate( nAngle10, COL_WHITE ); + + return Image( aRotBitmapEx ); +} + +void ToolBox::SetItemImageAngle( ToolBoxItemId nItemId, Degree10 nAngle10 ) +{ + ImplToolItems::size_type nPos = GetItemPos( nItemId ); + + if ( nPos == ITEM_NOTFOUND ) + return; + + ImplToolItem* pItem = &mpData->m_aItems[nPos]; + Size aOldSize = pItem->maImage.GetSizePixel(); + + Degree10 nDeltaAngle = (nAngle10 - pItem->mnImageAngle) % 3600_deg10; + while( nDeltaAngle < 0_deg10 ) + nDeltaAngle += 3600_deg10; + + pItem->mnImageAngle = nAngle10; + if( nDeltaAngle && !!pItem->maImage ) + { + pItem->maImage = ImplRotImage( pItem->maImage, nDeltaAngle ); + } + + if (!mbCalc) + { + if (aOldSize != pItem->maImage.GetSizePixel()) + ImplInvalidate(true); + else + ImplUpdateItem(nPos); + } +} + +static Image ImplMirrorImage( const Image& rImage ) +{ + BitmapEx aMirrBitmapEx( rImage.GetBitmapEx() ); + + aMirrBitmapEx.Mirror( BmpMirrorFlags::Horizontal ); + + return Image( aMirrBitmapEx ); +} + +void ToolBox::SetItemImageMirrorMode( ToolBoxItemId nItemId, bool bMirror ) +{ + ImplToolItems::size_type nPos = GetItemPos( nItemId ); + + if ( nPos == ITEM_NOTFOUND ) + return; + + ImplToolItem* pItem = &mpData->m_aItems[nPos]; + + if (pItem->mbMirrorMode != bMirror) + { + pItem->mbMirrorMode = bMirror; + if (!!pItem->maImage) + { + pItem->maImage = ImplMirrorImage(pItem->maImage); + } + + if (!mbCalc) + ImplUpdateItem(nPos); + } +} + +Image ToolBox::GetItemImage(ToolBoxItemId nItemId) const +{ + ImplToolItem* pItem = ImplGetItem(nItemId); + return pItem ? pItem->maImage : Image(); +} + +void ToolBox::SetItemText( ToolBoxItemId nItemId, const OUString& rText ) +{ + ImplToolItems::size_type nPos = GetItemPos( nItemId ); + + if ( nPos == ITEM_NOTFOUND ) + return; + + ImplToolItem* pItem = &mpData->m_aItems[nPos]; + // only once all is calculated, do extra work + if ( !mbCalc && + ((meButtonType != ButtonType::SYMBOLONLY) || !pItem->maImage) ) + { + tools::Long nOldWidth = GetOutDev()->GetCtrlTextWidth( pItem->maText ); + pItem->maText = MnemonicGenerator::EraseAllMnemonicChars(rText); + mpData->ImplClearLayoutData(); + if ( nOldWidth != GetOutDev()->GetCtrlTextWidth( pItem->maText ) ) + ImplInvalidate( true ); + else + ImplUpdateItem( nPos ); + } + else + pItem->maText = MnemonicGenerator::EraseAllMnemonicChars(rText); + + // Notify button changed event to prepare accessibility bridge + CallEventListeners( VclEventId::ToolboxButtonStateChanged, reinterpret_cast< void* >( nPos ) ); + + // Notify + CallEventListeners( VclEventId::ToolboxItemTextChanged, reinterpret_cast< void* >( nPos ) ); +} + +const OUString& ToolBox::GetItemText( ToolBoxItemId nItemId ) const +{ + + ImplToolItem* pItem = ImplGetItem( nItemId ); + + assert( pItem ); + + return pItem->maText; +} + +void ToolBox::SetItemWindow( ToolBoxItemId nItemId, vcl::Window* pNewWindow ) +{ + ImplToolItems::size_type nPos = GetItemPos( nItemId ); + + if ( nPos != ITEM_NOTFOUND ) + { + ImplToolItem* pItem = &mpData->m_aItems[nPos]; + pItem->mpWindow = pNewWindow; + if ( pNewWindow ) + pNewWindow->Hide(); + ImplInvalidate( true ); + CallEventListeners( VclEventId::ToolboxItemWindowChanged, reinterpret_cast< void* >( nPos ) ); + } +} + +vcl::Window* ToolBox::GetItemWindow( ToolBoxItemId nItemId ) const +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + if ( pItem ) + return pItem->mpWindow; + else + return nullptr; +} + +void ToolBox::EndSelection() +{ + if ( mbDrag ) + { + // reset + mbDrag = false; + if (mnCurPos != ITEM_NOTFOUND) + InvalidateItem(mnCurPos); + EndTracking(); + if (IsMouseCaptured()) + ReleaseMouse(); + Deactivate(); + } + + mnCurPos = ITEM_NOTFOUND; + mnCurItemId = ToolBoxItemId(0); + mnDownItemId = ToolBoxItemId(0); + mnMouseModifier = 0; +} + +void ToolBox::SetItemDown( ToolBoxItemId nItemId, bool bDown ) +{ + ImplToolItems::size_type nPos = GetItemPos( nItemId ); + + if ( nPos == ITEM_NOTFOUND ) + return; + + if ( bDown ) + { + if ( nPos != mnCurPos ) + { + mnCurPos = nPos; + InvalidateItem(mnCurPos); + GetOutDev()->Flush(); + } + } + else + { + if ( nPos == mnCurPos ) + { + InvalidateItem(mnCurPos); + GetOutDev()->Flush(); + mnCurPos = ITEM_NOTFOUND; + } + } + + if ( mbDrag ) + { + mbDrag = false; + EndTracking(); + if (IsMouseCaptured()) + ReleaseMouse(); + Deactivate(); + } + + mnCurItemId = ToolBoxItemId(0); + mnDownItemId = ToolBoxItemId(0); + mnMouseModifier = 0; +} + +void ToolBox::SetItemState( ToolBoxItemId nItemId, TriState eState ) +{ + ImplToolItems::size_type nPos = GetItemPos( nItemId ); + + if ( nPos == ITEM_NOTFOUND ) + return; + + ImplToolItem* pItem = &mpData->m_aItems[nPos]; + + // the state has changed + if ( pItem->meState == eState ) + return; + + // if RadioCheck, un-check the previous + if ( (eState == TRISTATE_TRUE) && (pItem->mnBits & ToolBoxItemBits::AUTOCHECK) && + (pItem->mnBits & ToolBoxItemBits::RADIOCHECK) ) + { + ImplToolItem* pGroupItem; + ImplToolItems::size_type nGroupPos; + ImplToolItems::size_type nItemCount = GetItemCount(); + + nGroupPos = nPos; + while ( nGroupPos ) + { + pGroupItem = &mpData->m_aItems[nGroupPos-1]; + if ( pGroupItem->mnBits & ToolBoxItemBits::RADIOCHECK ) + { + if ( pGroupItem->meState != TRISTATE_FALSE ) + SetItemState( pGroupItem->mnId, TRISTATE_FALSE ); + } + else + break; + nGroupPos--; + } + + nGroupPos = nPos+1; + while ( nGroupPos < nItemCount ) + { + pGroupItem = &mpData->m_aItems[nGroupPos]; + if ( pGroupItem->mnBits & ToolBoxItemBits::RADIOCHECK ) + { + if ( pGroupItem->meState != TRISTATE_FALSE ) + SetItemState( pGroupItem->mnId, TRISTATE_FALSE ); + } + else + break; + nGroupPos++; + } + } + + pItem->meState = eState; + ImplUpdateItem( nPos ); + + // Notify button changed event to prepare accessibility bridge + CallEventListeners( VclEventId::ToolboxButtonStateChanged, reinterpret_cast< void* >( nPos ) ); + + // Call accessible listener to notify state_changed event + CallEventListeners( VclEventId::ToolboxItemUpdated, reinterpret_cast< void* >(nPos) ); +} + +TriState ToolBox::GetItemState( ToolBoxItemId nItemId ) const +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + if ( pItem ) + return pItem->meState; + else + return TRISTATE_FALSE; +} + +void ToolBox::EnableItem( ToolBoxItemId nItemId, bool bEnable ) +{ + ImplToolItems::size_type nPos = GetItemPos( nItemId ); + + if ( nPos == ITEM_NOTFOUND ) + return; + + ImplToolItem* pItem = &mpData->m_aItems[nPos]; + if ( pItem->mbEnabled == bEnable ) + return; + + pItem->mbEnabled = bEnable; + + // if existing, also redraw the window + if ( pItem->mpWindow ) + pItem->mpWindow->Enable( pItem->mbEnabled ); + + // update item + ImplUpdateItem( nPos ); + + ImplUpdateInputEnable(); + + // Notify button changed event to prepare accessibility bridge + CallEventListeners( VclEventId::ToolboxButtonStateChanged, reinterpret_cast< void* >( nPos ) ); + + CallEventListeners( bEnable ? VclEventId::ToolboxItemEnabled : VclEventId::ToolboxItemDisabled, reinterpret_cast< void* >( nPos ) ); +} + +bool ToolBox::IsItemEnabled( ToolBoxItemId nItemId ) const +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + if ( pItem ) + return pItem->mbEnabled; + else + return false; +} + +void ToolBox::ShowItem( ToolBoxItemId nItemId, bool bVisible ) +{ + ImplToolItems::size_type nPos = GetItemPos( nItemId ); + mpData->ImplClearLayoutData(); + + if ( nPos != ITEM_NOTFOUND ) + { + ImplToolItem* pItem = &mpData->m_aItems[nPos]; + if ( pItem->mbVisible != bVisible ) + { + pItem->mbVisible = bVisible; + ImplInvalidate(); + } + } +} + +bool ToolBox::IsItemClipped( ToolBoxItemId nItemId ) const +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + if ( pItem ) + return pItem->IsClipped(); + else + return false; +} + +bool ToolBox::IsItemVisible( ToolBoxItemId nItemId ) const +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + if ( pItem ) + return pItem->mbVisible; + else + return false; +} + +bool ToolBox::IsItemReallyVisible( ToolBoxItemId nItemId ) const +{ + // is the item on the visible area of the toolbox? + bool bRet = false; + tools::Rectangle aRect( mnLeftBorder, mnTopBorder, mnDX-mnRightBorder, mnDY-mnBottomBorder ); + ImplToolItem* pItem = ImplGetItem( nItemId ); + + if ( pItem && pItem->mbVisible && + !pItem->maRect.IsEmpty() && aRect.Overlaps( pItem->maRect ) ) + { + bRet = true; + } + + return bRet; +} + +void ToolBox::SetItemCommand(ToolBoxItemId nItemId, const OUString& rCommand) +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + if (pItem) + pItem->maCommandStr = rCommand; +} + +OUString ToolBox::GetItemCommand( ToolBoxItemId nItemId ) const +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + if (pItem) + return pItem->maCommandStr; + + return OUString(); +} + +void ToolBox::SetQuickHelpText( ToolBoxItemId nItemId, const OUString& rText ) +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + if ( pItem ) + pItem->maQuickHelpText = rText; +} + +OUString ToolBox::GetQuickHelpText( ToolBoxItemId nItemId ) const +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + if ( pItem ) + return pItem->maQuickHelpText; + else + return OUString(); +} + +void ToolBox::SetHelpText( ToolBoxItemId nItemId, const OUString& rText ) +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + if ( pItem ) + pItem->maHelpText = rText; +} + +const OUString& ToolBox::GetHelpText( ToolBoxItemId nItemId ) const +{ + return ImplGetHelpText( nItemId ); +} + +void ToolBox::SetHelpId( ToolBoxItemId nItemId, const OString& rHelpId ) +{ + ImplToolItem* pItem = ImplGetItem( nItemId ); + + if ( pItem ) + pItem->maHelpId = rHelpId; +} + +// disable key input if all items are disabled +void ToolBox::ImplUpdateInputEnable() +{ + mpData->mbKeyInputDisabled = std::none_of(mpData->m_aItems.begin(), mpData->m_aItems.end(), + [](const ImplToolItem& rItem) { + // at least one useful entry + return rItem.mbEnabled; + }); +} + +void ToolBox::ImplFillLayoutData() +{ + mpData->m_pLayoutData.emplace(); + + ImplToolItems::size_type nCount = mpData->m_aItems.size(); + for( ImplToolItems::size_type i = 0; i < nCount; i++ ) + { + ImplToolItem* pItem = &mpData->m_aItems[i]; + + // only draw, if the rectangle is within PaintRectangle + if (!pItem->maRect.IsEmpty()) + InvalidateItem(i); + } +} + +OUString ToolBox::GetDisplayText() const +{ + if( ! mpData->m_pLayoutData ) + const_cast<ToolBox *>(this)->ImplFillLayoutData(); + return mpData->m_pLayoutData ? mpData->m_pLayoutData->m_aDisplayText : OUString(); +} + +tools::Rectangle ToolBox::GetCharacterBounds( ToolBoxItemId nItemID, tools::Long nIndex ) +{ + tools::Long nItemIndex = -1; + if( ! mpData->m_pLayoutData ) + ImplFillLayoutData(); + if( mpData->m_pLayoutData ) + { + for( size_t i = 0; i < mpData->m_pLayoutData->m_aLineItemIds.size(); i++ ) + { + if( mpData->m_pLayoutData->m_aLineItemIds[i] == nItemID ) + { + nItemIndex = mpData->m_pLayoutData->m_aLineIndices[i]; + break; + } + } + } + return (mpData->m_pLayoutData && nItemIndex != -1) ? mpData->m_pLayoutData->GetCharacterBounds( nItemIndex+nIndex ) : tools::Rectangle(); +} + +tools::Long ToolBox::GetIndexForPoint( const Point& rPoint, ToolBoxItemId& rItemID ) +{ + tools::Long nIndex = -1; + rItemID = ToolBoxItemId(0); + if( ! mpData->m_pLayoutData ) + ImplFillLayoutData(); + if( mpData->m_pLayoutData ) + { + nIndex = mpData->m_pLayoutData->GetIndexForPoint( rPoint ); + for( size_t i = 0; i < mpData->m_pLayoutData->m_aLineIndices.size(); i++ ) + { + if( mpData->m_pLayoutData->m_aLineIndices[i] <= nIndex && + (i == mpData->m_pLayoutData->m_aLineIndices.size()-1 || mpData->m_pLayoutData->m_aLineIndices[i+1] > nIndex) ) + { + rItemID = mpData->m_pLayoutData->m_aLineItemIds[i]; + break; + } + } + } + return nIndex; +} + +void ToolBox::SetDropdownClickHdl( const Link<ToolBox *, void>& rLink ) +{ + if (mpData != nullptr) { + mpData->maDropdownClickHdl = rLink; + } +} + +void ToolBox::SetMenuType( ToolBoxMenuType aType ) +{ + if( aType == mpData->maMenuType ) + return; + + mpData->maMenuType = aType; + if( IsFloatingMode() ) + { + // the menu button may have to be moved into the decoration which changes the layout + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper ) + pWrapper->ShowMenuTitleButton( bool( aType & ToolBoxMenuType::Customize) ); + + mbFormat = true; + ImplFormat(); + ImplSetMinMaxFloatSize(); + } + else + { + // trigger redraw of menu button + if( !mpData->maMenubuttonItem.maRect.IsEmpty() ) + Invalidate(mpData->maMenubuttonItem.maRect); + } +} + +ToolBoxMenuType ToolBox::GetMenuType() const +{ + return mpData->maMenuType; +} + +bool ToolBox::IsMenuEnabled() const +{ + return mpData->maMenuType != ToolBoxMenuType::NONE; +} + +PopupMenu* ToolBox::GetMenu() const +{ + return mpData == nullptr ? nullptr : mpData->mpMenu; +} + +void ToolBox::SetMenuExecuteHdl( const Link<ToolBox *, void>& rLink ) +{ + mpData->maMenuButtonHdl = rLink; +} + +bool ToolBox::ImplHasClippedItems() +{ + // are any items currently clipped ? + ImplFormat(); + return std::any_of(mpData->m_aItems.begin(), mpData->m_aItems.end(), + [](const ImplToolItem& rItem) { return rItem.IsClipped(); }); +} + +namespace +{ + MenuItemBits ConvertBitsFromToolBoxToMenu(ToolBoxItemBits nToolItemBits) + { + MenuItemBits nMenuItemBits = MenuItemBits::NONE; + if ((nToolItemBits & ToolBoxItemBits::CHECKABLE) || + (nToolItemBits & ToolBoxItemBits::DROPDOWN)) + { + nMenuItemBits |= MenuItemBits::CHECKABLE; + } + return nMenuItemBits; + } +} + +void ToolBox::UpdateCustomMenu() +{ + // fill clipped items into menu + PopupMenu *pMenu = GetMenu(); + pMenu->Clear(); + + // add menu items: first the overflow items, then hidden items, both in the + // order they would usually appear in the toolbar. Separators that would be + // in the toolbar are ignored as they would introduce too much clutter, + // instead we have a single separator to help distinguish between overflow + // and hidden items. + if ( mpData->m_aItems.empty() ) + return; + + // nStartPos will hold the number of clipped items appended from first loop + for ( const auto& rItem : mpData->m_aItems ) + { + if( rItem.IsClipped() ) + { + sal_uInt16 id = sal_uInt16(rItem.mnId) + TOOLBOX_MENUITEM_START; + MenuItemBits nMenuItemBits = ConvertBitsFromToolBoxToMenu(rItem.mnBits); + pMenu->InsertItem( id, rItem.maText, rItem.maImage, nMenuItemBits); + pMenu->SetItemCommand( id, rItem.maCommandStr ); + pMenu->EnableItem( id, rItem.mbEnabled ); + pMenu->CheckItem ( id, rItem.meState == TRISTATE_TRUE ); + } + } + + // add a separator below the inserted clipped-items + pMenu->InsertSeparator(); + + // now append the items that are explicitly disabled + for ( const auto& rItem : mpData->m_aItems ) + { + if( rItem.IsItemHidden() ) + { + sal_uInt16 id = sal_uInt16(rItem.mnId) + TOOLBOX_MENUITEM_START; + MenuItemBits nMenuItemBits = ConvertBitsFromToolBoxToMenu(rItem.mnBits); + pMenu->InsertItem( id, rItem.maText, rItem.maImage, nMenuItemBits ); + pMenu->SetItemCommand( id, rItem.maCommandStr ); + pMenu->EnableItem( id, rItem.mbEnabled ); + pMenu->CheckItem( id, rItem.meState == TRISTATE_TRUE ); + } + } +} + +IMPL_LINK( ToolBox, ImplCustomMenuListener, VclMenuEvent&, rEvent, void ) +{ + if( rEvent.GetMenu() == GetMenu() && rEvent.GetId() == VclEventId::MenuSelect ) + { + sal_uInt16 id = GetMenu()->GetItemId( rEvent.GetItemPos() ); + if( id >= TOOLBOX_MENUITEM_START ) + TriggerItem( ToolBoxItemId(id - TOOLBOX_MENUITEM_START) ); + } +} + +void ToolBox::ExecuteCustomMenu( const tools::Rectangle& rRect ) +{ + if ( !IsMenuEnabled() || ImplIsInPopupMode() ) + return; + + UpdateCustomMenu(); + + if( GetMenuType() & ToolBoxMenuType::Customize ) + // call button handler to allow for menu customization + mpData->maMenuButtonHdl.Call( this ); + + GetMenu()->AddEventListener( LINK( this, ToolBox, ImplCustomMenuListener ) ); + + // make sure all disabled entries will be shown + GetMenu()->SetMenuFlags( + GetMenu()->GetMenuFlags() | MenuFlags::AlwaysShowDisabledEntries ); + + // toolbox might be destroyed during execute + bool bBorderDel = false; + + VclPtr<vcl::Window> pWin = this; + tools::Rectangle aMenuRect = rRect; + VclPtr<ImplBorderWindow> pBorderWin; + if( aMenuRect.IsEmpty() && IsFloatingMode() ) + { + // custom menu is placed in the decoration + pBorderWin = dynamic_cast<ImplBorderWindow*>( GetWindow( GetWindowType::Border ) ); + if( pBorderWin && !pBorderWin->GetMenuRect().IsEmpty() ) + { + pWin = pBorderWin; + aMenuRect = pBorderWin->GetMenuRect(); + bBorderDel = true; + } + } + + sal_uInt16 uId = GetMenu()->Execute( pWin, tools::Rectangle( ImplGetPopupPosition( aMenuRect ), Size() ), + PopupMenuFlags::ExecuteDown | PopupMenuFlags::NoMouseUpClose ); + + if ( pWin->isDisposed() ) + return; + + if( GetMenu() ) + GetMenu()->RemoveEventListener( LINK( this, ToolBox, ImplCustomMenuListener ) ); + if( bBorderDel ) + { + if( pBorderWin->isDisposed() ) + return; + } + + pWin->Invalidate( aMenuRect ); + + if( uId ) + GrabFocusToDocument(); +} + +// checks override first, useful during calculation of sizes +bool ToolBox::ImplIsFloatingMode() const +{ + SAL_WARN_IF( mpData->mbAssumeDocked && mpData->mbAssumeFloating, "vcl", + "cannot assume docked and floating" ); + + if( mpData->mbAssumeDocked ) + return false; + else if( mpData->mbAssumeFloating ) + return true; + else + return IsFloatingMode(); +} + +// checks override first, useful during calculation of sizes +bool ToolBox::ImplIsInPopupMode() const +{ + if( mpData->mbAssumePopupMode ) + return true; + else + { + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + return ( pWrapper && pWrapper->GetFloatingWindow() && static_cast<FloatingWindow*>(pWrapper->GetFloatingWindow())->IsInPopupMode() ); + } +} + +void ToolBox::Lock( bool bLock ) +{ + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( !pWrapper ) + return; + if( mpData->mbIsLocked != bLock ) + { + mpData->mbIsLocked = bLock; + if( !ImplIsFloatingMode() ) + { + mbCalc = true; + mbFormat = true; + SetSizePixel( CalcWindowSizePixel(1) ); + Invalidate(); + } + } +} + +bool ToolBox::AlwaysLocked() +{ + // read config item to determine toolbox behaviour, used for subtoolbars + + static int nAlwaysLocked = -1; + + if( nAlwaysLocked == -1 ) + { + nAlwaysLocked = 0; // ask configuration only once + + utl::OConfigurationNode aNode = utl::OConfigurationTreeRoot::tryCreateWithComponentContext( + comphelper::getProcessComponentContext(), + "/org.openoffice.Office.UI.GlobalSettings/Toolbars" ); // note: case sensitive ! + if ( aNode.isValid() ) + { + // feature enabled ? + bool bStatesEnabled = bool(); + css::uno::Any aValue = aNode.getNodeValue( "StatesEnabled" ); + if( aValue >>= bStatesEnabled ) + { + if( bStatesEnabled ) + { + // now read the locking state + utl::OConfigurationNode aNode2 = utl::OConfigurationTreeRoot::tryCreateWithComponentContext( + comphelper::getProcessComponentContext(), + "/org.openoffice.Office.UI.GlobalSettings/Toolbars/States" ); // note: case sensitive ! + + bool bLocked = bool(); + css::uno::Any aValue2 = aNode2.getNodeValue( "Locked" ); + if( aValue2 >>= bLocked ) + nAlwaysLocked = bLocked ? 1 : 0; + } + } + } + } + + return nAlwaysLocked == 1; +} + +bool ToolBox::WillUsePopupMode() const +{ + return mpData->mbWillUsePopupMode; +} + +void ToolBox::WillUsePopupMode( bool b ) +{ + mpData->mbWillUsePopupMode = b; +} + +void ToolBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + DockingWindow::DumpAsPropertyTree(rJsonWriter); + + auto childrenNode = rJsonWriter.startArray("children"); + for (ToolBox::ImplToolItems::size_type i = 0; i < GetItemCount(); ++i) + { + auto childNode = rJsonWriter.startStruct(); + ToolBoxItemId nId = GetItemId(i); + + vcl::Window* pWindow = GetItemWindow(nId); + if (pWindow) + { + pWindow->DumpAsPropertyTree(rJsonWriter); + } + else + { + OUString sCommand = GetItemCommand(nId); + rJsonWriter.put("type", "toolitem"); + rJsonWriter.put("text", GetItemText(nId)); + rJsonWriter.put("command", sCommand); + if (IsItemChecked(nId)) + rJsonWriter.put("selected", true); + if (!IsItemVisible(nId)) + rJsonWriter.put("visible", false); + if (GetItemBits(nId) & ToolBoxItemBits::DROPDOWN) + rJsonWriter.put("dropdown", true); + if (!IsItemEnabled(nId)) + rJsonWriter.put("enabled", false); + + Image aImage = GetItemImage(nId); + if (!sCommand.startsWith(".uno:") && !!aImage) + { + SvMemoryStream aOStm(6535, 6535); + if(GraphicConverter::Export(aOStm, aImage.GetBitmapEx(), ConvertDataFormat::PNG) == ERRCODE_NONE) + { + css::uno::Sequence<sal_Int8> aSeq( static_cast<sal_Int8 const *>(aOStm.GetData()), aOStm.Tell()); + OUStringBuffer aBuffer("data:image/png;base64,"); + ::comphelper::Base64::encode(aBuffer, aSeq); + rJsonWriter.put("image", aBuffer.makeStringAndClear()); + } + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/window.cxx b/vcl/source/window/window.cxx new file mode 100644 index 000000000..aabd1778c --- /dev/null +++ b/vcl/source/window/window.cxx @@ -0,0 +1,3978 @@ +/* -*- 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/strbuf.hxx> +#include <sal/log.hxx> + +#include <sal/types.h> +#include <tools/diagnose_ex.h> +#include <vcl/salgtype.hxx> +#include <vcl/event.hxx> +#include <vcl/help.hxx> +#include <vcl/cursor.hxx> +#include <vcl/svapp.hxx> +#include <vcl/transfer.hxx> +#include <vcl/vclevent.hxx> +#include <vcl/window.hxx> +#include <vcl/syswin.hxx> +#include <vcl/dockwin.hxx> +#include <vcl/wall.hxx> +#include <vcl/toolkit/fixed.hxx> +#include <vcl/taskpanelist.hxx> +#include <vcl/toolkit/unowrap.hxx> +#include <vcl/lazydelete.hxx> +#include <vcl/virdev.hxx> +#include <vcl/settings.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/IDialogRenderable.hxx> + +#include <vcl/uitest/uiobject.hxx> + +#include <ImplOutDevData.hxx> +#include <impfontcache.hxx> +#include <salframe.hxx> +#include <salobj.hxx> +#include <salinst.hxx> +#include <salgdi.hxx> +#include <svdata.hxx> +#include <window.h> +#include <toolbox.h> +#include <brdwin.hxx> +#include <helpwin.hxx> + +#include <com/sun/star/accessibility/AccessibleRelation.hpp> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/XAccessibleEditableText.hpp> +#include <com/sun/star/awt/XWindowPeer.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp> +#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp> +#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp> +#include <com/sun/star/rendering/CanvasFactory.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <unotools/configmgr.hxx> +#include <osl/diagnose.h> +#include <tools/debug.hxx> +#include <tools/json_writer.hxx> +#include <boost/property_tree/ptree.hpp> + +#include <cassert> +#include <typeinfo> + +#ifdef _WIN32 // see #140456# +#include <win/salframe.h> +#endif + +#include "impldockingwrapper.hxx" + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::datatransfer::clipboard; +using namespace ::com::sun::star::datatransfer::dnd; + +namespace vcl { + +Window::Window( WindowType nType ) + : mpWindowImpl(new WindowImpl( *this, nType )) +{ + // true: this outdev will be mirrored if RTL window layout (UI mirroring) is globally active + mpWindowImpl->mxOutDev->mbEnableRTL = AllSettings::GetLayoutRTL(); +} + +Window::Window( vcl::Window* pParent, WinBits nStyle ) + : mpWindowImpl(new WindowImpl( *this, WindowType::WINDOW )) +{ + // true: this outdev will be mirrored if RTL window layout (UI mirroring) is globally active + mpWindowImpl->mxOutDev->mbEnableRTL = AllSettings::GetLayoutRTL(); + + ImplInit( pParent, nStyle, nullptr ); +} + +#if OSL_DEBUG_LEVEL > 0 +namespace +{ + OString lcl_createWindowInfo(const vcl::Window* pWindow) + { + // skip border windows, they do not carry information that + // would help with diagnosing the problem + const vcl::Window* pTempWin( pWindow ); + while ( pTempWin && pTempWin->GetType() == WindowType::BORDERWINDOW ) { + pTempWin = pTempWin->GetWindow( GetWindowType::FirstChild ); + } + // check if pTempWin is not null, otherwise use the + // original address + if ( pTempWin ) { + pWindow = pTempWin; + } + + return OString::Concat(" ") + + typeid( *pWindow ).name() + + "(" + + OUStringToOString( + pWindow->GetText(), + RTL_TEXTENCODING_UTF8 + ) + + ")"; + } +} +#endif + +void Window::dispose() +{ + assert( mpWindowImpl ); + assert( !mpWindowImpl->mbInDispose ); // should only be called from disposeOnce() + assert( (!mpWindowImpl->mpParent || + mpWindowImpl->mpParent->mpWindowImpl) && + "vcl::Window child should have its parent disposed first" ); + + // remove Key and Mouse events issued by Application::PostKey/MouseEvent + Application::RemoveMouseAndKeyEvents( this ); + + // Dispose of the canvas implementation (which, currently, has an + // own wrapper window as a child to this one. + GetOutDev()->ImplDisposeCanvas(); + + mpWindowImpl->mbInDispose = true; + + CallEventListeners( VclEventId::ObjectDying ); + + // do not send child events for frames that were registered as native frames + if( !ImplIsAccessibleNativeFrame() && mpWindowImpl->mbReallyVisible ) + if ( ImplIsAccessibleCandidate() && GetAccessibleParentWindow() ) + GetAccessibleParentWindow()->CallEventListeners( VclEventId::WindowChildDestroyed, this ); + + // remove associated data structures from dockingmanager + ImplGetDockingManager()->RemoveWindow( this ); + + // remove ownerdraw decorated windows from list in the top-most frame window + if( (GetStyle() & WB_OWNERDRAWDECORATION) && mpWindowImpl->mbFrame ) + { + ::std::vector< VclPtr<vcl::Window> >& rList = ImplGetOwnerDrawList(); + auto p = ::std::find( rList.begin(), rList.end(), VclPtr<vcl::Window>(this) ); + if( p != rList.end() ) + rList.erase( p ); + } + + // shutdown drag and drop + Reference < XComponent > xDnDComponent( mpWindowImpl->mxDNDListenerContainer, UNO_QUERY ); + + if( xDnDComponent.is() ) + xDnDComponent->dispose(); + + if( mpWindowImpl->mbFrame && mpWindowImpl->mpFrameData ) + { + try + { + // deregister drop target listener + if( mpWindowImpl->mpFrameData->mxDropTargetListener.is() ) + { + Reference< XDragGestureRecognizer > xDragGestureRecognizer(mpWindowImpl->mpFrameData->mxDragSource, UNO_QUERY); + if( xDragGestureRecognizer.is() ) + { + xDragGestureRecognizer->removeDragGestureListener( + Reference< XDragGestureListener > (mpWindowImpl->mpFrameData->mxDropTargetListener, UNO_QUERY)); + } + + mpWindowImpl->mpFrameData->mxDropTarget->removeDropTargetListener( mpWindowImpl->mpFrameData->mxDropTargetListener ); + mpWindowImpl->mpFrameData->mxDropTargetListener.clear(); + } + + // shutdown drag and drop for this frame window + Reference< XComponent > xComponent( mpWindowImpl->mpFrameData->mxDropTarget, UNO_QUERY ); + + // DNDEventDispatcher does not hold a reference of the DropTarget, + // so it's ok if it does not support XComponent + if( xComponent.is() ) + xComponent->dispose(); + } + catch (const Exception&) + { + // can be safely ignored here. + } + } + + UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper( false ); + if ( pWrapper ) + pWrapper->WindowDestroyed( this ); + + // MT: Must be called after WindowDestroyed! + // Otherwise, if the accessible is a VCLXWindow, it will try to destroy this window again! + // But accessibility implementations from applications need this dispose. + if ( mpWindowImpl->mxAccessible.is() ) + { + Reference< XComponent> xC( mpWindowImpl->mxAccessible, UNO_QUERY ); + if ( xC.is() ) + xC->dispose(); + } + + ImplSVData* pSVData = ImplGetSVData(); + + if ( ImplGetSVHelpData().mpHelpWin && (ImplGetSVHelpData().mpHelpWin->GetParent() == this) ) + ImplDestroyHelpWindow( true ); + + SAL_WARN_IF(pSVData->mpWinData->mpTrackWin.get() == this, "vcl.window", + "Window::~Window(): Window is in TrackingMode"); + SAL_WARN_IF(IsMouseCaptured(), "vcl.window", + "Window::~Window(): Window has the mouse captured"); + + // due to old compatibility + if (pSVData->mpWinData->mpTrackWin == this) + EndTracking(); + if (IsMouseCaptured()) + ReleaseMouse(); + +#if OSL_DEBUG_LEVEL > 0 + // always perform these tests in debug builds + { + OStringBuffer aErrorStr; + bool bError = false; + vcl::Window* pTempWin; + + if ( mpWindowImpl->mpFirstChild ) + { + OStringBuffer aTempStr = "Window (" + + lcl_createWindowInfo(this) + + ") with live children destroyed: "; + pTempWin = mpWindowImpl->mpFirstChild; + while ( pTempWin ) + { + aTempStr.append(lcl_createWindowInfo(pTempWin)); + pTempWin = pTempWin->mpWindowImpl->mpNext; + } + OSL_FAIL( aTempStr.getStr() ); + Application::Abort(OStringToOUString(aTempStr, RTL_TEXTENCODING_UTF8)); + } + + if (mpWindowImpl->mpFrameData != nullptr) + { + pTempWin = mpWindowImpl->mpFrameData->mpFirstOverlap; + while ( pTempWin ) + { + if ( ImplIsRealParentPath( pTempWin ) ) + { + bError = true; + aErrorStr.append(lcl_createWindowInfo(pTempWin)); + } + pTempWin = pTempWin->mpWindowImpl->mpNextOverlap; + } + if ( bError ) + { + OString aTempStr = + "Window (" + + lcl_createWindowInfo(this) + + ") with live SystemWindows destroyed: " + + aErrorStr; + OSL_FAIL(aTempStr.getStr()); + Application::Abort(OStringToOUString(aTempStr, RTL_TEXTENCODING_UTF8)); + } + } + + bError = false; + pTempWin = pSVData->maFrameData.mpFirstFrame; + while ( pTempWin ) + { + if ( ImplIsRealParentPath( pTempWin ) ) + { + bError = true; + aErrorStr.append(lcl_createWindowInfo(pTempWin)); + } + pTempWin = pTempWin->mpWindowImpl->mpFrameData->mpNextFrame; + } + if ( bError ) + { + OString aTempStr = "Window (" + + lcl_createWindowInfo(this) + + ") with live SystemWindows destroyed: " + + aErrorStr; + OSL_FAIL( aTempStr.getStr() ); + Application::Abort(OStringToOUString(aTempStr, RTL_TEXTENCODING_UTF8)); + } + + if ( mpWindowImpl->mpFirstOverlap ) + { + OStringBuffer aTempStr = "Window (" + + lcl_createWindowInfo(this) + + ") with live SystemWindows destroyed: "; + pTempWin = mpWindowImpl->mpFirstOverlap; + while ( pTempWin ) + { + aTempStr.append(lcl_createWindowInfo(pTempWin)); + pTempWin = pTempWin->mpWindowImpl->mpNext; + } + OSL_FAIL( aTempStr.getStr() ); + Application::Abort(OStringToOUString(aTempStr, RTL_TEXTENCODING_UTF8)); + } + + vcl::Window* pMyParent = GetParent(); + SystemWindow* pMySysWin = nullptr; + + while ( pMyParent ) + { + if ( pMyParent->IsSystemWindow() ) + { + pMySysWin = dynamic_cast<SystemWindow *>(pMyParent); + } + pMyParent = pMyParent->GetParent(); + } + if ( pMySysWin && pMySysWin->ImplIsInTaskPaneList( this ) ) + { + OString aTempStr = "Window (" + + lcl_createWindowInfo(this) + + ") still in TaskPanelList!"; + OSL_FAIL( aTempStr.getStr() ); + Application::Abort(OStringToOUString(aTempStr, RTL_TEXTENCODING_UTF8)); + } + } +#endif + + if( mpWindowImpl->mbIsInTaskPaneList ) + { + vcl::Window* pMyParent = GetParent(); + SystemWindow* pMySysWin = nullptr; + + while ( pMyParent ) + { + if ( pMyParent->IsSystemWindow() ) + { + pMySysWin = dynamic_cast<SystemWindow *>(pMyParent); + } + pMyParent = pMyParent->GetParent(); + } + if ( pMySysWin && pMySysWin->ImplIsInTaskPaneList( this ) ) + { + pMySysWin->GetTaskPaneList()->RemoveWindow( this ); + } + else + { + SAL_WARN( "vcl", "Window (" << GetText() << ") not found in TaskPanelList"); + } + } + + // remove from size-group if necessary + remove_from_all_size_groups(); + + // clear mnemonic labels + std::vector<VclPtr<FixedText> > aMnemonicLabels(list_mnemonic_labels()); + for (auto const& mnemonicLabel : aMnemonicLabels) + { + remove_mnemonic_label(mnemonicLabel); + } + + // hide window in order to trigger the Paint-Handling + Hide(); + + // EndExtTextInputMode + if (pSVData->mpWinData->mpExtTextInputWin == this) + { + EndExtTextInput(); + if (pSVData->mpWinData->mpExtTextInputWin == this) + pSVData->mpWinData->mpExtTextInputWin = nullptr; + } + + // check if the focus window is our child + bool bHasFocusedChild = false; + if (pSVData->mpWinData->mpFocusWin && ImplIsRealParentPath(pSVData->mpWinData->mpFocusWin)) + { + // #122232#, this must not happen and is an application bug ! but we try some cleanup to hopefully avoid crashes, see below + bHasFocusedChild = true; +#if OSL_DEBUG_LEVEL > 0 + OUString aTempStr = "Window (" + GetText() + + ") with focused child window destroyed ! THIS WILL LEAD TO CRASHES AND MUST BE FIXED !"; + SAL_WARN( "vcl", aTempStr ); + Application::Abort(aTempStr); +#endif + } + + // if we get focus pass focus to another window + vcl::Window* pOverlapWindow = ImplGetFirstOverlapWindow(); + if (pSVData->mpWinData->mpFocusWin == this + || bHasFocusedChild) // #122232#, see above, try some cleanup + { + if ( mpWindowImpl->mbFrame ) + { + pSVData->mpWinData->mpFocusWin = nullptr; + pOverlapWindow->mpWindowImpl->mpLastFocusWindow = nullptr; + } + else + { + vcl::Window* pParent = GetParent(); + vcl::Window* pBorderWindow = mpWindowImpl->mpBorderWindow; + // when windows overlap, give focus to the parent + // of the next FrameWindow + if ( pBorderWindow ) + { + if ( pBorderWindow->ImplIsOverlapWindow() ) + pParent = pBorderWindow->mpWindowImpl->mpOverlapWindow; + } + else if ( ImplIsOverlapWindow() ) + pParent = mpWindowImpl->mpOverlapWindow; + + if ( pParent && pParent->IsEnabled() && pParent->IsInputEnabled() && ! pParent->IsInModalMode() ) + pParent->GrabFocus(); + else + mpWindowImpl->mpFrameWindow->GrabFocus(); + + // If the focus was set back to 'this' set it to nothing + if (pSVData->mpWinData->mpFocusWin == this) + { + pSVData->mpWinData->mpFocusWin = nullptr; + pOverlapWindow->mpWindowImpl->mpLastFocusWindow = nullptr; + } + } + } + + if ( pOverlapWindow != nullptr && + pOverlapWindow->mpWindowImpl->mpLastFocusWindow == this ) + pOverlapWindow->mpWindowImpl->mpLastFocusWindow = nullptr; + + // reset hint for DefModalDialogParent + if( pSVData->maFrameData.mpActiveApplicationFrame == this ) + pSVData->maFrameData.mpActiveApplicationFrame = nullptr; + + // reset hint of what was the last wheeled window + if (pSVData->mpWinData->mpLastWheelWindow == this) + pSVData->mpWinData->mpLastWheelWindow = nullptr; + + // reset marked windows + if ( mpWindowImpl->mpFrameData != nullptr ) + { + if ( mpWindowImpl->mpFrameData->mpFocusWin == this ) + mpWindowImpl->mpFrameData->mpFocusWin = nullptr; + if ( mpWindowImpl->mpFrameData->mpMouseMoveWin == this ) + mpWindowImpl->mpFrameData->mpMouseMoveWin = nullptr; + if ( mpWindowImpl->mpFrameData->mpMouseDownWin == this ) + mpWindowImpl->mpFrameData->mpMouseDownWin = nullptr; + } + + // reset Deactivate-Window + if (pSVData->mpWinData->mpLastDeacWin == this) + pSVData->mpWinData->mpLastDeacWin = nullptr; + + if ( mpWindowImpl->mbFrame && mpWindowImpl->mpFrameData ) + { + if ( mpWindowImpl->mpFrameData->mnFocusId ) + Application::RemoveUserEvent( mpWindowImpl->mpFrameData->mnFocusId ); + mpWindowImpl->mpFrameData->mnFocusId = nullptr; + if ( mpWindowImpl->mpFrameData->mnMouseMoveId ) + Application::RemoveUserEvent( mpWindowImpl->mpFrameData->mnMouseMoveId ); + mpWindowImpl->mpFrameData->mnMouseMoveId = nullptr; + } + + // release SalGraphics + VclPtr<OutputDevice> pOutDev = GetOutDev(); + pOutDev->ReleaseGraphics(); + + // remove window from the lists + ImplRemoveWindow( true ); + + // de-register as "top window child" at our parent, if necessary + if ( mpWindowImpl->mbFrame ) + { + bool bIsTopWindow + = mpWindowImpl->mpWinData && (mpWindowImpl->mpWinData->mnIsTopWindow == 1); + if ( mpWindowImpl->mpRealParent && bIsTopWindow ) + { + ImplWinData* pParentWinData = mpWindowImpl->mpRealParent->ImplGetWinData(); + + auto myPos = ::std::find( pParentWinData->maTopWindowChildren.begin(), + pParentWinData->maTopWindowChildren.end(), VclPtr<vcl::Window>(this) ); + SAL_WARN_IF( myPos == pParentWinData->maTopWindowChildren.end(), "vcl.window", "Window::~Window: inconsistency in top window chain!" ); + if ( myPos != pParentWinData->maTopWindowChildren.end() ) + pParentWinData->maTopWindowChildren.erase( myPos ); + } + } + + mpWindowImpl->mpWinData.reset(); + + // remove BorderWindow or Frame window data + mpWindowImpl->mpBorderWindow.disposeAndClear(); + if ( mpWindowImpl->mbFrame ) + { + if ( pSVData->maFrameData.mpFirstFrame == this ) + pSVData->maFrameData.mpFirstFrame = mpWindowImpl->mpFrameData->mpNextFrame; + else + { + sal_Int32 nWindows = 0; + vcl::Window* pSysWin = pSVData->maFrameData.mpFirstFrame; + while ( pSysWin && pSysWin->mpWindowImpl->mpFrameData->mpNextFrame.get() != this ) + { + pSysWin = pSysWin->mpWindowImpl->mpFrameData->mpNextFrame; + nWindows++; + } + + if ( pSysWin ) + { + assert (mpWindowImpl->mpFrameData->mpNextFrame.get() != pSysWin); + pSysWin->mpWindowImpl->mpFrameData->mpNextFrame = mpWindowImpl->mpFrameData->mpNextFrame; + } + else // if it is not in the list, we can't remove it. + SAL_WARN("vcl.window", "Window " << this << " marked as frame window, " + "is missing from list of " << nWindows << " frames"); + } + if (mpWindowImpl->mpFrame) // otherwise exception during init + { + mpWindowImpl->mpFrame->SetCallback( nullptr, nullptr ); + pSVData->mpDefInst->DestroyFrame( mpWindowImpl->mpFrame ); + } + assert (mpWindowImpl->mpFrameData->mnFocusId == nullptr); + assert (mpWindowImpl->mpFrameData->mnMouseMoveId == nullptr); + + mpWindowImpl->mpFrameData->mpBuffer.disposeAndClear(); + delete mpWindowImpl->mpFrameData; + mpWindowImpl->mpFrameData = nullptr; + } + + if (mpWindowImpl->mxWindowPeer) + mpWindowImpl->mxWindowPeer->dispose(); + + // should be the last statements + mpWindowImpl.reset(); + + pOutDev.disposeAndClear(); + // just to make loplugin:vclwidgets happy + VclReferenceBase::dispose(); +} + +Window::~Window() +{ + disposeOnce(); +} + +// We will eventually being removing the inheritance of OutputDevice +// from Window. It will be replaced with a transient relationship such +// that the OutputDevice is only live for the scope of the Paint method. +// In the meantime this can help move us towards a Window use an +// OutputDevice, not being one. + +::OutputDevice const* Window::GetOutDev() const +{ + return mpWindowImpl->mxOutDev.get(); +} + +::OutputDevice* Window::GetOutDev() +{ + return mpWindowImpl->mxOutDev.get(); +} + +Color WindowOutputDevice::GetBackgroundColor() const +{ + return mxOwnerWindow->GetDisplayBackground().GetColor(); +} + +bool WindowOutputDevice::CanEnableNativeWidget() const +{ + return mxOwnerWindow->IsNativeWidgetEnabled(); +} + +} /* namespace vcl */ + +WindowImpl::WindowImpl( vcl::Window& rWindow, WindowType nType ) +{ + mxOutDev = VclPtr<vcl::WindowOutputDevice>::Create(rWindow); + maZoom = Fraction( 1, 1 ); + mfPartialScrollX = 0.0; + mfPartialScrollY = 0.0; + maWinRegion = vcl::Region(true); + maWinClipRegion = vcl::Region(true); + mpWinData = nullptr; // Extra Window Data, that we don't need for all windows + mpFrameData = nullptr; // Frame Data + mpFrame = nullptr; // Pointer to frame window + mpSysObj = nullptr; + mpFrameWindow = nullptr; // window to top level parent (same as frame window) + mpOverlapWindow = nullptr; // first overlap parent + mpBorderWindow = nullptr; // Border-Window + mpClientWindow = nullptr; // Client-Window of a FrameWindow + mpParent = nullptr; // parent (incl. BorderWindow) + mpRealParent = nullptr; // real parent (excl. BorderWindow) + mpFirstChild = nullptr; // first child window + mpLastChild = nullptr; // last child window + mpFirstOverlap = nullptr; // first overlap window (only set in overlap windows) + mpLastOverlap = nullptr; // last overlap window (only set in overlap windows) + mpPrev = nullptr; // prev window + mpNext = nullptr; // next window + mpNextOverlap = nullptr; // next overlap window of frame + mpLastFocusWindow = nullptr; // window for focus restore + mpDlgCtrlDownWindow = nullptr; // window for dialog control + mnEventListenersIteratingCount = 0; + mnChildEventListenersIteratingCount = 0; + mpCursor = nullptr; // cursor + maPointer = PointerStyle::Arrow; + mpVCLXWindow = nullptr; + mpAccessibleInfos = nullptr; + maControlForeground = COL_TRANSPARENT; // no foreground set + maControlBackground = COL_TRANSPARENT; // no background set + mnLeftBorder = 0; // left border + mnTopBorder = 0; // top border + mnRightBorder = 0; // right border + mnBottomBorder = 0; // bottom border + mnWidthRequest = -1; // width request + mnHeightRequest = -1; // height request + mnOptimalWidthCache = -1; // optimal width cache + mnOptimalHeightCache = -1; // optimal height cache + mnX = 0; // X-Position to Parent + mnY = 0; // Y-Position to Parent + mnAbsScreenX = 0; // absolute X-position on screen, used for RTL window positioning + mpChildClipRegion = nullptr; // Child-Clip-Region when ClipChildren + mpPaintRegion = nullptr; // Paint-ClipRegion + mnStyle = 0; // style (init in ImplInitWindow) + mnPrevStyle = 0; // prevstyle (set in SetStyle) + mnExtendedStyle = WindowExtendedStyle::NONE; // extended style (init in ImplInitWindow) + mnType = nType; // type + mnGetFocusFlags = GetFocusFlags::NONE; // Flags for GetFocus()-Call + mnWaitCount = 0; // Wait-Count (>1 == "wait" mouse pointer) + mnPaintFlags = ImplPaintFlags::NONE; // Flags for ImplCallPaint + mnParentClipMode = ParentClipMode::NONE; // Flags for Parent-ClipChildren-Mode + mnActivateMode = ActivateModeFlags::NONE; // Will be converted in System/Overlap-Windows + mnDlgCtrlFlags = DialogControlFlags::NONE; // DialogControl-Flags + meAlwaysInputMode = AlwaysInputNone; // AlwaysEnableInput not called + meHalign = VclAlign::Fill; + meValign = VclAlign::Fill; + mePackType = VclPackType::Start; + mnPadding = 0; + mnGridHeight = 1; + mnGridLeftAttach = -1; + mnGridTopAttach = -1; + mnGridWidth = 1; + mnBorderWidth = 0; + mnMarginLeft = 0; + mnMarginRight = 0; + mnMarginTop = 0; + mnMarginBottom = 0; + mbFrame = false; // true: Window is a frame window + mbBorderWin = false; // true: Window is a border window + mbOverlapWin = false; // true: Window is an overlap window + mbSysWin = false; // true: SystemWindow is the base class + mbDialog = false; // true: Dialog is the base class + mbDockWin = false; // true: DockingWindow is the base class + mbFloatWin = false; // true: FloatingWindow is the base class + mbPushButton = false; // true: PushButton is the base class + mbToolBox = false; // true: ToolBox is the base class + mbMenuFloatingWindow = false; // true: MenuFloatingWindow is the base class + mbToolbarFloatingWindow = false; // true: ImplPopupFloatWin is the base class, used for subtoolbars + mbSplitter = false; // true: Splitter is the base class + mbVisible = false; // true: Show( true ) called + mbOverlapVisible = false; // true: Hide called for visible window from ImplHideAllOverlapWindow() + mbDisabled = false; // true: Enable( false ) called + mbInputDisabled = false; // true: EnableInput( false ) called + mbNoUpdate = false; // true: SetUpdateMode( false ) called + mbNoParentUpdate = false; // true: SetParentUpdateMode( false ) called + mbActive = false; // true: Window Active + mbReallyVisible = false; // true: this and all parents to an overlapped window are visible + mbReallyShown = false; // true: this and all parents to an overlapped window are shown + mbInInitShow = false; // true: we are in InitShow + mbChildPtrOverwrite = false; // true: PointerStyle overwrites Child-Pointer + mbNoPtrVisible = false; // true: ShowPointer( false ) called + mbPaintFrame = false; // true: Paint is visible, but not painted + mbInPaint = false; // true: Inside PaintHdl + mbMouseButtonDown = false; // true: BaseMouseButtonDown called + mbMouseButtonUp = false; // true: BaseMouseButtonUp called + mbKeyInput = false; // true: BaseKeyInput called + mbKeyUp = false; // true: BaseKeyUp called + mbCommand = false; // true: BaseCommand called + mbDefPos = true; // true: Position is not Set + mbDefSize = true; // true: Size is not Set + mbCallMove = true; // true: Move must be called by Show + mbCallResize = true; // true: Resize must be called by Show + mbWaitSystemResize = true; // true: Wait for System-Resize + mbInitWinClipRegion = true; // true: Calc Window Clip Region + mbInitChildRegion = false; // true: InitChildClipRegion + mbWinRegion = false; // true: Window Region + mbClipChildren = false; // true: Child-window should be clipped + mbClipSiblings = false; // true: Adjacent Child-window should be clipped + mbChildTransparent = false; // true: Child-windows are allowed to switch to transparent (incl. Parent-CLIPCHILDREN) + mbPaintTransparent = false; // true: Paints should be executed on the Parent + mbMouseTransparent = false; // true: Window is transparent for Mouse + mbDlgCtrlStart = false; // true: From here on own Dialog-Control + mbFocusVisible = false; // true: Focus Visible + mbUseNativeFocus = false; + mbNativeFocusVisible = false; // true: native Focus Visible + mbInShowFocus = false; // prevent recursion + mbInHideFocus = false; // prevent recursion + mbTrackVisible = false; // true: Tracking Visible + mbControlForeground = false; // true: Foreground-Property set + mbControlBackground = false; // true: Background-Property set + mbAlwaysOnTop = false; // true: always visible for all others windows + mbCompoundControl = false; // true: Composite Control => Listener... + mbCompoundControlHasFocus = false; // true: Composite Control has focus somewhere + mbPaintDisabled = false; // true: Paint should not be executed + mbAllResize = false; // true: Also sent ResizeEvents with 0,0 + mbInDispose = false; // true: We're still in Window::dispose() + mbExtTextInput = false; // true: ExtTextInput-Mode is active + mbInFocusHdl = false; // true: Within GetFocus-Handler + mbCreatedWithToolkit = false; + mbSuppressAccessibilityEvents = false; // true: do not send any accessibility events + mbDrawSelectionBackground = false; // true: draws transparent window background to indicate (toolbox) selection + mbIsInTaskPaneList = false; // true: window was added to the taskpanelist in the topmost system window + mnNativeBackground = ControlPart::NONE; // initialize later, depends on type + mbHelpTextDynamic = false; // true: append help id in HELP_DEBUG case + mbFakeFocusSet = false; // true: pretend as if the window has focus. + mbHexpand = false; + mbVexpand = false; + mbExpand = false; + mbFill = true; + mbSecondary = false; + mbNonHomogeneous = false; + static bool bDoubleBuffer = getenv("VCL_DOUBLEBUFFERING_FORCE_ENABLE"); + mbDoubleBufferingRequested = bDoubleBuffer; // when we are not sure, assume it cannot do double-buffering via RenderContext + mpLOKNotifier = nullptr; + mnLOKWindowId = 0; + mbLOKParentNotifier = false; + mbUseFrameData = false; +} + +WindowImpl::~WindowImpl() +{ + mpChildClipRegion.reset(); + mpAccessibleInfos.reset(); +} + +ImplWinData::ImplWinData() : + mnCursorExtWidth(0), + mbVertical(false), + mnCompositionCharRects(0), + mnTrackFlags(ShowTrackFlags::NONE), + mnIsTopWindow(sal_uInt16(~0)), // not initialized yet, 0/1 will indicate TopWindow (see IsTopWindow()) + mbMouseOver(false), + mbEnableNativeWidget(false) +{ +} + +ImplWinData::~ImplWinData() +{ + mpCompositionCharRects.reset(); +} + +ImplFrameData::ImplFrameData( vcl::Window *pWindow ) + : maPaintIdle( "vcl::Window maPaintIdle" ), + maResizeIdle( "vcl::Window maResizeIdle" ) +{ + ImplSVData* pSVData = ImplGetSVData(); + assert (pSVData->maFrameData.mpFirstFrame.get() != pWindow); + mpNextFrame = pSVData->maFrameData.mpFirstFrame; + pSVData->maFrameData.mpFirstFrame = pWindow; + mpFirstOverlap = nullptr; + mpFocusWin = nullptr; + mpMouseMoveWin = nullptr; + mpMouseDownWin = nullptr; + mpTrackWin = nullptr; + mxFontCollection = pSVData->maGDIData.mxScreenFontList; + mxFontCache = pSVData->maGDIData.mxScreenFontCache; + mnFocusId = nullptr; + mnMouseMoveId = nullptr; + mnLastMouseX = -1; + mnLastMouseY = -1; + mnBeforeLastMouseX = -1; + mnBeforeLastMouseY = -1; + mnFirstMouseX = -1; + mnFirstMouseY = -1; + mnLastMouseWinX = -1; + mnLastMouseWinY = -1; + mnModalMode = 0; + mnMouseDownTime = 0; + mnClickCount = 0; + mnFirstMouseCode = 0; + mnMouseCode = 0; + mnMouseMode = MouseEventModifiers::NONE; + mbHasFocus = false; + mbInMouseMove = false; + mbMouseIn = false; + mbStartDragCalled = false; + mbNeedSysWindow = false; + mbMinimized = false; + mbStartFocusState = false; + mbInSysObjFocusHdl = false; + mbInSysObjToTopHdl = false; + mbSysObjFocus = false; + maPaintIdle.SetPriority( TaskPriority::REPAINT ); + maPaintIdle.SetInvokeHandler( LINK( pWindow, vcl::Window, ImplHandlePaintHdl ) ); + maResizeIdle.SetPriority( TaskPriority::RESIZE ); + maResizeIdle.SetInvokeHandler( LINK( pWindow, vcl::Window, ImplHandleResizeTimerHdl ) ); + mbInternalDragGestureRecognizer = false; + mbDragging = false; + mbInBufferedPaint = false; + mnDPIX = 96; + mnDPIY = 96; + mnTouchPanPosition = -1; +} + +namespace vcl { + +bool WindowOutputDevice::AcquireGraphics() const +{ + DBG_TESTSOLARMUTEX(); + + if (isDisposed()) + return false; + + if (mpGraphics) + return true; + + mbInitLineColor = true; + mbInitFillColor = true; + mbInitFont = true; + mbInitTextColor = true; + mbInitClipRegion = true; + + ImplSVData* pSVData = ImplGetSVData(); + + mpGraphics = mxOwnerWindow->mpWindowImpl->mpFrame->AcquireGraphics(); + // try harder if no wingraphics was available directly + if ( !mpGraphics ) + { + // find another output device in the same frame + vcl::WindowOutputDevice* pReleaseOutDev = pSVData->maGDIData.mpLastWinGraphics.get(); + while ( pReleaseOutDev ) + { + if ( pReleaseOutDev->mxOwnerWindow && pReleaseOutDev->mxOwnerWindow->mpWindowImpl->mpFrame == mxOwnerWindow->mpWindowImpl->mpFrame ) + break; + pReleaseOutDev = static_cast<vcl::WindowOutputDevice*>(pReleaseOutDev->mpPrevGraphics.get()); + } + + if ( pReleaseOutDev ) + { + // steal the wingraphics from the other outdev + mpGraphics = pReleaseOutDev->mpGraphics; + pReleaseOutDev->ReleaseGraphics( false ); + } + else + { + // if needed retry after releasing least recently used wingraphics + while ( !mpGraphics ) + { + if ( !pSVData->maGDIData.mpLastWinGraphics ) + break; + pSVData->maGDIData.mpLastWinGraphics->ReleaseGraphics(); + mpGraphics = mxOwnerWindow->mpWindowImpl->mpFrame->AcquireGraphics(); + } + } + } + + if ( mpGraphics ) + { + // update global LRU list of wingraphics + mpNextGraphics = pSVData->maGDIData.mpFirstWinGraphics.get(); + pSVData->maGDIData.mpFirstWinGraphics = const_cast<vcl::WindowOutputDevice*>(this); + if ( mpNextGraphics ) + mpNextGraphics->mpPrevGraphics = const_cast<vcl::WindowOutputDevice*>(this); + if ( !pSVData->maGDIData.mpLastWinGraphics ) + pSVData->maGDIData.mpLastWinGraphics = const_cast<vcl::WindowOutputDevice*>(this); + + mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp ); + mpGraphics->setAntiAlias(bool(mnAntialiasing & AntialiasingFlags::Enable)); + } + + return mpGraphics != nullptr; +} + +void WindowOutputDevice::ReleaseGraphics( bool bRelease ) +{ + DBG_TESTSOLARMUTEX(); + + if ( !mpGraphics ) + return; + + // release the fonts of the physically released graphics device + if( bRelease ) + ImplReleaseFonts(); + + ImplSVData* pSVData = ImplGetSVData(); + + vcl::Window* pWindow = mxOwnerWindow.get(); + if (!pWindow) + return; + + if ( bRelease ) + pWindow->mpWindowImpl->mpFrame->ReleaseGraphics( mpGraphics ); + // remove from global LRU list of window graphics + if ( mpPrevGraphics ) + mpPrevGraphics->mpNextGraphics = mpNextGraphics; + else + pSVData->maGDIData.mpFirstWinGraphics = static_cast<vcl::WindowOutputDevice*>(mpNextGraphics.get()); + if ( mpNextGraphics ) + mpNextGraphics->mpPrevGraphics = mpPrevGraphics; + else + pSVData->maGDIData.mpLastWinGraphics = static_cast<vcl::WindowOutputDevice*>(mpPrevGraphics.get()); + + mpGraphics = nullptr; + mpPrevGraphics = nullptr; + mpNextGraphics = nullptr; +} + +static sal_Int32 CountDPIScaleFactor(sal_Int32 nDPI) +{ +#ifndef MACOSX + // Setting of HiDPI is unfortunately all only a heuristic; and to add + // insult to an injury, the system is constantly lying to us about + // the DPI and whatnot + // eg. fdo#77059 - set the value from which we do consider the + // screen HiDPI to greater than 168 + if (nDPI > 216) // 96 * 2 + 96 / 4 + return 250; + else if (nDPI > 168) // 96 * 2 - 96 / 4 + return 200; + else if (nDPI > 120) // 96 * 1.5 - 96 / 4 + return 150; +#else + (void)nDPI; +#endif + + return 100; +} + +void Window::ImplInit( vcl::Window* pParent, WinBits nStyle, SystemParentData* pSystemParentData ) +{ + SAL_WARN_IF( !mpWindowImpl->mbFrame && !pParent && GetType() != WindowType::FIXEDIMAGE, "vcl.window", + "Window::Window(): pParent == NULL" ); + + ImplSVData* pSVData = ImplGetSVData(); + vcl::Window* pRealParent = pParent; + + // inherit 3D look + if ( !mpWindowImpl->mbOverlapWin && pParent && (pParent->GetStyle() & WB_3DLOOK) ) + nStyle |= WB_3DLOOK; + + // create border window if necessary + if ( !mpWindowImpl->mbFrame && !mpWindowImpl->mbBorderWin && !mpWindowImpl->mpBorderWindow + && (nStyle & (WB_BORDER | WB_SYSTEMCHILDWINDOW) ) ) + { + BorderWindowStyle nBorderTypeStyle = BorderWindowStyle::NONE; + if( nStyle & WB_SYSTEMCHILDWINDOW ) + { + // handle WB_SYSTEMCHILDWINDOW + // these should be analogous to a top level frame; meaning they + // should have a border window with style BorderWindowStyle::Frame + // which controls their size + nBorderTypeStyle |= BorderWindowStyle::Frame; + nStyle |= WB_BORDER; + } + VclPtrInstance<ImplBorderWindow> pBorderWin( pParent, nStyle & (WB_BORDER | WB_DIALOGCONTROL | WB_NODIALOGCONTROL), nBorderTypeStyle ); + static_cast<vcl::Window*>(pBorderWin)->mpWindowImpl->mpClientWindow = this; + pBorderWin->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder ); + mpWindowImpl->mpBorderWindow = pBorderWin; + pParent = mpWindowImpl->mpBorderWindow; + } + else if( !mpWindowImpl->mbFrame && ! pParent ) + { + mpWindowImpl->mbOverlapWin = true; + mpWindowImpl->mbFrame = true; + } + + // insert window in list + ImplInsertWindow( pParent ); + mpWindowImpl->mnStyle = nStyle; + + if( pParent && ! mpWindowImpl->mbFrame ) + mpWindowImpl->mxOutDev->mbEnableRTL = AllSettings::GetLayoutRTL(); + + // test for frame creation + if ( mpWindowImpl->mbFrame ) + { + // create frame + SalFrameStyleFlags nFrameStyle = SalFrameStyleFlags::NONE; + + if ( nStyle & WB_MOVEABLE ) + nFrameStyle |= SalFrameStyleFlags::MOVEABLE; + if ( nStyle & WB_SIZEABLE ) + nFrameStyle |= SalFrameStyleFlags::SIZEABLE; + if ( nStyle & WB_CLOSEABLE ) + nFrameStyle |= SalFrameStyleFlags::CLOSEABLE; + if ( nStyle & WB_APP ) + nFrameStyle |= SalFrameStyleFlags::DEFAULT; + // check for undecorated floating window + if( // 1. floating windows that are not moveable/sizeable (only closeable allowed) + ( !(nFrameStyle & ~SalFrameStyleFlags::CLOSEABLE) && + ( mpWindowImpl->mbFloatWin || ((GetType() == WindowType::BORDERWINDOW) && static_cast<ImplBorderWindow*>(this)->mbFloatWindow) || (nStyle & WB_SYSTEMFLOATWIN) ) ) || + // 2. borderwindows of floaters with ownerdraw decoration + ((GetType() == WindowType::BORDERWINDOW) && static_cast<ImplBorderWindow*>(this)->mbFloatWindow && (nStyle & WB_OWNERDRAWDECORATION) ) ) + { + nFrameStyle = SalFrameStyleFlags::FLOAT; + if( nStyle & WB_OWNERDRAWDECORATION ) + nFrameStyle |= SalFrameStyleFlags::OWNERDRAWDECORATION | SalFrameStyleFlags::NOSHADOW; + } + else if( mpWindowImpl->mbFloatWin ) + nFrameStyle |= SalFrameStyleFlags::TOOLWINDOW; + + if( nStyle & WB_INTROWIN ) + nFrameStyle |= SalFrameStyleFlags::INTRO; + if( nStyle & WB_TOOLTIPWIN ) + nFrameStyle |= SalFrameStyleFlags::TOOLTIP; + + if( nStyle & WB_NOSHADOW ) + nFrameStyle |= SalFrameStyleFlags::NOSHADOW; + + if( nStyle & WB_SYSTEMCHILDWINDOW ) + nFrameStyle |= SalFrameStyleFlags::SYSTEMCHILD; + + switch (mpWindowImpl->mnType) + { + case WindowType::DIALOG: + case WindowType::TABDIALOG: + case WindowType::MODELESSDIALOG: + case WindowType::MESSBOX: + case WindowType::INFOBOX: + case WindowType::WARNINGBOX: + case WindowType::ERRORBOX: + case WindowType::QUERYBOX: + nFrameStyle |= SalFrameStyleFlags::DIALOG; + break; + default: + break; + } + + // tdf#144624 for the DefaultWindow, which is never visible, don't + // create an icon for it so construction of a DefaultWindow cannot + // trigger creation of a VirtualDevice which itself requires a + // DefaultWindow to exist + if( nStyle & WB_DEFAULTWIN ) + nFrameStyle |= SalFrameStyleFlags::NOICON; + + SalFrame* pParentFrame = nullptr; + if ( pParent ) + pParentFrame = pParent->mpWindowImpl->mpFrame; + SalFrame* pFrame; + if ( pSystemParentData ) + pFrame = pSVData->mpDefInst->CreateChildFrame( pSystemParentData, nFrameStyle | SalFrameStyleFlags::PLUG ); + else + pFrame = pSVData->mpDefInst->CreateFrame( pParentFrame, nFrameStyle ); + if ( !pFrame ) + { + // do not abort but throw an exception, may be the current thread terminates anyway (plugin-scenario) + throw RuntimeException( + "Could not create system window!", + Reference< XInterface >() ); + } + + pFrame->SetCallback( this, ImplWindowFrameProc ); + + // set window frame data + mpWindowImpl->mpFrameData = new ImplFrameData( this ); + mpWindowImpl->mpFrame = pFrame; + mpWindowImpl->mpFrameWindow = this; + mpWindowImpl->mpOverlapWindow = this; + + if (!(nStyle & WB_DEFAULTWIN) && mpWindowImpl->mbDoubleBufferingRequested) + RequestDoubleBuffering(true); + + if ( pRealParent && IsTopWindow() ) + { + ImplWinData* pParentWinData = pRealParent->ImplGetWinData(); + pParentWinData->maTopWindowChildren.emplace_back(this ); + } + } + + // init data + mpWindowImpl->mpRealParent = pRealParent; + + // #99318: make sure fontcache and list is available before call to SetSettings + mpWindowImpl->mxOutDev->mxFontCollection = mpWindowImpl->mpFrameData->mxFontCollection; + mpWindowImpl->mxOutDev->mxFontCache = mpWindowImpl->mpFrameData->mxFontCache; + + if ( mpWindowImpl->mbFrame ) + { + if ( pParent ) + { + mpWindowImpl->mpFrameData->mnDPIX = pParent->mpWindowImpl->mpFrameData->mnDPIX; + mpWindowImpl->mpFrameData->mnDPIY = pParent->mpWindowImpl->mpFrameData->mnDPIY; + } + else + { + OutputDevice *pOutDev = GetOutDev(); + if ( pOutDev->AcquireGraphics() ) + { + mpWindowImpl->mxOutDev->mpGraphics->GetResolution( mpWindowImpl->mpFrameData->mnDPIX, mpWindowImpl->mpFrameData->mnDPIY ); + } + } + + // add ownerdraw decorated frame windows to list in the top-most frame window + // so they can be hidden on lose focus + if( nStyle & WB_OWNERDRAWDECORATION ) + ImplGetOwnerDrawList().emplace_back(this ); + + // delay settings initialization until first "real" frame + // this relies on the IntroWindow not needing any system settings + if ( !pSVData->maAppData.mbSettingsInit && + ! (nStyle & (WB_INTROWIN|WB_DEFAULTWIN)) + ) + { + // side effect: ImplUpdateGlobalSettings does an ImplGetFrame()->UpdateSettings + ImplUpdateGlobalSettings( *pSVData->maAppData.mxSettings ); + mpWindowImpl->mxOutDev->SetSettings( *pSVData->maAppData.mxSettings ); + pSVData->maAppData.mbSettingsInit = true; + } + + // If we create a Window with default size, query this + // size directly, because we want resize all Controls to + // the correct size before we display the window + if ( nStyle & (WB_MOVEABLE | WB_SIZEABLE | WB_APP) ) + mpWindowImpl->mpFrame->GetClientSize( mpWindowImpl->mxOutDev->mnOutWidth, mpWindowImpl->mxOutDev->mnOutHeight ); + } + else + { + if ( pParent ) + { + if ( !ImplIsOverlapWindow() ) + { + mpWindowImpl->mbDisabled = pParent->mpWindowImpl->mbDisabled; + mpWindowImpl->mbInputDisabled = pParent->mpWindowImpl->mbInputDisabled; + mpWindowImpl->meAlwaysInputMode = pParent->mpWindowImpl->meAlwaysInputMode; + } + + if (!utl::ConfigManager::IsFuzzing()) + { + // we don't want to call the WindowOutputDevice override of this because + // it calls back into us. + mpWindowImpl->mxOutDev->OutputDevice::SetSettings( pParent->GetSettings() ); + } + } + + } + + // setup the scale factor for HiDPI displays + mpWindowImpl->mxOutDev->mnDPIScalePercentage = CountDPIScaleFactor(mpWindowImpl->mpFrameData->mnDPIY); + mpWindowImpl->mxOutDev->mnDPIX = mpWindowImpl->mpFrameData->mnDPIX; + mpWindowImpl->mxOutDev->mnDPIY = mpWindowImpl->mpFrameData->mnDPIY; + + if (!utl::ConfigManager::IsFuzzing()) + { + const StyleSettings& rStyleSettings = mpWindowImpl->mxOutDev->mxSettings->GetStyleSettings(); + mpWindowImpl->mxOutDev->maFont = rStyleSettings.GetAppFont(); + + if ( nStyle & WB_3DLOOK ) + { + SetTextColor( rStyleSettings.GetButtonTextColor() ); + SetBackground( Wallpaper( rStyleSettings.GetFaceColor() ) ); + } + else + { + SetTextColor( rStyleSettings.GetWindowTextColor() ); + SetBackground( Wallpaper( rStyleSettings.GetWindowColor() ) ); + } + } + else + { + mpWindowImpl->mxOutDev->maFont = OutputDevice::GetDefaultFont( DefaultFontType::FIXED, LANGUAGE_ENGLISH_US, GetDefaultFontFlags::NONE ); + } + + ImplPointToLogic(*GetOutDev(), mpWindowImpl->mxOutDev->maFont); + + (void)ImplUpdatePos(); + + // calculate app font res (except for the Intro Window or the default window) + if ( mpWindowImpl->mbFrame && !pSVData->maGDIData.mnAppFontX && ! (nStyle & (WB_INTROWIN|WB_DEFAULTWIN)) ) + ImplInitAppFontData( this ); +} + +void Window::ImplInitAppFontData( vcl::Window const * pWindow ) +{ + ImplSVData* pSVData = ImplGetSVData(); + tools::Long nTextHeight = pWindow->GetTextHeight(); + tools::Long nTextWidth = pWindow->approximate_char_width() * 8; + tools::Long nSymHeight = nTextHeight*4; + // Make the basis wider if the font is too narrow + // such that the dialog looks symmetrical and does not become too narrow. + // Add some extra space when the dialog has the same width, + // as a little more space is better. + if ( nSymHeight > nTextWidth ) + nTextWidth = nSymHeight; + else if ( nSymHeight+5 > nTextWidth ) + nTextWidth = nSymHeight+5; + pSVData->maGDIData.mnAppFontX = nTextWidth * 10 / 8; + pSVData->maGDIData.mnAppFontY = nTextHeight * 10; + +#ifdef MACOSX + // FIXME: this is currently only on macOS, check with other + // platforms + if( pSVData->maNWFData.mbNoFocusRects ) + { + // try to find out whether there is a large correction + // of control sizes, if yes, make app font scalings larger + // so dialog positioning is not completely off + ImplControlValue aControlValue; + tools::Rectangle aCtrlRegion( Point(), Size( nTextWidth < 10 ? 10 : nTextWidth, nTextHeight < 10 ? 10 : nTextHeight ) ); + tools::Rectangle aBoundingRgn( aCtrlRegion ); + tools::Rectangle aContentRgn( aCtrlRegion ); + if( pWindow->GetNativeControlRegion( ControlType::Editbox, ControlPart::Entire, aCtrlRegion, + ControlState::ENABLED, aControlValue, + aBoundingRgn, aContentRgn ) ) + { + // comment: the magical +6 is for the extra border in bordered + // (which is the standard) edit fields + if( aContentRgn.GetHeight() - nTextHeight > (nTextHeight+4)/4 ) + pSVData->maGDIData.mnAppFontY = (aContentRgn.GetHeight()-4) * 10; + } + } +#endif +} + +ImplWinData* Window::ImplGetWinData() const +{ + if (!mpWindowImpl->mpWinData) + { + static const char* pNoNWF = getenv( "SAL_NO_NWF" ); + + const_cast<vcl::Window*>(this)->mpWindowImpl->mpWinData.reset(new ImplWinData); + mpWindowImpl->mpWinData->mbEnableNativeWidget = !(pNoNWF && *pNoNWF); // true: try to draw this control with native theme API + } + + return mpWindowImpl->mpWinData.get(); +} + + +void WindowOutputDevice::CopyDeviceArea( SalTwoRect& aPosAry, bool bWindowInvalidate ) +{ + if (aPosAry.mnSrcWidth == 0 || aPosAry.mnSrcHeight == 0 || aPosAry.mnDestWidth == 0 || aPosAry.mnDestHeight == 0) + return; + + if (bWindowInvalidate) + { + const tools::Rectangle aSrcRect(Point(aPosAry.mnSrcX, aPosAry.mnSrcY), + Size(aPosAry.mnSrcWidth, aPosAry.mnSrcHeight)); + + mxOwnerWindow->ImplMoveAllInvalidateRegions(aSrcRect, + aPosAry.mnDestX-aPosAry.mnSrcX, + aPosAry.mnDestY-aPosAry.mnSrcY, + false); + + mpGraphics->CopyArea(aPosAry.mnDestX, aPosAry.mnDestY, + aPosAry.mnSrcX, aPosAry.mnSrcY, + aPosAry.mnSrcWidth, aPosAry.mnSrcHeight, + *this); + + return; + } + + OutputDevice::CopyDeviceArea(aPosAry, bWindowInvalidate); +} + +const OutputDevice* WindowOutputDevice::DrawOutDevDirectCheck(const OutputDevice& rSrcDev) const +{ + const OutputDevice* pSrcDevChecked; + if ( this == &rSrcDev ) + pSrcDevChecked = nullptr; + else if (GetOutDevType() != rSrcDev.GetOutDevType()) + pSrcDevChecked = &rSrcDev; + else if (mxOwnerWindow->mpWindowImpl->mpFrameWindow == static_cast<const vcl::WindowOutputDevice&>(rSrcDev).mxOwnerWindow->mpWindowImpl->mpFrameWindow) + pSrcDevChecked = nullptr; + else + pSrcDevChecked = &rSrcDev; + + return pSrcDevChecked; +} + +void WindowOutputDevice::DrawOutDevDirectProcess( const OutputDevice& rSrcDev, SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) +{ + if (pSrcGraphics) + mpGraphics->CopyBits(rPosAry, *pSrcGraphics, *this, rSrcDev); + else + mpGraphics->CopyBits(rPosAry, *this); +} + +SalGraphics* Window::ImplGetFrameGraphics() const +{ + if ( mpWindowImpl->mpFrameWindow->GetOutDev()->mpGraphics ) + { + mpWindowImpl->mpFrameWindow->GetOutDev()->mbInitClipRegion = true; + } + else + { + OutputDevice* pFrameWinOutDev = mpWindowImpl->mpFrameWindow->GetOutDev(); + if ( ! pFrameWinOutDev->AcquireGraphics() ) + { + return nullptr; + } + } + mpWindowImpl->mpFrameWindow->GetOutDev()->mpGraphics->ResetClipRegion(); + return mpWindowImpl->mpFrameWindow->GetOutDev()->mpGraphics; +} + +void Window::ImplSetReallyVisible() +{ + // #i43594# it is possible that INITSHOW was never send, because the visibility state changed between + // ImplCallInitShow() and ImplSetReallyVisible() when called from Show() + // mbReallyShown is a useful indicator + if( !mpWindowImpl->mbReallyShown ) + ImplCallInitShow(); + + bool bBecameReallyVisible = !mpWindowImpl->mbReallyVisible; + + GetOutDev()->mbDevOutput = true; + mpWindowImpl->mbReallyVisible = true; + mpWindowImpl->mbReallyShown = true; + + // the SHOW/HIDE events serve as indicators to send child creation/destroy events to the access bridge. + // For this, the data member of the event must not be NULL. + // Previously, we did this in Window::Show, but there some events got lost in certain situations. Now + // we're doing it when the visibility really changes + if( bBecameReallyVisible && ImplIsAccessibleCandidate() ) + CallEventListeners( VclEventId::WindowShow, this ); + // TODO. It's kind of a hack that we're re-using the VclEventId::WindowShow. Normally, we should + // introduce another event which explicitly triggers the Accessibility implementations. + + vcl::Window* pWindow = mpWindowImpl->mpFirstOverlap; + while ( pWindow ) + { + if ( pWindow->mpWindowImpl->mbVisible ) + pWindow->ImplSetReallyVisible(); + pWindow = pWindow->mpWindowImpl->mpNext; + } + + pWindow = mpWindowImpl->mpFirstChild; + while ( pWindow ) + { + if ( pWindow->mpWindowImpl->mbVisible ) + pWindow->ImplSetReallyVisible(); + pWindow = pWindow->mpWindowImpl->mpNext; + } +} + +void Window::ImplInitResolutionSettings() +{ + // recalculate AppFont-resolution and DPI-resolution + if (mpWindowImpl->mbFrame) + { + GetOutDev()->mnDPIX = mpWindowImpl->mpFrameData->mnDPIX; + GetOutDev()->mnDPIY = mpWindowImpl->mpFrameData->mnDPIY; + + // setup the scale factor for HiDPI displays + GetOutDev()->mnDPIScalePercentage = CountDPIScaleFactor(mpWindowImpl->mpFrameData->mnDPIY); + const StyleSettings& rStyleSettings = GetOutDev()->mxSettings->GetStyleSettings(); + SetPointFont(*GetOutDev(), rStyleSettings.GetAppFont()); + } + else if ( mpWindowImpl->mpParent ) + { + GetOutDev()->mnDPIX = mpWindowImpl->mpParent->GetOutDev()->mnDPIX; + GetOutDev()->mnDPIY = mpWindowImpl->mpParent->GetOutDev()->mnDPIY; + GetOutDev()->mnDPIScalePercentage = mpWindowImpl->mpParent->GetOutDev()->mnDPIScalePercentage; + } + + // update the recalculated values for logical units + // and also tools belonging to the values + if (IsMapModeEnabled()) + { + MapMode aMapMode = GetMapMode(); + SetMapMode(); + SetMapMode( aMapMode ); + } +} + +void Window::ImplPointToLogic(vcl::RenderContext const & rRenderContext, vcl::Font& rFont) const +{ + Size aSize = rFont.GetFontSize(); + + if (aSize.Width()) + { + aSize.setWidth( aSize.Width() * ( mpWindowImpl->mpFrameData->mnDPIX) ); + aSize.AdjustWidth(72 / 2 ); + aSize.setWidth( aSize.Width() / 72 ); + } + aSize.setHeight( aSize.Height() * ( mpWindowImpl->mpFrameData->mnDPIY) ); + aSize.AdjustHeight(72/2 ); + aSize.setHeight( aSize.Height() / 72 ); + + if (rRenderContext.IsMapModeEnabled()) + aSize = rRenderContext.PixelToLogic(aSize); + + rFont.SetFontSize(aSize); +} + +void Window::ImplLogicToPoint(vcl::RenderContext const & rRenderContext, vcl::Font& rFont) const +{ + Size aSize = rFont.GetFontSize(); + + if (rRenderContext.IsMapModeEnabled()) + aSize = rRenderContext.LogicToPixel(aSize); + + if (aSize.Width()) + { + aSize.setWidth( aSize.Width() * 72 ); + aSize.AdjustWidth(mpWindowImpl->mpFrameData->mnDPIX / 2 ); + aSize.setWidth( aSize.Width() / ( mpWindowImpl->mpFrameData->mnDPIX) ); + } + aSize.setHeight( aSize.Height() * 72 ); + aSize.AdjustHeight(mpWindowImpl->mpFrameData->mnDPIY / 2 ); + aSize.setHeight( aSize.Height() / ( mpWindowImpl->mpFrameData->mnDPIY) ); + + rFont.SetFontSize(aSize); +} + +bool Window::ImplUpdatePos() +{ + bool bSysChild = false; + + if ( ImplIsOverlapWindow() ) + { + GetOutDev()->mnOutOffX = mpWindowImpl->mnX; + GetOutDev()->mnOutOffY = mpWindowImpl->mnY; + } + else + { + vcl::Window* pParent = ImplGetParent(); + + GetOutDev()->mnOutOffX = mpWindowImpl->mnX + pParent->GetOutDev()->mnOutOffX; + GetOutDev()->mnOutOffY = mpWindowImpl->mnY + pParent->GetOutDev()->mnOutOffY; + } + + VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild; + while ( pChild ) + { + if ( pChild->ImplUpdatePos() ) + bSysChild = true; + pChild = pChild->mpWindowImpl->mpNext; + } + + if ( mpWindowImpl->mpSysObj ) + bSysChild = true; + + return bSysChild; +} + +void Window::ImplUpdateSysObjPos() +{ + if ( mpWindowImpl->mpSysObj ) + mpWindowImpl->mpSysObj->SetPosSize( GetOutDev()->mnOutOffX, GetOutDev()->mnOutOffY, GetOutDev()->mnOutWidth, GetOutDev()->mnOutHeight ); + + VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild; + while ( pChild ) + { + pChild->ImplUpdateSysObjPos(); + pChild = pChild->mpWindowImpl->mpNext; + } +} + +void Window::ImplPosSizeWindow( tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, PosSizeFlags nFlags ) +{ + bool bNewPos = false; + bool bNewSize = false; + bool bCopyBits = false; + tools::Long nOldOutOffX = GetOutDev()->mnOutOffX; + tools::Long nOldOutOffY = GetOutDev()->mnOutOffY; + tools::Long nOldOutWidth = GetOutDev()->mnOutWidth; + tools::Long nOldOutHeight = GetOutDev()->mnOutHeight; + std::unique_ptr<vcl::Region> pOverlapRegion; + std::unique_ptr<vcl::Region> pOldRegion; + + if ( IsReallyVisible() ) + { + tools::Rectangle aOldWinRect( Point( nOldOutOffX, nOldOutOffY ), + Size( nOldOutWidth, nOldOutHeight ) ); + pOldRegion.reset( new vcl::Region( aOldWinRect ) ); + if ( mpWindowImpl->mbWinRegion ) + pOldRegion->Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) ); + + if ( GetOutDev()->mnOutWidth && GetOutDev()->mnOutHeight && !mpWindowImpl->mbPaintTransparent && + !mpWindowImpl->mbInitWinClipRegion && !mpWindowImpl->maWinClipRegion.IsEmpty() && + !HasPaintEvent() ) + bCopyBits = true; + } + + bool bnXRecycled = false; // avoid duplicate mirroring in RTL case + if ( nFlags & PosSizeFlags::Width ) + { + if(!( nFlags & PosSizeFlags::X )) + { + nX = mpWindowImpl->mnX; + nFlags |= PosSizeFlags::X; + bnXRecycled = true; // we're using a mnX which was already mirrored in RTL case + } + + if ( nWidth < 0 ) + nWidth = 0; + if ( nWidth != GetOutDev()->mnOutWidth ) + { + GetOutDev()->mnOutWidth = nWidth; + bNewSize = true; + bCopyBits = false; + } + } + if ( nFlags & PosSizeFlags::Height ) + { + if ( nHeight < 0 ) + nHeight = 0; + if ( nHeight != GetOutDev()->mnOutHeight ) + { + GetOutDev()->mnOutHeight = nHeight; + bNewSize = true; + bCopyBits = false; + } + } + + if ( nFlags & PosSizeFlags::X ) + { + tools::Long nOrgX = nX; + Point aPtDev( Point( nX+GetOutDev()->mnOutOffX, 0 ) ); + OutputDevice *pOutDev = GetOutDev(); + if( pOutDev->HasMirroredGraphics() ) + { + aPtDev.setX( GetOutDev()->mpGraphics->mirror2( aPtDev.X(), *GetOutDev() ) ); + + // #106948# always mirror our pos if our parent is not mirroring, even + // if we are also not mirroring + // RTL: check if parent is in different coordinates + if( !bnXRecycled && mpWindowImpl->mpParent && !mpWindowImpl->mpParent->mpWindowImpl->mbFrame && mpWindowImpl->mpParent->GetOutDev()->ImplIsAntiparallel() ) + { + nX = mpWindowImpl->mpParent->GetOutDev()->mnOutWidth - GetOutDev()->mnOutWidth - nX; + } + /* #i99166# An LTR window in RTL UI that gets sized only would be + expected to not moved its upper left point + */ + if( bnXRecycled ) + { + if( GetOutDev()->ImplIsAntiparallel() ) + { + aPtDev.setX( mpWindowImpl->mnAbsScreenX ); + nOrgX = mpWindowImpl->maPos.X(); + } + } + } + else if( !bnXRecycled && mpWindowImpl->mpParent && !mpWindowImpl->mpParent->mpWindowImpl->mbFrame && mpWindowImpl->mpParent->GetOutDev()->ImplIsAntiparallel() ) + { + // mirrored window in LTR UI + nX = mpWindowImpl->mpParent->GetOutDev()->mnOutWidth - GetOutDev()->mnOutWidth - nX; + } + + // check maPos as well, as it could have been changed for client windows (ImplCallMove()) + if ( mpWindowImpl->mnAbsScreenX != aPtDev.X() || nX != mpWindowImpl->mnX || nOrgX != mpWindowImpl->maPos.X() ) + { + if ( bCopyBits && !pOverlapRegion ) + { + pOverlapRegion.reset( new vcl::Region() ); + ImplCalcOverlapRegion( GetOutputRectPixel(), + *pOverlapRegion, false, true ); + } + mpWindowImpl->mnX = nX; + mpWindowImpl->maPos.setX( nOrgX ); + mpWindowImpl->mnAbsScreenX = aPtDev.X(); + bNewPos = true; + } + } + if ( nFlags & PosSizeFlags::Y ) + { + // check maPos as well, as it could have been changed for client windows (ImplCallMove()) + if ( nY != mpWindowImpl->mnY || nY != mpWindowImpl->maPos.Y() ) + { + if ( bCopyBits && !pOverlapRegion ) + { + pOverlapRegion.reset( new vcl::Region() ); + ImplCalcOverlapRegion( GetOutputRectPixel(), + *pOverlapRegion, false, true ); + } + mpWindowImpl->mnY = nY; + mpWindowImpl->maPos.setY( nY ); + bNewPos = true; + } + } + + if ( !(bNewPos || bNewSize) ) + return; + + bool bUpdateSysObjPos = false; + if ( bNewPos ) + bUpdateSysObjPos = ImplUpdatePos(); + + // the borderwindow always specifies the position for its client window + if ( mpWindowImpl->mpBorderWindow ) + mpWindowImpl->maPos = mpWindowImpl->mpBorderWindow->mpWindowImpl->maPos; + + if ( mpWindowImpl->mpClientWindow ) + { + mpWindowImpl->mpClientWindow->ImplPosSizeWindow( mpWindowImpl->mpClientWindow->mpWindowImpl->mnLeftBorder, + mpWindowImpl->mpClientWindow->mpWindowImpl->mnTopBorder, + GetOutDev()->mnOutWidth - mpWindowImpl->mpClientWindow->mpWindowImpl->mnLeftBorder-mpWindowImpl->mpClientWindow->mpWindowImpl->mnRightBorder, + GetOutDev()->mnOutHeight - mpWindowImpl->mpClientWindow->mpWindowImpl->mnTopBorder-mpWindowImpl->mpClientWindow->mpWindowImpl->mnBottomBorder, + PosSizeFlags::X | PosSizeFlags::Y | + PosSizeFlags::Width | PosSizeFlags::Height ); + // If we have a client window, then this is the position + // of the Application's floating windows + mpWindowImpl->mpClientWindow->mpWindowImpl->maPos = mpWindowImpl->maPos; + if ( bNewPos ) + { + if ( mpWindowImpl->mpClientWindow->IsVisible() ) + { + mpWindowImpl->mpClientWindow->ImplCallMove(); + } + else + { + mpWindowImpl->mpClientWindow->mpWindowImpl->mbCallMove = true; + } + } + } + + // Move()/Resize() will be called only for Show(), such that + // at least one is called before Show() + if ( IsVisible() ) + { + if ( bNewPos ) + { + ImplCallMove(); + } + if ( bNewSize ) + { + ImplCallResize(); + } + } + else + { + if ( bNewPos ) + mpWindowImpl->mbCallMove = true; + if ( bNewSize ) + mpWindowImpl->mbCallResize = true; + } + + bool bUpdateSysObjClip = false; + if ( IsReallyVisible() ) + { + if ( bNewPos || bNewSize ) + { + // set Clip-Flag + bUpdateSysObjClip = !ImplSetClipFlag( true ); + } + + // invalidate window content ? + if ( bNewPos || (GetOutDev()->mnOutWidth > nOldOutWidth) || (GetOutDev()->mnOutHeight > nOldOutHeight) ) + { + if ( bNewPos ) + { + bool bInvalidate = false; + bool bParentPaint = true; + if ( !ImplIsOverlapWindow() ) + bParentPaint = mpWindowImpl->mpParent->IsPaintEnabled(); + if ( bCopyBits && bParentPaint && !HasPaintEvent() ) + { + vcl::Region aRegion( GetOutputRectPixel() ); + if ( mpWindowImpl->mbWinRegion ) + aRegion.Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) ); + ImplClipBoundaries( aRegion, false, true ); + if ( !pOverlapRegion->IsEmpty() ) + { + pOverlapRegion->Move( GetOutDev()->mnOutOffX - nOldOutOffX, GetOutDev()->mnOutOffY - nOldOutOffY ); + aRegion.Exclude( *pOverlapRegion ); + } + if ( !aRegion.IsEmpty() ) + { + // adapt Paint areas + ImplMoveAllInvalidateRegions( tools::Rectangle( Point( nOldOutOffX, nOldOutOffY ), + Size( nOldOutWidth, nOldOutHeight ) ), + GetOutDev()->mnOutOffX - nOldOutOffX, GetOutDev()->mnOutOffY - nOldOutOffY, + true ); + SalGraphics* pGraphics = ImplGetFrameGraphics(); + if ( pGraphics ) + { + + OutputDevice *pOutDev = GetOutDev(); + const bool bSelectClipRegion = pOutDev->SelectClipRegion( aRegion, pGraphics ); + if ( bSelectClipRegion ) + { + pGraphics->CopyArea( GetOutDev()->mnOutOffX, GetOutDev()->mnOutOffY, + nOldOutOffX, nOldOutOffY, + nOldOutWidth, nOldOutHeight, + *GetOutDev() ); + } + else + bInvalidate = true; + } + else + bInvalidate = true; + if ( !bInvalidate ) + { + if ( !pOverlapRegion->IsEmpty() ) + ImplInvalidateFrameRegion( pOverlapRegion.get(), InvalidateFlags::Children ); + } + } + else + bInvalidate = true; + } + else + bInvalidate = true; + if ( bInvalidate ) + ImplInvalidateFrameRegion( nullptr, InvalidateFlags::Children ); + } + else + { + vcl::Region aRegion( GetOutputRectPixel() ); + aRegion.Exclude( *pOldRegion ); + if ( mpWindowImpl->mbWinRegion ) + aRegion.Intersect( GetOutDev()->ImplPixelToDevicePixel( mpWindowImpl->maWinRegion ) ); + ImplClipBoundaries( aRegion, false, true ); + if ( !aRegion.IsEmpty() ) + ImplInvalidateFrameRegion( &aRegion, InvalidateFlags::Children ); + } + } + + // invalidate Parent or Overlaps + if ( bNewPos || + (GetOutDev()->mnOutWidth < nOldOutWidth) || (GetOutDev()->mnOutHeight < nOldOutHeight) ) + { + vcl::Region aRegion( *pOldRegion ); + if ( !mpWindowImpl->mbPaintTransparent ) + ImplExcludeWindowRegion( aRegion ); + ImplClipBoundaries( aRegion, false, true ); + if ( !aRegion.IsEmpty() && !mpWindowImpl->mpBorderWindow ) + ImplInvalidateParentFrameRegion( aRegion ); + } + } + + // adapt system objects + if ( bUpdateSysObjClip ) + ImplUpdateSysObjClip(); + if ( bUpdateSysObjPos ) + ImplUpdateSysObjPos(); + if ( bNewSize && mpWindowImpl->mpSysObj ) + mpWindowImpl->mpSysObj->SetPosSize( GetOutDev()->mnOutOffX, GetOutDev()->mnOutOffY, GetOutDev()->mnOutWidth, GetOutDev()->mnOutHeight ); +} + +void Window::ImplNewInputContext() +{ + ImplSVData* pSVData = ImplGetSVData(); + vcl::Window* pFocusWin = pSVData->mpWinData->mpFocusWin; + if ( !pFocusWin || !pFocusWin->mpWindowImpl || pFocusWin->isDisposed() ) + return; + + // Is InputContext changed? + const InputContext& rInputContext = pFocusWin->GetInputContext(); + if ( rInputContext == pFocusWin->mpWindowImpl->mpFrameData->maOldInputContext ) + return; + + pFocusWin->mpWindowImpl->mpFrameData->maOldInputContext = rInputContext; + + SalInputContext aNewContext; + const vcl::Font& rFont = rInputContext.GetFont(); + const OUString& rFontName = rFont.GetFamilyName(); + rtl::Reference<LogicalFontInstance> pFontInstance; + aNewContext.mpFont = nullptr; + if (!rFontName.isEmpty()) + { + OutputDevice *pFocusWinOutDev = pFocusWin->GetOutDev(); + Size aSize = pFocusWinOutDev->ImplLogicToDevicePixel( rFont.GetFontSize() ); + if ( !aSize.Height() ) + { + // only set default sizes if the font height in logical + // coordinates equals 0 + if ( rFont.GetFontSize().Height() ) + aSize.setHeight( 1 ); + else + aSize.setHeight( (12*pFocusWin->GetOutDev()->mnDPIY)/72 ); + } + pFontInstance = pFocusWin->GetOutDev()->mxFontCache->GetFontInstance( + pFocusWin->GetOutDev()->mxFontCollection.get(), + rFont, aSize, static_cast<float>(aSize.Height()) ); + if ( pFontInstance ) + aNewContext.mpFont = pFontInstance; + } + aNewContext.mnOptions = rInputContext.GetOptions(); + pFocusWin->ImplGetFrame()->SetInputContext( &aNewContext ); +} + +void Window::SetDumpAsPropertyTreeHdl(const Link<tools::JsonWriter&, void>& rLink) +{ + if (mpWindowImpl) // may be called after dispose + { + mpWindowImpl->maDumpAsPropertyTreeHdl = rLink; + } +} + +void Window::SetModalHierarchyHdl(const Link<bool, void>& rLink) +{ + ImplGetFrame()->SetModalHierarchyHdl(rLink); +} + +KeyIndicatorState Window::GetIndicatorState() const +{ + return mpWindowImpl->mpFrame->GetIndicatorState(); +} + +void Window::SimulateKeyPress( sal_uInt16 nKeyCode ) const +{ + mpWindowImpl->mpFrame->SimulateKeyPress(nKeyCode); +} + +void Window::KeyInput( const KeyEvent& rKEvt ) +{ + KeyCode cod = rKEvt.GetKeyCode (); + bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel; + + // do not respond to accelerators unless Alt or Ctrl is held + if (cod.GetCode () >= 0x200 && cod.GetCode () <= 0x219) + { + if (autoacc && cod.GetModifier () != KEY_MOD2 && !(cod.GetModifier() & KEY_MOD1)) + return; + } + + NotifyEvent aNEvt( MouseNotifyEvent::KEYINPUT, this, &rKEvt ); + if ( !CompatNotify( aNEvt ) ) + mpWindowImpl->mbKeyInput = true; +} + +void Window::KeyUp( const KeyEvent& rKEvt ) +{ + NotifyEvent aNEvt( MouseNotifyEvent::KEYUP, this, &rKEvt ); + if ( !CompatNotify( aNEvt ) ) + mpWindowImpl->mbKeyUp = true; +} + +void Window::Draw( OutputDevice*, const Point&, SystemTextColorFlags ) +{ +} + +void Window::Move() {} + +void Window::Resize() {} + +void Window::Activate() {} + +void Window::Deactivate() {} + +void Window::GetFocus() +{ + if ( HasFocus() && mpWindowImpl->mpLastFocusWindow && !(mpWindowImpl->mnDlgCtrlFlags & DialogControlFlags::WantFocus) ) + { + VclPtr<vcl::Window> xWindow(this); + mpWindowImpl->mpLastFocusWindow->GrabFocus(); + if( xWindow->isDisposed() ) + return; + } + + NotifyEvent aNEvt( MouseNotifyEvent::GETFOCUS, this ); + CompatNotify( aNEvt ); +} + +void Window::LoseFocus() +{ + NotifyEvent aNEvt( MouseNotifyEvent::LOSEFOCUS, this ); + CompatNotify( aNEvt ); +} + +void Window::SetHelpHdl(const Link<vcl::Window&, bool>& rLink) +{ + if (mpWindowImpl) // may be called after dispose + { + mpWindowImpl->maHelpRequestHdl = rLink; + } +} + +void Window::RequestHelp( const HelpEvent& rHEvt ) +{ + // if Balloon-Help is requested, show the balloon + // with help text set + if ( rHEvt.GetMode() & HelpEventMode::BALLOON ) + { + OUString rStr = GetHelpText(); + if ( rStr.isEmpty() ) + rStr = GetQuickHelpText(); + if ( rStr.isEmpty() && ImplGetParent() && !ImplIsOverlapWindow() ) + ImplGetParent()->RequestHelp( rHEvt ); + else + { + Point aPos = GetPosPixel(); + if ( ImplGetParent() && !ImplIsOverlapWindow() ) + aPos = OutputToScreenPixel(Point(0, 0)); + tools::Rectangle aRect( aPos, GetSizePixel() ); + + Help::ShowBalloon( this, rHEvt.GetMousePosPixel(), aRect, rStr ); + } + } + else if ( rHEvt.GetMode() & HelpEventMode::QUICK ) + { + const OUString& rStr = GetQuickHelpText(); + if ( rStr.isEmpty() && ImplGetParent() && !ImplIsOverlapWindow() ) + ImplGetParent()->RequestHelp( rHEvt ); + else + { + Point aPos = GetPosPixel(); + if ( ImplGetParent() && !ImplIsOverlapWindow() ) + aPos = OutputToScreenPixel(Point(0, 0)); + tools::Rectangle aRect( aPos, GetSizePixel() ); + Help::ShowQuickHelp( this, aRect, rStr, QuickHelpFlags::CtrlText ); + } + } + else if (!mpWindowImpl->maHelpRequestHdl.IsSet() || mpWindowImpl->maHelpRequestHdl.Call(*this)) + { + OUString aStrHelpId( OStringToOUString( GetHelpId(), RTL_TEXTENCODING_UTF8 ) ); + if ( aStrHelpId.isEmpty() && ImplGetParent() ) + ImplGetParent()->RequestHelp( rHEvt ); + else + { + Help* pHelp = Application::GetHelp(); + if ( pHelp ) + { + if( !aStrHelpId.isEmpty() ) + pHelp->Start( aStrHelpId, this ); + else + pHelp->Start( OOO_HELP_INDEX, this ); + } + } + } +} + +void Window::Command( const CommandEvent& rCEvt ) +{ + CallEventListeners( VclEventId::WindowCommand, const_cast<CommandEvent *>(&rCEvt) ); + + NotifyEvent aNEvt( MouseNotifyEvent::COMMAND, this, &rCEvt ); + if ( !CompatNotify( aNEvt ) ) + mpWindowImpl->mbCommand = true; +} + +void Window::Tracking( const TrackingEvent& rTEvt ) +{ + + ImplDockingWindowWrapper *pWrapper = ImplGetDockingManager()->GetDockingWindowWrapper( this ); + if( pWrapper ) + pWrapper->Tracking( rTEvt ); +} + +void Window::StateChanged(StateChangedType eType) +{ + switch (eType) + { + //stuff that doesn't invalidate the layout + case StateChangedType::ControlForeground: + case StateChangedType::ControlBackground: + case StateChangedType::UpdateMode: + case StateChangedType::ReadOnly: + case StateChangedType::Enable: + case StateChangedType::State: + case StateChangedType::Data: + case StateChangedType::InitShow: + case StateChangedType::ControlFocus: + break; + //stuff that does invalidate the layout + default: + queue_resize(eType); + break; + } +} + +void Window::SetStyle( WinBits nStyle ) +{ + if ( mpWindowImpl && mpWindowImpl->mnStyle != nStyle ) + { + mpWindowImpl->mnPrevStyle = mpWindowImpl->mnStyle; + mpWindowImpl->mnStyle = nStyle; + CompatStateChanged( StateChangedType::Style ); + } +} + +void Window::SetExtendedStyle( WindowExtendedStyle nExtendedStyle ) +{ + + if ( mpWindowImpl->mnExtendedStyle == nExtendedStyle ) + return; + + vcl::Window* pWindow = ImplGetBorderWindow(); + if( ! pWindow ) + pWindow = this; + if( pWindow->mpWindowImpl->mbFrame ) + { + SalExtStyle nExt = 0; + if( nExtendedStyle & WindowExtendedStyle::Document ) + nExt |= SAL_FRAME_EXT_STYLE_DOCUMENT; + if( nExtendedStyle & WindowExtendedStyle::DocModified ) + nExt |= SAL_FRAME_EXT_STYLE_DOCMODIFIED; + + pWindow->ImplGetFrame()->SetExtendedFrameStyle( nExt ); + } + mpWindowImpl->mnExtendedStyle = nExtendedStyle; +} + +void Window::SetBorderStyle( WindowBorderStyle nBorderStyle ) +{ + + if ( !mpWindowImpl->mpBorderWindow ) + return; + + if( nBorderStyle == WindowBorderStyle::REMOVEBORDER && + ! mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame && + mpWindowImpl->mpBorderWindow->mpWindowImpl->mpParent + ) + { + // this is a little awkward: some controls (e.g. svtools ProgressBar) + // cannot avoid getting constructed with WB_BORDER but want to disable + // borders in case of NWF drawing. So they need a method to remove their border window + VclPtr<vcl::Window> pBorderWin = mpWindowImpl->mpBorderWindow; + // remove us as border window's client + pBorderWin->mpWindowImpl->mpClientWindow = nullptr; + mpWindowImpl->mpBorderWindow = nullptr; + mpWindowImpl->mpRealParent = pBorderWin->mpWindowImpl->mpParent; + // reparent us above the border window + SetParent( pBorderWin->mpWindowImpl->mpParent ); + // set us to the position and size of our previous border + Point aBorderPos( pBorderWin->GetPosPixel() ); + Size aBorderSize( pBorderWin->GetSizePixel() ); + setPosSizePixel( aBorderPos.X(), aBorderPos.Y(), aBorderSize.Width(), aBorderSize.Height() ); + // release border window + pBorderWin.disposeAndClear(); + + // set new style bits + SetStyle( GetStyle() & (~WB_BORDER) ); + } + else + { + if ( mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW ) + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->SetBorderStyle( nBorderStyle ); + else + mpWindowImpl->mpBorderWindow->SetBorderStyle( nBorderStyle ); + } +} + +WindowBorderStyle Window::GetBorderStyle() const +{ + + if ( mpWindowImpl->mpBorderWindow ) + { + if ( mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW ) + return static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->GetBorderStyle(); + else + return mpWindowImpl->mpBorderWindow->GetBorderStyle(); + } + + return WindowBorderStyle::NONE; +} + +tools::Long Window::CalcTitleWidth() const +{ + + if ( mpWindowImpl->mpBorderWindow ) + { + if ( mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW ) + return static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->CalcTitleWidth(); + else + return mpWindowImpl->mpBorderWindow->CalcTitleWidth(); + } + else if ( mpWindowImpl->mbFrame && (mpWindowImpl->mnStyle & WB_MOVEABLE) ) + { + // we guess the width for frame windows as we do not know the + // border of external dialogs + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + vcl::Font aFont = GetFont(); + const_cast<vcl::Window*>(this)->SetPointFont(const_cast<::OutputDevice&>(*GetOutDev()), rStyleSettings.GetTitleFont()); + tools::Long nTitleWidth = GetTextWidth( GetText() ); + const_cast<vcl::Window*>(this)->SetFont( aFont ); + nTitleWidth += rStyleSettings.GetTitleHeight() * 3; + nTitleWidth += StyleSettings::GetBorderSize() * 2; + nTitleWidth += 10; + return nTitleWidth; + } + + return 0; +} + +void Window::SetInputContext( const InputContext& rInputContext ) +{ + + mpWindowImpl->maInputContext = rInputContext; + if ( !mpWindowImpl->mbInFocusHdl && HasFocus() ) + ImplNewInputContext(); +} + +void Window::PostExtTextInputEvent(VclEventId nType, const OUString& rText) +{ + switch (nType) + { + case VclEventId::ExtTextInput: + { + std::unique_ptr<ExtTextInputAttr[]> pAttr(new ExtTextInputAttr[rText.getLength()]); + for (int i = 0; i < rText.getLength(); ++i) { + pAttr[i] = ExtTextInputAttr::Underline; + } + SalExtTextInputEvent aEvent { rText, pAttr.get(), rText.getLength(), EXTTEXTINPUT_CURSOR_OVERWRITE }; + ImplWindowFrameProc(this, SalEvent::ExtTextInput, &aEvent); + } + break; + case VclEventId::EndExtTextInput: + ImplWindowFrameProc(this, SalEvent::EndExtTextInput, nullptr); + break; + default: + assert(false); + } +} + +void Window::EndExtTextInput() +{ + if ( mpWindowImpl->mbExtTextInput ) + ImplGetFrame()->EndExtTextInput( EndExtTextInputFlags::Complete ); +} + +void Window::SetCursorRect( const tools::Rectangle* pRect, tools::Long nExtTextInputWidth ) +{ + + ImplWinData* pWinData = ImplGetWinData(); + if ( pWinData->mpCursorRect ) + { + if ( pRect ) + pWinData->mpCursorRect = *pRect; + else + pWinData->mpCursorRect.reset(); + } + else + { + if ( pRect ) + pWinData->mpCursorRect = *pRect; + } + + pWinData->mnCursorExtWidth = nExtTextInputWidth; + +} + +const tools::Rectangle* Window::GetCursorRect() const +{ + + ImplWinData* pWinData = ImplGetWinData(); + return pWinData->mpCursorRect ? &*pWinData->mpCursorRect : nullptr; +} + +tools::Long Window::GetCursorExtTextInputWidth() const +{ + + ImplWinData* pWinData = ImplGetWinData(); + return pWinData->mnCursorExtWidth; +} + +void Window::SetCompositionCharRect( const tools::Rectangle* pRect, tools::Long nCompositionLength, bool bVertical ) { + + ImplWinData* pWinData = ImplGetWinData(); + pWinData->mpCompositionCharRects.reset(); + pWinData->mbVertical = bVertical; + pWinData->mnCompositionCharRects = nCompositionLength; + if ( pRect && (nCompositionLength > 0) ) + { + pWinData->mpCompositionCharRects.reset( new tools::Rectangle[nCompositionLength] ); + for (tools::Long i = 0; i < nCompositionLength; ++i) + pWinData->mpCompositionCharRects[i] = pRect[i]; + } +} + +void Window::CollectChildren(::std::vector<vcl::Window *>& rAllChildren ) +{ + rAllChildren.push_back( this ); + + VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild; + while ( pChild ) + { + pChild->CollectChildren( rAllChildren ); + pChild = pChild->mpWindowImpl->mpNext; + } +} + +void Window::SetPointFont(vcl::RenderContext& rRenderContext, const vcl::Font& rFont) +{ + vcl::Font aFont = rFont; + ImplPointToLogic(rRenderContext, aFont); + rRenderContext.SetFont(aFont); +} + +vcl::Font Window::GetPointFont(vcl::RenderContext const & rRenderContext) const +{ + vcl::Font aFont = rRenderContext.GetFont(); + ImplLogicToPoint(rRenderContext, aFont); + return aFont; +} + +void Window::Show(bool bVisible, ShowFlags nFlags) +{ + if ( !mpWindowImpl || mpWindowImpl->mbVisible == bVisible ) + return; + + VclPtr<vcl::Window> xWindow(this); + + bool bRealVisibilityChanged = false; + mpWindowImpl->mbVisible = bVisible; + + if ( !bVisible ) + { + ImplHideAllOverlaps(); + if( !xWindow->mpWindowImpl ) + return; + + if ( mpWindowImpl->mpBorderWindow ) + { + bool bOldUpdate = mpWindowImpl->mpBorderWindow->mpWindowImpl->mbNoParentUpdate; + if ( mpWindowImpl->mbNoParentUpdate ) + mpWindowImpl->mpBorderWindow->mpWindowImpl->mbNoParentUpdate = true; + mpWindowImpl->mpBorderWindow->Show( false, nFlags ); + mpWindowImpl->mpBorderWindow->mpWindowImpl->mbNoParentUpdate = bOldUpdate; + } + else if ( mpWindowImpl->mbFrame ) + { + mpWindowImpl->mbSuppressAccessibilityEvents = true; + mpWindowImpl->mpFrame->Show( false ); + } + + CompatStateChanged( StateChangedType::Visible ); + + if ( mpWindowImpl->mbReallyVisible ) + { + if ( mpWindowImpl->mbInitWinClipRegion ) + ImplInitWinClipRegion(); + + vcl::Region aInvRegion = mpWindowImpl->maWinClipRegion; + + if( !xWindow->mpWindowImpl ) + return; + + bRealVisibilityChanged = mpWindowImpl->mbReallyVisible; + ImplResetReallyVisible(); + ImplSetClipFlag(); + + if ( ImplIsOverlapWindow() && !mpWindowImpl->mbFrame ) + { + // convert focus + if ( !(nFlags & ShowFlags::NoFocusChange) && HasChildPathFocus() ) + { + if ( mpWindowImpl->mpOverlapWindow->IsEnabled() && + mpWindowImpl->mpOverlapWindow->IsInputEnabled() && + ! mpWindowImpl->mpOverlapWindow->IsInModalMode() + ) + mpWindowImpl->mpOverlapWindow->GrabFocus(); + } + } + + if ( !mpWindowImpl->mbFrame ) + { + if (mpWindowImpl->mpWinData && mpWindowImpl->mpWinData->mbEnableNativeWidget) + { + /* + * #i48371# native theming: some themes draw outside the control + * area we tell them to (bad thing, but we cannot do much about it ). + * On hiding these controls they get invalidated with their window rectangle + * which leads to the parts outside the control area being left and not + * invalidated. Workaround: invalidate an area on the parent, too + */ + const int workaround_border = 5; + tools::Rectangle aBounds( aInvRegion.GetBoundRect() ); + aBounds.AdjustLeft( -workaround_border ); + aBounds.AdjustTop( -workaround_border ); + aBounds.AdjustRight(workaround_border ); + aBounds.AdjustBottom(workaround_border ); + aInvRegion = aBounds; + } + if ( !mpWindowImpl->mbNoParentUpdate ) + { + if ( !aInvRegion.IsEmpty() ) + ImplInvalidateParentFrameRegion( aInvRegion ); + } + ImplGenerateMouseMove(); + } + } + } + else + { + // inherit native widget flag for form controls + // required here, because frames never show up in the child hierarchy - which should be fixed... + // eg, the drop down of a combobox which is a system floating window + if( mpWindowImpl->mbFrame && GetParent() && !GetParent()->isDisposed() && + GetParent()->IsCompoundControl() && + GetParent()->IsNativeWidgetEnabled() != IsNativeWidgetEnabled() && + !(GetStyle() & WB_TOOLTIPWIN) ) + { + EnableNativeWidget( GetParent()->IsNativeWidgetEnabled() ); + } + + if ( mpWindowImpl->mbCallMove ) + { + ImplCallMove(); + } + if ( mpWindowImpl->mbCallResize ) + { + ImplCallResize(); + } + + CompatStateChanged( StateChangedType::Visible ); + + vcl::Window* pTestParent; + if ( ImplIsOverlapWindow() ) + pTestParent = mpWindowImpl->mpOverlapWindow; + else + pTestParent = ImplGetParent(); + if ( mpWindowImpl->mbFrame || pTestParent->mpWindowImpl->mbReallyVisible ) + { + // if a window becomes visible, send all child windows a StateChange, + // such that these can initialise themselves + ImplCallInitShow(); + + // If it is a SystemWindow it automatically pops up on top of + // all other windows if needed. + if ( ImplIsOverlapWindow() && !(nFlags & ShowFlags::NoActivate) ) + { + ImplStartToTop(( nFlags & ShowFlags::ForegroundTask ) ? ToTopFlags::ForegroundTask : ToTopFlags::NONE ); + ImplFocusToTop( ToTopFlags::NONE, false ); + } + + // adjust mpWindowImpl->mbReallyVisible + bRealVisibilityChanged = !mpWindowImpl->mbReallyVisible; + ImplSetReallyVisible(); + + // assure clip rectangles will be recalculated + ImplSetClipFlag(); + + if ( !mpWindowImpl->mbFrame ) + { + InvalidateFlags nInvalidateFlags = InvalidateFlags::Children; + if( ! IsPaintTransparent() ) + nInvalidateFlags |= InvalidateFlags::NoTransparent; + ImplInvalidate( nullptr, nInvalidateFlags ); + ImplGenerateMouseMove(); + } + } + + if ( mpWindowImpl->mpBorderWindow ) + mpWindowImpl->mpBorderWindow->Show( true, nFlags ); + else if ( mpWindowImpl->mbFrame ) + { + // #106431#, hide SplashScreen + ImplSVData* pSVData = ImplGetSVData(); + if ( !pSVData->mpIntroWindow ) + { + // The right way would be just to call this (not even in the 'if') + auto pApp = GetpApp(); + if ( pApp ) + pApp->InitFinished(); + } + else if ( !ImplIsWindowOrChild( pSVData->mpIntroWindow ) ) + { + // ... but the VCL splash is broken, and it needs this + // (for ./soffice .uno:NewDoc) + pSVData->mpIntroWindow->Hide(); + } + + //SAL_WARN_IF( mpWindowImpl->mbSuppressAccessibilityEvents, "vcl", "Window::Show() - Frame reactivated"); + mpWindowImpl->mbSuppressAccessibilityEvents = false; + + mpWindowImpl->mbPaintFrame = true; + if (!Application::IsHeadlessModeEnabled()) + { + bool bNoActivate(nFlags & (ShowFlags::NoActivate|ShowFlags::NoFocusChange)); + mpWindowImpl->mpFrame->Show( true, bNoActivate ); + } + if( !xWindow->mpWindowImpl ) + return; + + // Query the correct size of the window, if we are waiting for + // a system resize + if ( mpWindowImpl->mbWaitSystemResize ) + { + tools::Long nOutWidth; + tools::Long nOutHeight; + mpWindowImpl->mpFrame->GetClientSize( nOutWidth, nOutHeight ); + ImplHandleResize( this, nOutWidth, nOutHeight ); + } + + if (mpWindowImpl->mpFrameData->mpBuffer && mpWindowImpl->mpFrameData->mpBuffer->GetOutputSizePixel() != GetOutputSizePixel()) + // Make sure that the buffer size matches the window size, even if no resize was needed. + mpWindowImpl->mpFrameData->mpBuffer->SetOutputSizePixel(GetOutputSizePixel()); + } + + if( !xWindow->mpWindowImpl ) + return; + + ImplShowAllOverlaps(); + } + + if( !xWindow->mpWindowImpl ) + return; + + // the SHOW/HIDE events also serve as indicators to send child creation/destroy events to the access bridge + // However, the access bridge only uses this event if the data member is not NULL (it's kind of a hack that + // we re-use the SHOW/HIDE events this way, with this particular semantics). + // Since #104887#, the notifications for the access bridge are done in Impl(Set|Reset)ReallyVisible. Here, we + // now only notify with a NULL data pointer, for all other clients except the access bridge. + if ( !bRealVisibilityChanged ) + CallEventListeners( mpWindowImpl->mbVisible ? VclEventId::WindowShow : VclEventId::WindowHide ); + if( xWindow->isDisposed() ) + return; + +} + +Size Window::GetSizePixel() const +{ + if (!mpWindowImpl) + { + SAL_WARN("vcl.layout", "WTF no windowimpl"); + return Size(0,0); + } + + // #i43257# trigger pending resize handler to assure correct window sizes + if( mpWindowImpl->mpFrameData->maResizeIdle.IsActive() ) + { + VclPtr<vcl::Window> xWindow( const_cast<Window*>(this) ); + mpWindowImpl->mpFrameData->maResizeIdle.Stop(); + mpWindowImpl->mpFrameData->maResizeIdle.Invoke( nullptr ); + if( xWindow->isDisposed() ) + return Size(0,0); + } + + return Size( GetOutDev()->mnOutWidth + mpWindowImpl->mnLeftBorder+mpWindowImpl->mnRightBorder, + GetOutDev()->mnOutHeight + mpWindowImpl->mnTopBorder+mpWindowImpl->mnBottomBorder ); +} + +void Window::GetBorder( sal_Int32& rLeftBorder, sal_Int32& rTopBorder, + sal_Int32& rRightBorder, sal_Int32& rBottomBorder ) const +{ + rLeftBorder = mpWindowImpl->mnLeftBorder; + rTopBorder = mpWindowImpl->mnTopBorder; + rRightBorder = mpWindowImpl->mnRightBorder; + rBottomBorder = mpWindowImpl->mnBottomBorder; +} + +void Window::Enable( bool bEnable, bool bChild ) +{ + if ( isDisposed() ) + return; + + if ( !bEnable ) + { + // the tracking mode will be stopped or the capture will be stolen + // when a window is disabled, + if ( IsTracking() ) + EndTracking( TrackingEventFlags::Cancel ); + if ( IsMouseCaptured() ) + ReleaseMouse(); + // try to pass focus to the next control + // if the window has focus and is contained in the dialog control + // mpWindowImpl->mbDisabled should only be set after a call of ImplDlgCtrlNextWindow(). + // Otherwise ImplDlgCtrlNextWindow() should be used + if ( HasFocus() ) + ImplDlgCtrlNextWindow(); + } + + if ( mpWindowImpl->mpBorderWindow ) + { + mpWindowImpl->mpBorderWindow->Enable( bEnable, false ); + if ( (mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW) && + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow ) + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow->Enable( bEnable ); + } + + // #i56102# restore app focus win in case the + // window was disabled when the frame focus changed + ImplSVData* pSVData = ImplGetSVData(); + if (bEnable && pSVData->mpWinData->mpFocusWin == nullptr + && mpWindowImpl->mpFrameData->mbHasFocus && mpWindowImpl->mpFrameData->mpFocusWin == this) + pSVData->mpWinData->mpFocusWin = this; + + if ( mpWindowImpl->mbDisabled != !bEnable ) + { + mpWindowImpl->mbDisabled = !bEnable; + if ( mpWindowImpl->mpSysObj ) + mpWindowImpl->mpSysObj->Enable( bEnable && !mpWindowImpl->mbInputDisabled ); + CompatStateChanged( StateChangedType::Enable ); + + CallEventListeners( bEnable ? VclEventId::WindowEnabled : VclEventId::WindowDisabled ); + } + + if ( bChild ) + { + VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild; + while ( pChild ) + { + pChild->Enable( bEnable, bChild ); + pChild = pChild->mpWindowImpl->mpNext; + } + } + + if ( IsReallyVisible() ) + ImplGenerateMouseMove(); +} + +void Window::EnableInput( bool bEnable, bool bChild ) +{ + if (!mpWindowImpl) + return; + + if ( mpWindowImpl->mpBorderWindow ) + { + mpWindowImpl->mpBorderWindow->EnableInput( bEnable, false ); + if ( (mpWindowImpl->mpBorderWindow->GetType() == WindowType::BORDERWINDOW) && + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow ) + static_cast<ImplBorderWindow*>(mpWindowImpl->mpBorderWindow.get())->mpMenuBarWindow->EnableInput( bEnable ); + } + + if ( (!bEnable && mpWindowImpl->meAlwaysInputMode != AlwaysInputEnabled) || bEnable ) + { + // automatically stop the tracking mode or steal capture + // if the window is disabled + if ( !bEnable ) + { + if ( IsTracking() ) + EndTracking( TrackingEventFlags::Cancel ); + if ( IsMouseCaptured() ) + ReleaseMouse(); + } + + if ( mpWindowImpl->mbInputDisabled != !bEnable ) + { + mpWindowImpl->mbInputDisabled = !bEnable; + if ( mpWindowImpl->mpSysObj ) + mpWindowImpl->mpSysObj->Enable( !mpWindowImpl->mbDisabled && bEnable ); + } + } + + // #i56102# restore app focus win in case the + // window was disabled when the frame focus changed + ImplSVData* pSVData = ImplGetSVData(); + if (bEnable && pSVData->mpWinData->mpFocusWin == nullptr + && mpWindowImpl->mpFrameData->mbHasFocus && mpWindowImpl->mpFrameData->mpFocusWin == this) + pSVData->mpWinData->mpFocusWin = this; + + if ( bChild ) + { + VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild; + while ( pChild ) + { + pChild->EnableInput( bEnable, bChild ); + pChild = pChild->mpWindowImpl->mpNext; + } + } + + if ( IsReallyVisible() ) + ImplGenerateMouseMove(); +} + +void Window::EnableInput( bool bEnable, const vcl::Window* pExcludeWindow ) +{ + if (!mpWindowImpl) + return; + + EnableInput( bEnable ); + + // pExecuteWindow is the first Overlap-Frame --> if this + // shouldn't be the case, then this must be changed in dialog.cxx + if( pExcludeWindow ) + pExcludeWindow = pExcludeWindow->ImplGetFirstOverlapWindow(); + vcl::Window* pSysWin = mpWindowImpl->mpFrameWindow->mpWindowImpl->mpFrameData->mpFirstOverlap; + while ( pSysWin ) + { + // Is Window in the path from this window + if ( ImplGetFirstOverlapWindow()->ImplIsWindowOrChild( pSysWin, true ) ) + { + // Is Window not in the exclude window path or not the + // exclude window, then change the status + if ( !pExcludeWindow || !pExcludeWindow->ImplIsWindowOrChild( pSysWin, true ) ) + pSysWin->EnableInput( bEnable ); + } + pSysWin = pSysWin->mpWindowImpl->mpNextOverlap; + } + + // enable/disable floating system windows as well + vcl::Window* pFrameWin = ImplGetSVData()->maFrameData.mpFirstFrame; + while ( pFrameWin ) + { + if( pFrameWin->ImplIsFloatingWindow() ) + { + // Is Window in the path from this window + if ( ImplGetFirstOverlapWindow()->ImplIsWindowOrChild( pFrameWin, true ) ) + { + // Is Window not in the exclude window path or not the + // exclude window, then change the status + if ( !pExcludeWindow || !pExcludeWindow->ImplIsWindowOrChild( pFrameWin, true ) ) + pFrameWin->EnableInput( bEnable ); + } + } + pFrameWin = pFrameWin->mpWindowImpl->mpFrameData->mpNextFrame; + } + + // the same for ownerdraw floating windows + if( !mpWindowImpl->mbFrame ) + return; + + ::std::vector< VclPtr<vcl::Window> >& rList = mpWindowImpl->mpFrameData->maOwnerDrawList; + for (auto const& elem : rList) + { + // Is Window in the path from this window + if ( ImplGetFirstOverlapWindow()->ImplIsWindowOrChild( elem, true ) ) + { + // Is Window not in the exclude window path or not the + // exclude window, then change the status + if ( !pExcludeWindow || !pExcludeWindow->ImplIsWindowOrChild( elem, true ) ) + elem->EnableInput( bEnable ); + } + } +} + +void Window::AlwaysEnableInput( bool bAlways, bool bChild ) +{ + + if ( mpWindowImpl->mpBorderWindow ) + mpWindowImpl->mpBorderWindow->AlwaysEnableInput( bAlways, false ); + + if( bAlways && mpWindowImpl->meAlwaysInputMode != AlwaysInputEnabled ) + { + mpWindowImpl->meAlwaysInputMode = AlwaysInputEnabled; + EnableInput(true, false); + } + else if( ! bAlways && mpWindowImpl->meAlwaysInputMode == AlwaysInputEnabled ) + { + mpWindowImpl->meAlwaysInputMode = AlwaysInputNone; + } + + if ( bChild ) + { + VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild; + while ( pChild ) + { + pChild->AlwaysEnableInput( bAlways, bChild ); + pChild = pChild->mpWindowImpl->mpNext; + } + } +} + +void Window::SetActivateMode( ActivateModeFlags nMode ) +{ + + if ( mpWindowImpl->mpBorderWindow ) + mpWindowImpl->mpBorderWindow->SetActivateMode( nMode ); + + if ( mpWindowImpl->mnActivateMode == nMode ) + return; + + mpWindowImpl->mnActivateMode = nMode; + + // possibly trigger Deactivate/Activate + if ( mpWindowImpl->mnActivateMode != ActivateModeFlags::NONE ) + { + if ( (mpWindowImpl->mbActive || (GetType() == WindowType::BORDERWINDOW)) && + !HasChildPathFocus( true ) ) + { + mpWindowImpl->mbActive = false; + Deactivate(); + } + } + else + { + if ( !mpWindowImpl->mbActive || (GetType() == WindowType::BORDERWINDOW) ) + { + mpWindowImpl->mbActive = true; + Activate(); + } + } +} + +void Window::setPosSizePixel( tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, PosSizeFlags nFlags ) +{ + bool bHasValidSize = !mpWindowImpl->mbDefSize; + + if ( nFlags & PosSizeFlags::Pos ) + mpWindowImpl->mbDefPos = false; + if ( nFlags & PosSizeFlags::Size ) + mpWindowImpl->mbDefSize = false; + + // The top BorderWindow is the window which is to be positioned + VclPtr<vcl::Window> pWindow = this; + while ( pWindow->mpWindowImpl->mpBorderWindow ) + pWindow = pWindow->mpWindowImpl->mpBorderWindow; + + if ( pWindow->mpWindowImpl->mbFrame ) + { + // Note: if we're positioning a frame, the coordinates are interpreted + // as being the top-left corner of the window's client area and NOT + // as the position of the border ! (due to limitations of several UNIX window managers) + tools::Long nOldWidth = pWindow->GetOutDev()->mnOutWidth; + + if ( !(nFlags & PosSizeFlags::Width) ) + nWidth = pWindow->GetOutDev()->mnOutWidth; + if ( !(nFlags & PosSizeFlags::Height) ) + nHeight = pWindow->GetOutDev()->mnOutHeight; + + sal_uInt16 nSysFlags=0; + VclPtr<vcl::Window> pParent = GetParent(); + VclPtr<vcl::Window> pWinParent = pWindow->GetParent(); + + if( nFlags & PosSizeFlags::Width ) + nSysFlags |= SAL_FRAME_POSSIZE_WIDTH; + if( nFlags & PosSizeFlags::Height ) + nSysFlags |= SAL_FRAME_POSSIZE_HEIGHT; + if( nFlags & PosSizeFlags::X ) + { + nSysFlags |= SAL_FRAME_POSSIZE_X; + if( pWinParent && (pWindow->GetStyle() & WB_SYSTEMCHILDWINDOW) ) + { + nX += pWinParent->GetOutDev()->mnOutOffX; + } + if( pParent && pParent->GetOutDev()->ImplIsAntiparallel() ) + { + tools::Rectangle aRect( Point ( nX, nY ), Size( nWidth, nHeight ) ); + const OutputDevice *pParentOutDev = pParent->GetOutDev(); + if (!comphelper::LibreOfficeKit::isActive()) + pParentOutDev->ReMirror( aRect ); + nX = aRect.Left(); + } + } + if( !comphelper::LibreOfficeKit::isActive() && + !(nFlags & PosSizeFlags::X) && bHasValidSize && + pWindow->mpWindowImpl->mpFrame->maGeometry.nWidth ) + { + // RTL: make sure the old right aligned position is not changed + // system windows will always grow to the right + if ( pWinParent ) + { + OutputDevice *pParentOutDev = pWinParent->GetOutDev(); + if( pParentOutDev->HasMirroredGraphics() ) + { + const SalFrameGeometry& aSysGeometry = mpWindowImpl->mpFrame->GetUnmirroredGeometry(); + const SalFrameGeometry& aParentSysGeometry = + pWinParent->mpWindowImpl->mpFrame->GetUnmirroredGeometry(); + tools::Long myWidth = nOldWidth; + if( !myWidth ) + myWidth = aSysGeometry.nWidth; + if( !myWidth ) + myWidth = nWidth; + nFlags |= PosSizeFlags::X; + nSysFlags |= SAL_FRAME_POSSIZE_X; + nX = aParentSysGeometry.nX - aSysGeometry.nLeftDecoration + aParentSysGeometry.nWidth + - myWidth - 1 - aSysGeometry.nX; + } + } + } + if( nFlags & PosSizeFlags::Y ) + { + nSysFlags |= SAL_FRAME_POSSIZE_Y; + if( pWinParent && (pWindow->GetStyle() & WB_SYSTEMCHILDWINDOW) ) + { + nY += pWinParent->GetOutDev()->mnOutOffY; + } + } + + if( nSysFlags & (SAL_FRAME_POSSIZE_WIDTH|SAL_FRAME_POSSIZE_HEIGHT) ) + { + // check for min/max client size and adjust size accordingly + // otherwise it may happen that the resize event is ignored, i.e. the old size remains + // unchanged but ImplHandleResize() is called with the wrong size + SystemWindow *pSystemWindow = dynamic_cast< SystemWindow* >( pWindow.get() ); + if( pSystemWindow ) + { + Size aMinSize = pSystemWindow->GetMinOutputSizePixel(); + Size aMaxSize = pSystemWindow->GetMaxOutputSizePixel(); + if( nWidth < aMinSize.Width() ) + nWidth = aMinSize.Width(); + if( nHeight < aMinSize.Height() ) + nHeight = aMinSize.Height(); + + if( nWidth > aMaxSize.Width() ) + nWidth = aMaxSize.Width(); + if( nHeight > aMaxSize.Height() ) + nHeight = aMaxSize.Height(); + } + } + + pWindow->mpWindowImpl->mpFrame->SetPosSize( nX, nY, nWidth, nHeight, nSysFlags ); + + // Adjust resize with the hack of different client size and frame geometries to fix + // native menu bars. Eventually this should be replaced by proper mnTopBorder usage. + pWindow->mpWindowImpl->mpFrame->GetClientSize(nWidth, nHeight); + + // Resize should be called directly. If we haven't + // set the correct size, we get a second resize from + // the system with the correct size. This can be happened + // if the size is too small or too large. + ImplHandleResize( pWindow, nWidth, nHeight ); + } + else + { + pWindow->ImplPosSizeWindow( nX, nY, nWidth, nHeight, nFlags ); + if ( IsReallyVisible() ) + ImplGenerateMouseMove(); + } +} + +Point Window::GetPosPixel() const +{ + return mpWindowImpl->maPos; +} + +tools::Rectangle Window::GetDesktopRectPixel() const +{ + tools::Rectangle rRect; + mpWindowImpl->mpFrameWindow->mpWindowImpl->mpFrame->GetWorkArea( rRect ); + return rRect; +} + +Point Window::OutputToScreenPixel( const Point& rPos ) const +{ + // relative to top level parent + return Point( rPos.X() + GetOutDev()->mnOutOffX, rPos.Y() + GetOutDev()->mnOutOffY ); +} + +Point Window::ScreenToOutputPixel( const Point& rPos ) const +{ + // relative to top level parent + return Point( rPos.X() - GetOutDev()->mnOutOffX, rPos.Y() - GetOutDev()->mnOutOffY ); +} + +tools::Long Window::ImplGetUnmirroredOutOffX() +{ + // revert mnOutOffX changes that were potentially made in ImplPosSizeWindow + tools::Long offx = GetOutDev()->mnOutOffX; + OutputDevice *pOutDev = GetOutDev(); + if( pOutDev->HasMirroredGraphics() ) + { + if( mpWindowImpl->mpParent && !mpWindowImpl->mpParent->mpWindowImpl->mbFrame && mpWindowImpl->mpParent->GetOutDev()->ImplIsAntiparallel() ) + { + if ( !ImplIsOverlapWindow() ) + offx -= mpWindowImpl->mpParent->GetOutDev()->mnOutOffX; + + offx = mpWindowImpl->mpParent->GetOutDev()->mnOutWidth - GetOutDev()->mnOutWidth - offx; + + if ( !ImplIsOverlapWindow() ) + offx += mpWindowImpl->mpParent->GetOutDev()->mnOutOffX; + + } + } + return offx; +} + +// normalized screen pixel are independent of mirroring +Point Window::OutputToNormalizedScreenPixel( const Point& rPos ) const +{ + // relative to top level parent + tools::Long offx = const_cast<vcl::Window*>(this)->ImplGetUnmirroredOutOffX(); + return Point( rPos.X()+offx, rPos.Y() + GetOutDev()->mnOutOffY ); +} + +Point Window::NormalizedScreenToOutputPixel( const Point& rPos ) const +{ + // relative to top level parent + tools::Long offx = const_cast<vcl::Window*>(this)->ImplGetUnmirroredOutOffX(); + return Point( rPos.X()-offx, rPos.Y() - GetOutDev()->mnOutOffY ); +} + +Point Window::OutputToAbsoluteScreenPixel( const Point& rPos ) const +{ + // relative to the screen + Point p = OutputToScreenPixel( rPos ); + SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry(); + p.AdjustX(g.nX ); + p.AdjustY(g.nY ); + return p; +} + +Point Window::AbsoluteScreenToOutputPixel( const Point& rPos ) const +{ + // relative to the screen + Point p = ScreenToOutputPixel( rPos ); + SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry(); + p.AdjustX( -(g.nX) ); + p.AdjustY( -(g.nY) ); + return p; +} + +tools::Rectangle Window::ImplOutputToUnmirroredAbsoluteScreenPixel( const tools::Rectangle &rRect ) const +{ + // this method creates unmirrored screen coordinates to be compared with the desktop + // and is used for positioning of RTL popup windows correctly on the screen + SalFrameGeometry g = mpWindowImpl->mpFrame->GetUnmirroredGeometry(); + + Point p1 = rRect.TopRight(); + p1 = OutputToScreenPixel(p1); + p1.setX( g.nX+g.nWidth-p1.X() ); + p1.AdjustY(g.nY ); + + Point p2 = rRect.BottomLeft(); + p2 = OutputToScreenPixel(p2); + p2.setX( g.nX+g.nWidth-p2.X() ); + p2.AdjustY(g.nY ); + + return tools::Rectangle( p1, p2 ); +} + +tools::Rectangle Window::ImplUnmirroredAbsoluteScreenToOutputPixel( const tools::Rectangle &rRect ) const +{ + // undo ImplOutputToUnmirroredAbsoluteScreenPixel + SalFrameGeometry g = mpWindowImpl->mpFrame->GetUnmirroredGeometry(); + + Point p1 = rRect.TopRight(); + p1.AdjustY(-g.nY ); + p1.setX( g.nX+g.nWidth-p1.X() ); + p1 = ScreenToOutputPixel(p1); + + Point p2 = rRect.BottomLeft(); + p2.AdjustY(-g.nY); + p2.setX( g.nX+g.nWidth-p2.X() ); + p2 = ScreenToOutputPixel(p2); + + return tools::Rectangle( p1, p2 ); +} + + +tools::Rectangle Window::GetWindowExtentsRelative(const vcl::Window *pRelativeWindow) const +{ + // with decoration + return ImplGetWindowExtentsRelative( pRelativeWindow ); +} + +tools::Rectangle Window::ImplGetWindowExtentsRelative(const vcl::Window *pRelativeWindow) const +{ + SalFrameGeometry g = mpWindowImpl->mpFrame->GetGeometry(); + // make sure we use the extent of our border window, + // otherwise we miss a few pixels + const vcl::Window *pWin = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow : this; + + Point aPos( pWin->OutputToScreenPixel( Point(0,0) ) ); + aPos.AdjustX(g.nX ); + aPos.AdjustY(g.nY ); + Size aSize ( pWin->GetSizePixel() ); + // #104088# do not add decoration to the workwindow to be compatible to java accessibility api + if( mpWindowImpl->mbFrame || (mpWindowImpl->mpBorderWindow && mpWindowImpl->mpBorderWindow->mpWindowImpl->mbFrame && GetType() != WindowType::WORKWINDOW) ) + { + aPos.AdjustX( -sal_Int32(g.nLeftDecoration) ); + aPos.AdjustY( -sal_Int32(g.nTopDecoration) ); + aSize.AdjustWidth(g.nLeftDecoration + g.nRightDecoration ); + aSize.AdjustHeight(g.nTopDecoration + g.nBottomDecoration ); + } + if( pRelativeWindow ) + { + // #106399# express coordinates relative to borderwindow + const vcl::Window *pRelWin = pRelativeWindow->mpWindowImpl->mpBorderWindow ? pRelativeWindow->mpWindowImpl->mpBorderWindow.get() : pRelativeWindow; + aPos = pRelWin->AbsoluteScreenToOutputPixel( aPos ); + } + return tools::Rectangle( aPos, aSize ); +} + +void Window::Scroll( tools::Long nHorzScroll, tools::Long nVertScroll, ScrollFlags nFlags ) +{ + + ImplScroll( GetOutputRectPixel(), + nHorzScroll, nVertScroll, nFlags & ~ScrollFlags::Clip ); +} + +void Window::Scroll( tools::Long nHorzScroll, tools::Long nVertScroll, + const tools::Rectangle& rRect, ScrollFlags nFlags ) +{ + OutputDevice *pOutDev = GetOutDev(); + tools::Rectangle aRect = pOutDev->ImplLogicToDevicePixel( rRect ); + aRect.Intersection( GetOutputRectPixel() ); + if ( !aRect.IsEmpty() ) + ImplScroll( aRect, nHorzScroll, nVertScroll, nFlags ); +} + +void WindowOutputDevice::Flush() +{ + if (mxOwnerWindow->mpWindowImpl) + mxOwnerWindow->mpWindowImpl->mpFrame->Flush( GetOutputRectPixel() ); +} + +void Window::SetUpdateMode( bool bUpdate ) +{ + if (mpWindowImpl) + { + mpWindowImpl->mbNoUpdate = !bUpdate; + CompatStateChanged( StateChangedType::UpdateMode ); + } +} + +void Window::GrabFocus() +{ + ImplGrabFocus( GetFocusFlags::NONE ); +} + +bool Window::HasFocus() const +{ + return (this == ImplGetSVData()->mpWinData->mpFocusWin); +} + +void Window::GrabFocusToDocument() +{ + ImplGrabFocusToDocument(GetFocusFlags::NONE); +} + +VclPtr<vcl::Window> Window::GetFocusedWindow() const +{ + if (mpWindowImpl && mpWindowImpl->mpFrameData) + return mpWindowImpl->mpFrameData->mpFocusWin; + else + return VclPtr<vcl::Window>(); +} + +void Window::SetFakeFocus( bool bFocus ) +{ + ImplGetWindowImpl()->mbFakeFocusSet = bFocus; +} + +bool Window::HasChildPathFocus( bool bSystemWindow ) const +{ + + vcl::Window* pFocusWin = ImplGetSVData()->mpWinData->mpFocusWin; + if ( pFocusWin ) + return ImplIsWindowOrChild( pFocusWin, bSystemWindow ); + return false; +} + +void Window::SetCursor( vcl::Cursor* pCursor ) +{ + + if ( mpWindowImpl->mpCursor != pCursor ) + { + if ( mpWindowImpl->mpCursor ) + mpWindowImpl->mpCursor->ImplHide(); + mpWindowImpl->mpCursor = pCursor; + if ( pCursor ) + pCursor->ImplShow(); + } +} + +void Window::SetText( const OUString& rStr ) +{ + if (!mpWindowImpl || rStr == mpWindowImpl->maText) + return; + + OUString oldTitle( mpWindowImpl->maText ); + mpWindowImpl->maText = rStr; + + if ( mpWindowImpl->mpBorderWindow ) + mpWindowImpl->mpBorderWindow->SetText( rStr ); + else if ( mpWindowImpl->mbFrame ) + mpWindowImpl->mpFrame->SetTitle( rStr ); + + CallEventListeners( VclEventId::WindowFrameTitleChanged, &oldTitle ); + + // #107247# needed for accessibility + // The VclEventId::WindowFrameTitleChanged is (mis)used to notify accessible name changes. + // Therefore a window, which is labeled by this window, must also notify an accessible + // name change. + if ( IsReallyVisible() ) + { + vcl::Window* pWindow = GetAccessibleRelationLabelFor(); + if ( pWindow && pWindow != this ) + pWindow->CallEventListeners( VclEventId::WindowFrameTitleChanged, &oldTitle ); + } + + CompatStateChanged( StateChangedType::Text ); +} + +OUString Window::GetText() const +{ + + return mpWindowImpl->maText; +} + +OUString Window::GetDisplayText() const +{ + + return GetText(); +} + +const Wallpaper& Window::GetDisplayBackground() const +{ + // FIXME: fix issue 52349, need to fix this really in + // all NWF enabled controls + const ToolBox* pTB = dynamic_cast<const ToolBox*>(this); + if( pTB && IsNativeWidgetEnabled() ) + return pTB->ImplGetToolBoxPrivateData()->maDisplayBackground; + + if( !IsBackground() ) + { + if( mpWindowImpl->mpParent ) + return mpWindowImpl->mpParent->GetDisplayBackground(); + } + + const Wallpaper& rBack = GetBackground(); + if( ! rBack.IsBitmap() && + ! rBack.IsGradient() && + rBack.GetColor()== COL_TRANSPARENT && + mpWindowImpl->mpParent ) + return mpWindowImpl->mpParent->GetDisplayBackground(); + return rBack; +} + +const OUString& Window::GetHelpText() const +{ + OUString aStrHelpId( OStringToOUString( GetHelpId(), RTL_TEXTENCODING_UTF8 ) ); + bool bStrHelpId = !aStrHelpId.isEmpty(); + + if ( !mpWindowImpl->maHelpText.getLength() && bStrHelpId ) + { + if ( !IsDialog() && (mpWindowImpl->mnType != WindowType::TABPAGE) && (mpWindowImpl->mnType != WindowType::FLOATINGWINDOW) ) + { + Help* pHelp = Application::GetHelp(); + if ( pHelp ) + { + mpWindowImpl->maHelpText = pHelp->GetHelpText(aStrHelpId, this); + mpWindowImpl->mbHelpTextDynamic = false; + } + } + } + else if( mpWindowImpl->mbHelpTextDynamic && bStrHelpId ) + { + static const char* pEnv = getenv( "HELP_DEBUG" ); + if( pEnv && *pEnv ) + { + OUString aTxt = mpWindowImpl->maHelpText + "\n------------------\n" + aStrHelpId; + mpWindowImpl->maHelpText = aTxt; + } + mpWindowImpl->mbHelpTextDynamic = false; + } + + //Fallback to Window::GetAccessibleDescription without reentry to GetHelpText() + if (mpWindowImpl->maHelpText.isEmpty() && mpWindowImpl->mpAccessibleInfos && mpWindowImpl->mpAccessibleInfos->pAccessibleDescription) + return *mpWindowImpl->mpAccessibleInfos->pAccessibleDescription; + return mpWindowImpl->maHelpText; +} + +void Window::SetWindowPeer( Reference< css::awt::XWindowPeer > const & xPeer, VCLXWindow* pVCLXWindow ) +{ + if (!mpWindowImpl || mpWindowImpl->mbInDispose) + return; + + // be safe against re-entrance: first clear the old ref, then assign the new one + if (mpWindowImpl->mxWindowPeer) + { + // first, disconnect the peer from ourself, otherwise disposing it, will dispose us + UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(); + SAL_WARN_IF( !pWrapper, "vcl.window", "SetComponentInterface: No Wrapper!" ); + if ( pWrapper ) + pWrapper->SetWindowInterface( nullptr, mpWindowImpl->mxWindowPeer ); + mpWindowImpl->mxWindowPeer->dispose(); + mpWindowImpl->mxWindowPeer.clear(); + } + mpWindowImpl->mxWindowPeer = xPeer; + + mpWindowImpl->mpVCLXWindow = pVCLXWindow; +} + +Reference< css::awt::XWindowPeer > Window::GetComponentInterface( bool bCreate ) +{ + if ( !mpWindowImpl->mxWindowPeer.is() && bCreate ) + { + UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(); + if ( pWrapper ) + mpWindowImpl->mxWindowPeer = pWrapper->GetWindowInterface( this ); + } + return mpWindowImpl->mxWindowPeer; +} + +void Window::SetComponentInterface( Reference< css::awt::XWindowPeer > const & xIFace ) +{ + UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(); + SAL_WARN_IF( !pWrapper, "vcl.window", "SetComponentInterface: No Wrapper!" ); + if ( pWrapper ) + pWrapper->SetWindowInterface( this, xIFace ); +} + +typedef std::map<vcl::LOKWindowId, VclPtr<vcl::Window>> LOKWindowsMap; + +namespace { + +LOKWindowsMap& GetLOKWindowsMap() +{ + // Map to remember the LOKWindowId <-> Window binding. + static LOKWindowsMap s_aLOKWindowsMap; + + return s_aLOKWindowsMap; +} + +} + +void Window::SetLOKNotifier(const vcl::ILibreOfficeKitNotifier* pNotifier, bool bParent) +{ + // don't allow setting this twice + assert(mpWindowImpl->mpLOKNotifier == nullptr); + assert(pNotifier); + // never use this in the desktop case + assert(comphelper::LibreOfficeKit::isActive()); + + if (!bParent) + { + // Counter to be able to have unique id's for each window. + static vcl::LOKWindowId sLastLOKWindowId = 1; + + // assign the LOK window id + assert(mpWindowImpl->mnLOKWindowId == 0); + mpWindowImpl->mnLOKWindowId = sLastLOKWindowId++; + GetLOKWindowsMap().emplace(mpWindowImpl->mnLOKWindowId, this); + } + else + mpWindowImpl->mbLOKParentNotifier = true; + + mpWindowImpl->mpLOKNotifier = pNotifier; +} + +VclPtr<Window> Window::FindLOKWindow(vcl::LOKWindowId nWindowId) +{ + const auto it = GetLOKWindowsMap().find(nWindowId); + if (it != GetLOKWindowsMap().end()) + return it->second; + + return VclPtr<Window>(); +} + +bool Window::IsLOKWindowsEmpty() +{ + return GetLOKWindowsMap().empty(); +} + +void Window::ReleaseLOKNotifier() +{ + // unregister the LOK window binding + if (mpWindowImpl->mnLOKWindowId > 0) + GetLOKWindowsMap().erase(mpWindowImpl->mnLOKWindowId); + + mpWindowImpl->mpLOKNotifier = nullptr; + mpWindowImpl->mnLOKWindowId = 0; +} + +ILibreOfficeKitNotifier::~ILibreOfficeKitNotifier() +{ + if (!comphelper::LibreOfficeKit::isActive()) + { + return; + } + + for (auto it = GetLOKWindowsMap().begin(); it != GetLOKWindowsMap().end();) + { + WindowImpl* pWindowImpl = it->second->ImplGetWindowImpl(); + if (pWindowImpl && pWindowImpl->mpLOKNotifier == this) + { + pWindowImpl->mpLOKNotifier = nullptr; + pWindowImpl->mnLOKWindowId = 0; + it = GetLOKWindowsMap().erase(it); + continue; + } + + ++it; + } +} + +const vcl::ILibreOfficeKitNotifier* Window::GetLOKNotifier() const +{ + return mpWindowImpl ? mpWindowImpl->mpLOKNotifier : nullptr; +} + +vcl::LOKWindowId Window::GetLOKWindowId() const +{ + return mpWindowImpl ? mpWindowImpl->mnLOKWindowId : 0; +} + +VclPtr<vcl::Window> Window::GetParentWithLOKNotifier() +{ + VclPtr<vcl::Window> pWindow(this); + + while (pWindow && !pWindow->GetLOKNotifier()) + pWindow = pWindow->GetParent(); + + return pWindow; +} + +namespace +{ + +const char* windowTypeName(WindowType nWindowType) +{ + switch (nWindowType) + { + case WindowType::NONE: return "none"; + case WindowType::MESSBOX: return "messagebox"; + case WindowType::INFOBOX: return "infobox"; + case WindowType::WARNINGBOX: return "warningbox"; + case WindowType::ERRORBOX: return "errorbox"; + case WindowType::QUERYBOX: return "querybox"; + case WindowType::WINDOW: return "window"; + case WindowType::WORKWINDOW: return "workwindow"; + case WindowType::CONTAINER: return "container"; + case WindowType::FLOATINGWINDOW: return "floatingwindow"; + case WindowType::DIALOG: return "dialog"; + case WindowType::MODELESSDIALOG: return "modelessdialog"; + case WindowType::CONTROL: return "control"; + case WindowType::PUSHBUTTON: return "pushbutton"; + case WindowType::OKBUTTON: return "okbutton"; + case WindowType::CANCELBUTTON: return "cancelbutton"; + case WindowType::HELPBUTTON: return "helpbutton"; + case WindowType::IMAGEBUTTON: return "imagebutton"; + case WindowType::MENUBUTTON: return "menubutton"; + case WindowType::MOREBUTTON: return "morebutton"; + case WindowType::SPINBUTTON: return "spinbutton"; + case WindowType::RADIOBUTTON: return "radiobutton"; + case WindowType::CHECKBOX: return "checkbox"; + case WindowType::TRISTATEBOX: return "tristatebox"; + case WindowType::EDIT: return "edit"; + case WindowType::MULTILINEEDIT: return "multilineedit"; + case WindowType::COMBOBOX: return "combobox"; + case WindowType::LISTBOX: return "listbox"; + case WindowType::MULTILISTBOX: return "multilistbox"; + case WindowType::FIXEDTEXT: return "fixedtext"; + case WindowType::FIXEDLINE: return "fixedline"; + case WindowType::FIXEDBITMAP: return "fixedbitmap"; + case WindowType::FIXEDIMAGE: return "fixedimage"; + case WindowType::GROUPBOX: return "groupbox"; + case WindowType::SCROLLBAR: return "scrollbar"; + case WindowType::SCROLLBARBOX: return "scrollbarbox"; + case WindowType::SPLITTER: return "splitter"; + case WindowType::SPLITWINDOW: return "splitwindow"; + case WindowType::SPINFIELD: return "spinfield"; + case WindowType::PATTERNFIELD: return "patternfield"; + case WindowType::METRICFIELD: return "metricfield"; + case WindowType::FORMATTEDFIELD: return "formattedfield"; + case WindowType::CURRENCYFIELD: return "currencyfield"; + case WindowType::DATEFIELD: return "datefield"; + case WindowType::TIMEFIELD: return "timefield"; + case WindowType::PATTERNBOX: return "patternbox"; + case WindowType::NUMERICBOX: return "numericbox"; + case WindowType::METRICBOX: return "metricbox"; + case WindowType::CURRENCYBOX: return "currencybox"; + case WindowType::DATEBOX: return "datebox"; + case WindowType::TIMEBOX: return "timebox"; + case WindowType::LONGCURRENCYBOX: return "longcurrencybox"; + case WindowType::SCROLLWINDOW: return "scrollwindow"; + case WindowType::TOOLBOX: return "toolbox"; + case WindowType::DOCKINGWINDOW: return "dockingwindow"; + case WindowType::STATUSBAR: return "statusbar"; + case WindowType::TABPAGE: return "tabpage"; + case WindowType::TABCONTROL: return "tabcontrol"; + case WindowType::TABDIALOG: return "tabdialog"; + case WindowType::BORDERWINDOW: return "borderwindow"; + case WindowType::BUTTONDIALOG: return "buttondialog"; + case WindowType::SYSTEMCHILDWINDOW: return "systemchildwindow"; + case WindowType::SLIDER: return "slider"; + case WindowType::MENUBARWINDOW: return "menubarwindow"; + case WindowType::TREELISTBOX: return "treelistbox"; + case WindowType::HELPTEXTWINDOW: return "helptextwindow"; + case WindowType::INTROWINDOW: return "introwindow"; + case WindowType::LISTBOXWINDOW: return "listboxwindow"; + case WindowType::DOCKINGAREA: return "dockingarea"; + case WindowType::RULER: return "ruler"; + case WindowType::HEADERBAR: return "headerbar"; + case WindowType::VERTICALTABCONTROL: return "verticaltabcontrol"; + + // nothing to do here, but for completeness + case WindowType::TOOLKIT_FRAMEWINDOW: return "toolkit_framewindow"; + case WindowType::TOOLKIT_SYSTEMCHILDWINDOW: return "toolkit_systemchildwindow"; + } + + return "none"; +} + +} + +void Window::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) +{ + if (!mpWindowImpl) + return; + + rJsonWriter.put("id", get_id()); // TODO could be missing - sort out + rJsonWriter.put("type", windowTypeName(GetType())); + rJsonWriter.put("text", GetText()); + rJsonWriter.put("enabled", IsEnabled()); + if (!IsVisible()) + rJsonWriter.put("visible", false); + + if (vcl::Window* pChild = mpWindowImpl->mpFirstChild) + { + auto childrenNode = rJsonWriter.startArray("children"); + while (pChild) + { + { + auto childNode = rJsonWriter.startStruct(); + pChild->DumpAsPropertyTree(rJsonWriter); + sal_Int32 nLeft = pChild->get_grid_left_attach(); + sal_Int32 nTop = pChild->get_grid_top_attach(); + if (nLeft != -1 && nTop != -1) + { + rJsonWriter.put("left", nLeft); + rJsonWriter.put("top", nTop); + } + + sal_Int32 nWidth = pChild->get_grid_width(); + if (nWidth > 1) + rJsonWriter.put("width", nWidth); + } + pChild = pChild->mpWindowImpl->mpNext; + } + } + + mpWindowImpl->maDumpAsPropertyTreeHdl.Call(rJsonWriter); +} + +void Window::ImplCallDeactivateListeners( vcl::Window *pNew ) +{ + // no deactivation if the newly activated window is my child + if ( !pNew || !ImplIsChild( pNew ) ) + { + VclPtr<vcl::Window> xWindow(this); + CallEventListeners( VclEventId::WindowDeactivate, pNew ); + if( !xWindow->mpWindowImpl ) + return; + + // #100759#, avoid walking the wrong frame's hierarchy + // eg, undocked docking windows (ImplDockFloatWin) + if ( ImplGetParent() && ImplGetParent()->mpWindowImpl && + mpWindowImpl->mpFrameWindow == ImplGetParent()->mpWindowImpl->mpFrameWindow ) + ImplGetParent()->ImplCallDeactivateListeners( pNew ); + } +} + +void Window::ImplCallActivateListeners( vcl::Window *pOld ) +{ + // no activation if the old active window is my child + if ( pOld && ImplIsChild( pOld )) + return; + + VclPtr<vcl::Window> xWindow(this); + CallEventListeners( VclEventId::WindowActivate, pOld ); + if( !xWindow->mpWindowImpl ) + return; + + if ( ImplGetParent() ) + ImplGetParent()->ImplCallActivateListeners( pOld ); + else if( (mpWindowImpl->mnStyle & WB_INTROWIN) == 0 ) + { + // top level frame reached: store hint for DefModalDialogParent + ImplGetSVData()->maFrameData.mpActiveApplicationFrame = mpWindowImpl->mpFrameWindow; + } +} + +void Window::SetClipboard(Reference<XClipboard> const & xClipboard) +{ + if (mpWindowImpl->mpFrameData) + mpWindowImpl->mpFrameData->mxClipboard = xClipboard; +} + +Reference< XClipboard > Window::GetClipboard() +{ + if (!mpWindowImpl->mpFrameData) + return static_cast<XClipboard*>(nullptr); + if (!mpWindowImpl->mpFrameData->mxClipboard.is()) + mpWindowImpl->mpFrameData->mxClipboard = GetSystemClipboard(); + return mpWindowImpl->mpFrameData->mxClipboard; +} + +void Window::RecordLayoutData( vcl::ControlLayoutData* pLayout, const tools::Rectangle& rRect ) +{ + assert(GetOutDev()->mpOutDevData); + GetOutDev()->mpOutDevData->mpRecordLayout = pLayout; + GetOutDev()->mpOutDevData->maRecordRect = rRect; + Paint(*GetOutDev(), rRect); + GetOutDev()->mpOutDevData->mpRecordLayout = nullptr; +} + +void Window::DrawSelectionBackground( const tools::Rectangle& rRect, + sal_uInt16 highlight, + bool bChecked, + bool bDrawBorder + ) +{ + if( rRect.IsEmpty() ) + return; + + const StyleSettings& rStyles = GetSettings().GetStyleSettings(); + + // colors used for item highlighting + Color aSelectionBorderCol( rStyles.GetHighlightColor() ); + Color aSelectionFillCol( aSelectionBorderCol ); + + bool bDark = rStyles.GetFaceColor().IsDark(); + bool bBright = ( rStyles.GetFaceColor() == COL_WHITE ); + + int c1 = aSelectionBorderCol.GetLuminance(); + int c2 = GetBackgroundColor().GetLuminance(); + + if( !bDark && !bBright && abs( c2-c1 ) < 75 ) + { + // contrast too low + sal_uInt16 h,s,b; + aSelectionFillCol.RGBtoHSB( h, s, b ); + if( b > 50 ) b -= 40; + else b += 40; + aSelectionFillCol = Color::HSBtoRGB( h, s, b ); + aSelectionBorderCol = aSelectionFillCol; + } + + tools::Rectangle aRect( rRect ); + Color oldFillCol = GetOutDev()->GetFillColor(); + Color oldLineCol = GetOutDev()->GetLineColor(); + + if( bDrawBorder ) + GetOutDev()->SetLineColor( bDark ? COL_WHITE : ( bBright ? COL_BLACK : aSelectionBorderCol ) ); + else + GetOutDev()->SetLineColor(); + + sal_uInt16 nPercent = 0; + if( !highlight ) + { + if( bDark ) + aSelectionFillCol = COL_BLACK; + else + nPercent = 80; // just checked (light) + } + else + { + if( bChecked && highlight == 2 ) + { + if( bDark ) + aSelectionFillCol = COL_LIGHTGRAY; + else if ( bBright ) + { + aSelectionFillCol = COL_BLACK; + GetOutDev()->SetLineColor( COL_BLACK ); + nPercent = 0; + } + else + nPercent = 20; // selected, pressed or checked ( very dark ) + } + else if( bChecked || highlight == 1 ) + { + if( bDark ) + aSelectionFillCol = COL_GRAY; + else if ( bBright ) + { + aSelectionFillCol = COL_BLACK; + GetOutDev()->SetLineColor( COL_BLACK ); + nPercent = 0; + } + else + nPercent = 35; // selected, pressed or checked ( very dark ) + } + else + { + if( bDark ) + aSelectionFillCol = COL_LIGHTGRAY; + else if ( bBright ) + { + aSelectionFillCol = COL_BLACK; + GetOutDev()->SetLineColor( COL_BLACK ); + if( highlight == 3 ) + nPercent = 80; + else + nPercent = 0; + } + else + nPercent = 70; // selected ( dark ) + } + } + + GetOutDev()->SetFillColor( aSelectionFillCol ); + + if( bDark ) + { + GetOutDev()->DrawRect( aRect ); + } + else + { + tools::Polygon aPoly( aRect ); + tools::PolyPolygon aPolyPoly( aPoly ); + GetOutDev()->DrawTransparent( aPolyPoly, nPercent ); + } + + GetOutDev()->SetFillColor( oldFillCol ); + GetOutDev()->SetLineColor( oldLineCol ); +} + +bool Window::IsScrollable() const +{ + // check for scrollbars + VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild; + while( pChild ) + { + if( pChild->GetType() == WindowType::SCROLLBAR ) + return true; + else + pChild = pChild->mpWindowImpl->mpNext; + } + return false; +} + +void Window::ImplMirrorFramePos( Point &pt ) const +{ + pt.setX( mpWindowImpl->mpFrame->maGeometry.nWidth-1-pt.X() ); +} + +// frame based modal counter (dialogs are not modal to the whole application anymore) +bool Window::IsInModalMode() const +{ + return (mpWindowImpl->mpFrameWindow->mpWindowImpl->mpFrameData->mnModalMode != 0); +} + +void Window::IncModalCount() +{ + vcl::Window* pFrameWindow = mpWindowImpl->mpFrameWindow; + vcl::Window* pParent = pFrameWindow; + while( pFrameWindow ) + { + pFrameWindow->mpWindowImpl->mpFrameData->mnModalMode++; + while( pParent && pParent->mpWindowImpl->mpFrameWindow == pFrameWindow ) + { + pParent = pParent->GetParent(); + } + pFrameWindow = pParent ? pParent->mpWindowImpl->mpFrameWindow.get() : nullptr; + } +} +void Window::DecModalCount() +{ + vcl::Window* pFrameWindow = mpWindowImpl->mpFrameWindow; + vcl::Window* pParent = pFrameWindow; + while( pFrameWindow ) + { + pFrameWindow->mpWindowImpl->mpFrameData->mnModalMode--; + while( pParent && pParent->mpWindowImpl->mpFrameWindow == pFrameWindow ) + { + pParent = pParent->GetParent(); + } + pFrameWindow = pParent ? pParent->mpWindowImpl->mpFrameWindow.get() : nullptr; + } +} + +void Window::ImplIsInTaskPaneList( bool mbIsInTaskList ) +{ + mpWindowImpl->mbIsInTaskPaneList = mbIsInTaskList; +} + +void Window::ImplNotifyIconifiedState( bool bIconified ) +{ + mpWindowImpl->mpFrameWindow->CallEventListeners( bIconified ? VclEventId::WindowMinimize : VclEventId::WindowNormalize ); + // #109206# notify client window as well to have toolkit topwindow listeners notified + if( mpWindowImpl->mpFrameWindow->mpWindowImpl->mpClientWindow && mpWindowImpl->mpFrameWindow != mpWindowImpl->mpFrameWindow->mpWindowImpl->mpClientWindow ) + mpWindowImpl->mpFrameWindow->mpWindowImpl->mpClientWindow->CallEventListeners( bIconified ? VclEventId::WindowMinimize : VclEventId::WindowNormalize ); +} + +bool Window::HasActiveChildFrame() const +{ + bool bRet = false; + vcl::Window *pFrameWin = ImplGetSVData()->maFrameData.mpFirstFrame; + while( pFrameWin ) + { + if( pFrameWin != mpWindowImpl->mpFrameWindow ) + { + bool bDecorated = false; + VclPtr< vcl::Window > pChildFrame = pFrameWin->ImplGetWindow(); + // #i15285# unfortunately WB_MOVEABLE is the same as WB_TABSTOP which can + // be removed for ToolBoxes to influence the keyboard accessibility + // thus WB_MOVEABLE is no indicator for decoration anymore + // but FloatingWindows carry this information in their TitleType... + // TODO: avoid duplicate WinBits !!! + if( pChildFrame && pChildFrame->ImplIsFloatingWindow() ) + bDecorated = static_cast<FloatingWindow*>(pChildFrame.get())->GetTitleType() != FloatWinTitleType::NONE; + if( bDecorated || (pFrameWin->mpWindowImpl->mnStyle & (WB_MOVEABLE | WB_SIZEABLE) ) ) + if( pChildFrame && pChildFrame->IsVisible() && pChildFrame->IsActive() ) + { + if( ImplIsChild( pChildFrame, true ) ) + { + bRet = true; + break; + } + } + } + pFrameWin = pFrameWin->mpWindowImpl->mpFrameData->mpNextFrame; + } + return bRet; +} + +LanguageType Window::GetInputLanguage() const +{ + return mpWindowImpl->mpFrame->GetInputLanguage(); +} + +void Window::EnableNativeWidget( bool bEnable ) +{ + static const char* pNoNWF = getenv( "SAL_NO_NWF" ); + if( pNoNWF && *pNoNWF ) + bEnable = false; + + if( bEnable != ImplGetWinData()->mbEnableNativeWidget ) + { + ImplGetWinData()->mbEnableNativeWidget = bEnable; + + // send datachanged event to allow for internal changes required for NWF + // like clipmode, transparency, etc. + DataChangedEvent aDCEvt( DataChangedEventType::SETTINGS, GetOutDev()->mxSettings.get(), AllSettingsFlags::STYLE ); + CompatDataChanged( aDCEvt ); + + // sometimes the borderwindow is queried, so keep it in sync + if( mpWindowImpl->mpBorderWindow ) + mpWindowImpl->mpBorderWindow->ImplGetWinData()->mbEnableNativeWidget = bEnable; + } + + // push down, useful for compound controls + VclPtr< vcl::Window > pChild = mpWindowImpl->mpFirstChild; + while( pChild ) + { + pChild->EnableNativeWidget( bEnable ); + pChild = pChild->mpWindowImpl->mpNext; + } +} + +bool Window::IsNativeWidgetEnabled() const +{ + return mpWindowImpl && ImplGetWinData()->mbEnableNativeWidget; +} + +Reference< css::rendering::XCanvas > WindowOutputDevice::ImplGetCanvas( bool bSpriteCanvas ) const +{ + // Feed any with operating system's window handle + + // common: first any is VCL pointer to window (for VCL canvas) + Sequence< Any > aArg{ + Any(reinterpret_cast<sal_Int64>(this)), + Any(css::awt::Rectangle( mnOutOffX, mnOutOffY, mnOutWidth, mnOutHeight )), + Any(mxOwnerWindow->mpWindowImpl->mbAlwaysOnTop), + Any(Reference< css::awt::XWindow >( + mxOwnerWindow->GetComponentInterface(), + UNO_QUERY )), + GetSystemGfxDataAny() + }; + + Reference< XComponentContext > xContext = comphelper::getProcessComponentContext(); + + // Create canvas instance with window handle + + static vcl::DeleteUnoReferenceOnDeinit<XMultiComponentFactory> xStaticCanvasFactory( + css::rendering::CanvasFactory::create( xContext ) ); + Reference<XMultiComponentFactory> xCanvasFactory(xStaticCanvasFactory.get()); + Reference< css::rendering::XCanvas > xCanvas; + + if(xCanvasFactory.is()) + { +#ifdef _WIN32 + // see #140456# - if we're running on a multiscreen setup, + // request special, multi-screen safe sprite canvas + // implementation (not DX5 canvas, as it cannot cope with + // surfaces spanning multiple displays). Note: canvas + // (without sprite) stays the same) + const sal_uInt32 nDisplay = static_cast< WinSalFrame* >( mxOwnerWindow->mpWindowImpl->mpFrame )->mnDisplay; + if( nDisplay >= Application::GetScreenCount() ) + { + xCanvas.set( xCanvasFactory->createInstanceWithArgumentsAndContext( + bSpriteCanvas ? + OUString( "com.sun.star.rendering.SpriteCanvas.MultiScreen" ) : + OUString( "com.sun.star.rendering.Canvas.MultiScreen" ), + aArg, + xContext ), + UNO_QUERY ); + + } + else +#endif + { + xCanvas.set( xCanvasFactory->createInstanceWithArgumentsAndContext( + bSpriteCanvas ? + OUString( "com.sun.star.rendering.SpriteCanvas" ) : + OUString( "com.sun.star.rendering.Canvas" ), + aArg, + xContext ), + UNO_QUERY ); + + } + } + + // no factory??? Empty reference, then. + return xCanvas; +} + +OUString Window::GetSurroundingText() const +{ + return OUString(); +} + +Selection Window::GetSurroundingTextSelection() const +{ + return Selection( 0, 0 ); +} + +namespace +{ + using namespace com::sun::star; + + uno::Reference<accessibility::XAccessibleEditableText> lcl_GetxText(vcl::Window *pFocusWin) + { + uno::Reference<accessibility::XAccessibleEditableText> xText; + try + { + uno::Reference< accessibility::XAccessible > xAccessible( pFocusWin->GetAccessible() ); + if (xAccessible.is()) + xText = FindFocusedEditableText(xAccessible->getAccessibleContext()); + } + catch(const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "vcl.gtk3", "Exception in getting input method surrounding text"); + } + return xText; + } +} + +// this is a rubbish implementation using a11y, ideally all subclasses implementing +// GetSurroundingText/GetSurroundingTextSelection should implement this and then this +// should be removed in favor of a stub that returns false +bool Window::DeleteSurroundingText(const Selection& rSelection) +{ + uno::Reference<accessibility::XAccessibleEditableText> xText = lcl_GetxText(this); + if (xText.is()) + { + sal_Int32 nPosition = xText->getCaretPosition(); + // #i111768# range checking + sal_Int32 nDeletePos = rSelection.Min(); + sal_Int32 nDeleteEnd = rSelection.Max(); + if (nDeletePos < 0) + nDeletePos = 0; + if (nDeleteEnd < 0) + nDeleteEnd = 0; + if (nDeleteEnd > xText->getCharacterCount()) + nDeleteEnd = xText->getCharacterCount(); + + xText->deleteText(nDeletePos, nDeleteEnd); + //tdf91641 adjust cursor if deleted chars shift it forward (normal case) + if (nDeletePos < nPosition) + { + if (nDeleteEnd <= nPosition) + nPosition = nPosition - (nDeleteEnd - nDeletePos); + else + nPosition = nDeletePos; + + if (xText->getCharacterCount() >= nPosition) + xText->setCaretPosition( nPosition ); + } + return true; + } + + return false; +} + +bool WindowOutputDevice::UsePolyPolygonForComplexGradient() +{ + return meRasterOp != RasterOp::OverPaint; +} + +void Window::ApplySettings(vcl::RenderContext& /*rRenderContext*/) +{ +} + +const SystemEnvData* Window::GetSystemData() const +{ + + return mpWindowImpl->mpFrame ? mpWindowImpl->mpFrame->GetSystemData() : nullptr; +} + +bool Window::SupportsDoubleBuffering() const +{ + return mpWindowImpl->mpFrameData->mpBuffer; +} + +void Window::RequestDoubleBuffering(bool bRequest) +{ + if (bRequest) + { + mpWindowImpl->mpFrameData->mpBuffer = VclPtrInstance<VirtualDevice>(); + // Make sure that the buffer size matches the frame size. + mpWindowImpl->mpFrameData->mpBuffer->SetOutputSizePixel(mpWindowImpl->mpFrameWindow->GetOutputSizePixel()); + } + else + mpWindowImpl->mpFrameData->mpBuffer.reset(); +} + +/* + * The rationale here is that we moved destructors to + * dispose and this altered a lot of code paths, that + * are better left unchanged for now. + */ +void Window::CompatGetFocus() +{ + if (!mpWindowImpl || mpWindowImpl->mbInDispose) + Window::GetFocus(); + else + GetFocus(); +} + +void Window::CompatLoseFocus() +{ + if (!mpWindowImpl || mpWindowImpl->mbInDispose) + Window::LoseFocus(); + else + LoseFocus(); +} + +void Window::CompatStateChanged( StateChangedType nStateChange ) +{ + if (!mpWindowImpl || mpWindowImpl->mbInDispose) + Window::StateChanged(nStateChange); + else + StateChanged(nStateChange); +} + +void Window::CompatDataChanged( const DataChangedEvent& rDCEvt ) +{ + if (!mpWindowImpl || mpWindowImpl->mbInDispose) + Window::DataChanged(rDCEvt); + else + DataChanged(rDCEvt); +} + +bool Window::CompatPreNotify( NotifyEvent& rNEvt ) +{ + if (!mpWindowImpl || mpWindowImpl->mbInDispose) + return Window::PreNotify( rNEvt ); + else + return PreNotify( rNEvt ); +} + +bool Window::CompatNotify( NotifyEvent& rNEvt ) +{ + if (!mpWindowImpl || mpWindowImpl->mbInDispose) + return Window::EventNotify( rNEvt ); + else + return EventNotify( rNEvt ); +} + +void Window::set_id(const OUString& rID) +{ + mpWindowImpl->maID = rID; +} + +const OUString& Window::get_id() const +{ + static OUString empty; + return mpWindowImpl ? mpWindowImpl->maID : empty; +} + +FactoryFunction Window::GetUITestFactory() const +{ + return WindowUIObject::create; +} + +WindowOutputDevice::WindowOutputDevice(vcl::Window& rOwnerWindow) : + ::OutputDevice(OUTDEV_WINDOW), + mxOwnerWindow(&rOwnerWindow) +{ + assert(mxOwnerWindow); +} + +WindowOutputDevice::~WindowOutputDevice() +{ + disposeOnce(); +} + +void WindowOutputDevice::dispose() +{ + assert((!mxOwnerWindow || mxOwnerWindow->isDisposed()) && "This belongs to the associated window and must be disposed after it"); + ::OutputDevice::dispose(); + // need to do this after OutputDevice::dispose so that the call to WindowOutputDevice::ReleaseGraphics + // can release the graphics properly + mxOwnerWindow.clear(); +} + +css::awt::DeviceInfo WindowOutputDevice::GetDeviceInfo() const +{ + css::awt::DeviceInfo aInfo = GetCommonDeviceInfo(mxOwnerWindow->GetSizePixel()); + mxOwnerWindow->GetBorder(aInfo.LeftInset, aInfo.TopInset, aInfo.RightInset, aInfo.BottomInset); + return aInfo; +} + + +} /* namespace vcl */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/window2.cxx b/vcl/source/window/window2.cxx new file mode 100644 index 000000000..ad7677195 --- /dev/null +++ b/vcl/source/window/window2.cxx @@ -0,0 +1,2043 @@ +/* -*- 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 <limits.h> + +#include <o3tl/float_int_conversion.hxx> +#include <sal/log.hxx> + +#include <tools/helpers.hxx> + +#include <vcl/toolkit/dialog.hxx> +#include <vcl/event.hxx> +#include <vcl/toolkit/fixed.hxx> +#include <vcl/layout.hxx> +#include <vcl/timer.hxx> +#include <vcl/window.hxx> +#include <vcl/scrbar.hxx> +#include <vcl/dockwin.hxx> +#include <vcl/settings.hxx> +#include <vcl/builder.hxx> +#include <o3tl/string_view.hxx> + +#include <window.h> +#include <svdata.hxx> +#include <salgdi.hxx> +#include <salframe.hxx> +#include <scrwnd.hxx> + +#include <com/sun/star/accessibility/AccessibleRelation.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> + +using namespace com::sun::star; + +namespace vcl { + +void Window::ShowFocus( const tools::Rectangle& rRect ) +{ + if( mpWindowImpl->mbInShowFocus ) + return; + mpWindowImpl->mbInShowFocus = true; + + ImplWinData* pWinData = ImplGetWinData(); + + // native themeing suggest not to use focus rects + if( ! ( mpWindowImpl->mbUseNativeFocus && + IsNativeWidgetEnabled() ) ) + { + if ( !mpWindowImpl->mbInPaint ) + { + if ( mpWindowImpl->mbFocusVisible ) + { + if ( *pWinData->mpFocusRect == rRect ) + { + mpWindowImpl->mbInShowFocus = false; + return; + } + + ImplInvertFocus( *pWinData->mpFocusRect ); + } + + ImplInvertFocus( rRect ); + } + pWinData->mpFocusRect = rRect; + mpWindowImpl->mbFocusVisible = true; + } + else + { + if( ! mpWindowImpl->mbNativeFocusVisible ) + { + mpWindowImpl->mbNativeFocusVisible = true; + if ( !mpWindowImpl->mbInPaint ) + Invalidate(); + } + } + mpWindowImpl->mbInShowFocus = false; +} + +void Window::HideFocus() +{ + + if( mpWindowImpl->mbInHideFocus ) + return; + mpWindowImpl->mbInHideFocus = true; + + // native themeing can suggest not to use focus rects + if( ! ( mpWindowImpl->mbUseNativeFocus && + IsNativeWidgetEnabled() ) ) + { + if ( !mpWindowImpl->mbFocusVisible ) + { + mpWindowImpl->mbInHideFocus = false; + return; + } + + if ( !mpWindowImpl->mbInPaint ) + ImplInvertFocus( *ImplGetWinData()->mpFocusRect ); + mpWindowImpl->mbFocusVisible = false; + } + else + { + if( mpWindowImpl->mbNativeFocusVisible ) + { + mpWindowImpl->mbNativeFocusVisible = false; + if ( !mpWindowImpl->mbInPaint ) + Invalidate(); + } + } + mpWindowImpl->mbInHideFocus = false; +} + +void Window::ShowTracking( const tools::Rectangle& rRect, ShowTrackFlags nFlags ) +{ + ImplWinData* pWinData = ImplGetWinData(); + + if ( !mpWindowImpl->mbInPaint || !(nFlags & ShowTrackFlags::TrackWindow) ) + { + if ( mpWindowImpl->mbTrackVisible ) + { + if ( (*pWinData->mpTrackRect == rRect) && + (pWinData->mnTrackFlags == nFlags) ) + return; + + InvertTracking( *pWinData->mpTrackRect, pWinData->mnTrackFlags ); + } + + InvertTracking( rRect, nFlags ); + } + + pWinData->mpTrackRect = rRect; + pWinData->mnTrackFlags = nFlags; + mpWindowImpl->mbTrackVisible = true; +} + +void Window::HideTracking() +{ + if ( mpWindowImpl->mbTrackVisible ) + { + ImplWinData* pWinData = ImplGetWinData(); + if ( !mpWindowImpl->mbInPaint || !(pWinData->mnTrackFlags & ShowTrackFlags::TrackWindow) ) + InvertTracking( *pWinData->mpTrackRect, pWinData->mnTrackFlags ); + mpWindowImpl->mbTrackVisible = false; + } +} + +void Window::InvertTracking( const tools::Rectangle& rRect, ShowTrackFlags nFlags ) +{ + OutputDevice *pOutDev = GetOutDev(); + tools::Rectangle aRect( pOutDev->ImplLogicToDevicePixel( rRect ) ); + + if ( aRect.IsEmpty() ) + return; + aRect.Justify(); + + SalGraphics* pGraphics; + + if ( nFlags & ShowTrackFlags::TrackWindow ) + { + if ( !GetOutDev()->IsDeviceOutputNecessary() ) + return; + + // we need a graphics + if ( !GetOutDev()->mpGraphics ) + { + if ( !pOutDev->AcquireGraphics() ) + return; + } + + if ( GetOutDev()->mbInitClipRegion ) + GetOutDev()->InitClipRegion(); + + if ( GetOutDev()->mbOutputClipped ) + return; + + pGraphics = GetOutDev()->mpGraphics; + } + else + { + pGraphics = ImplGetFrameGraphics(); + + if ( nFlags & ShowTrackFlags::Clip ) + { + vcl::Region aRegion( GetOutputRectPixel() ); + ImplClipBoundaries( aRegion, false, false ); + pOutDev->SelectClipRegion( aRegion, pGraphics ); + } + } + + ShowTrackFlags nStyle = nFlags & ShowTrackFlags::StyleMask; + if ( nStyle == ShowTrackFlags::Object ) + pGraphics->Invert( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), SalInvert::TrackFrame, *GetOutDev() ); + else if ( nStyle == ShowTrackFlags::Split ) + pGraphics->Invert( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), SalInvert::N50, *GetOutDev() ); + else + { + tools::Long nBorder = 1; + if ( nStyle == ShowTrackFlags::Big ) + nBorder = 5; + pGraphics->Invert( aRect.Left(), aRect.Top(), aRect.GetWidth(), nBorder, SalInvert::N50, *GetOutDev() ); + pGraphics->Invert( aRect.Left(), aRect.Bottom()-nBorder+1, aRect.GetWidth(), nBorder, SalInvert::N50, *GetOutDev() ); + pGraphics->Invert( aRect.Left(), aRect.Top()+nBorder, nBorder, aRect.GetHeight()-(nBorder*2), SalInvert::N50, *GetOutDev() ); + pGraphics->Invert( aRect.Right()-nBorder+1, aRect.Top()+nBorder, nBorder, aRect.GetHeight()-(nBorder*2), SalInvert::N50, *GetOutDev() ); + } +} + +IMPL_LINK( Window, ImplTrackTimerHdl, Timer*, pTimer, void ) +{ + ImplSVData* pSVData = ImplGetSVData(); + + // if Button-Repeat we have to change the timeout + if ( pSVData->mpWinData->mnTrackFlags & StartTrackingFlags::ButtonRepeat ) + pTimer->SetTimeout( GetSettings().GetMouseSettings().GetButtonRepeat() ); + + // create Tracking-Event + Point aMousePos( mpWindowImpl->mpFrameData->mnLastMouseX, mpWindowImpl->mpFrameData->mnLastMouseY ); + if( GetOutDev()->ImplIsAntiparallel() ) + { + // re-mirror frame pos at pChild + const OutputDevice *pOutDev = GetOutDev(); + pOutDev->ReMirror( aMousePos ); + } + MouseEvent aMEvt( ImplFrameToOutput( aMousePos ), + mpWindowImpl->mpFrameData->mnClickCount, MouseEventModifiers::NONE, + mpWindowImpl->mpFrameData->mnMouseCode, + mpWindowImpl->mpFrameData->mnMouseCode ); + TrackingEvent aTEvt( aMEvt, TrackingEventFlags::Repeat ); + Tracking( aTEvt ); +} + +void Window::SetUseFrameData(bool bUseFrameData) +{ + if (mpWindowImpl) + mpWindowImpl->mbUseFrameData = bUseFrameData; +} + +void Window::StartTracking( StartTrackingFlags nFlags ) +{ + if (!mpWindowImpl) + return; + + ImplSVData* pSVData = ImplGetSVData(); + VclPtr<vcl::Window> pTrackWin = mpWindowImpl->mbUseFrameData ? + mpWindowImpl->mpFrameData->mpTrackWin : + pSVData->mpWinData->mpTrackWin; + + if ( pTrackWin.get() != this ) + { + if ( pTrackWin ) + pTrackWin->EndTracking( TrackingEventFlags::Cancel ); + } + + if ( !mpWindowImpl->mbUseFrameData && + (nFlags & (StartTrackingFlags::ScrollRepeat | StartTrackingFlags::ButtonRepeat)) ) + { + pSVData->mpWinData->mpTrackTimer = new AutoTimer("vcl::Window pSVData->mpWinData->mpTrackTimer"); + + if ( nFlags & StartTrackingFlags::ScrollRepeat ) + pSVData->mpWinData->mpTrackTimer->SetTimeout( MouseSettings::GetScrollRepeat() ); + else + pSVData->mpWinData->mpTrackTimer->SetTimeout( MouseSettings::GetButtonStartRepeat() ); + pSVData->mpWinData->mpTrackTimer->SetInvokeHandler( LINK( this, Window, ImplTrackTimerHdl ) ); + pSVData->mpWinData->mpTrackTimer->Start(); + } + + if (mpWindowImpl->mbUseFrameData) + { + mpWindowImpl->mpFrameData->mpTrackWin = this; + } + else + { + pSVData->mpWinData->mpTrackWin = this; + pSVData->mpWinData->mnTrackFlags = nFlags; + CaptureMouse(); + } +} + +void Window::EndTracking( TrackingEventFlags nFlags ) +{ + if (!mpWindowImpl) + return; + + ImplSVData* pSVData = ImplGetSVData(); + VclPtr<vcl::Window> pTrackWin = mpWindowImpl->mbUseFrameData ? + mpWindowImpl->mpFrameData->mpTrackWin : + pSVData->mpWinData->mpTrackWin; + + if ( pTrackWin.get() != this ) + return; + + if ( !mpWindowImpl->mbUseFrameData && pSVData->mpWinData->mpTrackTimer ) + { + delete pSVData->mpWinData->mpTrackTimer; + pSVData->mpWinData->mpTrackTimer = nullptr; + } + + mpWindowImpl->mpFrameData->mpTrackWin = pSVData->mpWinData->mpTrackWin = nullptr; + pSVData->mpWinData->mnTrackFlags = StartTrackingFlags::NONE; + ReleaseMouse(); + + // call EndTracking if required + if (mpWindowImpl->mpFrameData) + { + Point aMousePos( mpWindowImpl->mpFrameData->mnLastMouseX, mpWindowImpl->mpFrameData->mnLastMouseY ); + if( GetOutDev()->ImplIsAntiparallel() ) + { + // re-mirror frame pos at pChild + const OutputDevice *pOutDev = GetOutDev(); + pOutDev->ReMirror( aMousePos ); + } + + MouseEvent aMEvt( ImplFrameToOutput( aMousePos ), + mpWindowImpl->mpFrameData->mnClickCount, MouseEventModifiers::NONE, + mpWindowImpl->mpFrameData->mnMouseCode, + mpWindowImpl->mpFrameData->mnMouseCode ); + TrackingEvent aTEvt( aMEvt, nFlags | TrackingEventFlags::End ); + // CompatTracking effectively + if (!mpWindowImpl || mpWindowImpl->mbInDispose) + return Window::Tracking( aTEvt ); + else + return Tracking( aTEvt ); + } +} + +bool Window::IsTracking() const +{ + return (mpWindowImpl->mbUseFrameData ? + mpWindowImpl->mpFrameData->mpTrackWin == this : + ImplGetSVData()->mpWinData->mpTrackWin == this); +} + +void Window::StartAutoScroll( StartAutoScrollFlags nFlags ) +{ + ImplSVData* pSVData = ImplGetSVData(); + + if ( pSVData->mpWinData->mpAutoScrollWin.get() != this ) + { + if ( pSVData->mpWinData->mpAutoScrollWin ) + pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll(); + } + + pSVData->mpWinData->mpAutoScrollWin = this; + pSVData->mpWinData->mnAutoScrollFlags = nFlags; + pSVData->maAppData.mpWheelWindow = VclPtr<ImplWheelWindow>::Create( this ); +} + +void Window::EndAutoScroll() +{ + ImplSVData* pSVData = ImplGetSVData(); + + if ( pSVData->mpWinData->mpAutoScrollWin.get() == this ) + { + pSVData->mpWinData->mpAutoScrollWin = nullptr; + pSVData->mpWinData->mnAutoScrollFlags = StartAutoScrollFlags::NONE; + pSVData->maAppData.mpWheelWindow->ImplStop(); + pSVData->maAppData.mpWheelWindow.disposeAndClear(); + } +} + +VclPtr<vcl::Window> Window::SaveFocus() +{ + ImplSVData* pSVData = ImplGetSVData(); + if ( pSVData->mpWinData->mpFocusWin ) + { + return pSVData->mpWinData->mpFocusWin; + } + else + return nullptr; +} + +void Window::EndSaveFocus(const VclPtr<vcl::Window>& xFocusWin) +{ + if (xFocusWin && !xFocusWin->isDisposed()) + { + xFocusWin->GrabFocus(); + } +} + +void Window::SetZoom( const Fraction& rZoom ) +{ + if ( mpWindowImpl && mpWindowImpl->maZoom != rZoom ) + { + mpWindowImpl->maZoom = rZoom; + CompatStateChanged( StateChangedType::Zoom ); + } +} + +void Window::SetZoomedPointFont(vcl::RenderContext& rRenderContext, const vcl::Font& rFont) +{ + const Fraction& rZoom = GetZoom(); + if (rZoom.GetNumerator() != rZoom.GetDenominator()) + { + vcl::Font aFont(rFont); + Size aSize = aFont.GetFontSize(); + aSize.setWidth( FRound(double(aSize.Width() * rZoom)) ); + aSize.setHeight( FRound(double(aSize.Height() * rZoom)) ); + aFont.SetFontSize(aSize); + SetPointFont(rRenderContext, aFont); + } + else + { + SetPointFont(rRenderContext, rFont); + } +} + +tools::Long Window::CalcZoom( tools::Long nCalc ) const +{ + + const Fraction& rZoom = GetZoom(); + if ( rZoom.GetNumerator() != rZoom.GetDenominator() ) + { + double n = double(nCalc * rZoom); + nCalc = FRound( n ); + } + return nCalc; +} + +void Window::SetControlFont() +{ + if (mpWindowImpl && mpWindowImpl->mpControlFont) + { + mpWindowImpl->mpControlFont.reset(); + CompatStateChanged(StateChangedType::ControlFont); + } +} + +void Window::SetControlFont(const vcl::Font& rFont) +{ + if (rFont == vcl::Font()) + { + SetControlFont(); + return; + } + + if (mpWindowImpl->mpControlFont) + { + if (*mpWindowImpl->mpControlFont == rFont) + return; + *mpWindowImpl->mpControlFont = rFont; + } + else + mpWindowImpl->mpControlFont = rFont; + + CompatStateChanged(StateChangedType::ControlFont); +} + +vcl::Font Window::GetControlFont() const +{ + if (mpWindowImpl->mpControlFont) + return *mpWindowImpl->mpControlFont; + else + { + vcl::Font aFont; + return aFont; + } +} + +void Window::ApplyControlFont(vcl::RenderContext& rRenderContext, const vcl::Font& rFont) +{ + vcl::Font aFont(rFont); + if (IsControlFont()) + aFont.Merge(GetControlFont()); + SetZoomedPointFont(rRenderContext, aFont); +} + +void Window::SetControlForeground() +{ + if (mpWindowImpl->mbControlForeground) + { + mpWindowImpl->maControlForeground = COL_TRANSPARENT; + mpWindowImpl->mbControlForeground = false; + CompatStateChanged(StateChangedType::ControlForeground); + } +} + +void Window::SetControlForeground(const Color& rColor) +{ + if (rColor.IsTransparent()) + { + if (mpWindowImpl->mbControlForeground) + { + mpWindowImpl->maControlForeground = COL_TRANSPARENT; + mpWindowImpl->mbControlForeground = false; + CompatStateChanged(StateChangedType::ControlForeground); + } + } + else + { + if (mpWindowImpl->maControlForeground != rColor) + { + mpWindowImpl->maControlForeground = rColor; + mpWindowImpl->mbControlForeground = true; + CompatStateChanged(StateChangedType::ControlForeground); + } + } +} + +void Window::ApplyControlForeground(vcl::RenderContext& rRenderContext, const Color& rDefaultColor) +{ + Color aTextColor(rDefaultColor); + if (IsControlForeground()) + aTextColor = GetControlForeground(); + rRenderContext.SetTextColor(aTextColor); +} + +void Window::SetControlBackground() +{ + if (mpWindowImpl->mbControlBackground) + { + mpWindowImpl->maControlBackground = COL_TRANSPARENT; + mpWindowImpl->mbControlBackground = false; + CompatStateChanged(StateChangedType::ControlBackground); + } +} + +void Window::SetControlBackground(const Color& rColor) +{ + if (rColor.IsTransparent()) + { + if (mpWindowImpl->mbControlBackground) + { + mpWindowImpl->maControlBackground = COL_TRANSPARENT; + mpWindowImpl->mbControlBackground = false; + CompatStateChanged(StateChangedType::ControlBackground); + } + } + else + { + if (mpWindowImpl->maControlBackground != rColor) + { + mpWindowImpl->maControlBackground = rColor; + mpWindowImpl->mbControlBackground = true; + CompatStateChanged(StateChangedType::ControlBackground); + } + } +} + +void Window::ApplyControlBackground(vcl::RenderContext& rRenderContext, const Color& rDefaultColor) +{ + Color aColor(rDefaultColor); + if (IsControlBackground()) + aColor = GetControlBackground(); + rRenderContext.SetBackground(aColor); +} + +Size Window::CalcWindowSize( const Size& rOutSz ) const +{ + Size aSz = rOutSz; + aSz.AdjustWidth(mpWindowImpl->mnLeftBorder+mpWindowImpl->mnRightBorder ); + aSz.AdjustHeight(mpWindowImpl->mnTopBorder+mpWindowImpl->mnBottomBorder ); + return aSz; +} + +Size Window::CalcOutputSize( const Size& rWinSz ) const +{ + Size aSz = rWinSz; + aSz.AdjustWidth( -(mpWindowImpl->mnLeftBorder+mpWindowImpl->mnRightBorder) ); + aSz.AdjustHeight( -(mpWindowImpl->mnTopBorder+mpWindowImpl->mnBottomBorder) ); + return aSz; +} + +vcl::Font Window::GetDrawPixelFont(OutputDevice const * pDev) const +{ + vcl::Font aFont = GetPointFont(*GetOutDev()); + Size aFontSize = aFont.GetFontSize(); + MapMode aPtMapMode(MapUnit::MapPoint); + aFontSize = pDev->LogicToPixel( aFontSize, aPtMapMode ); + aFont.SetFontSize( aFontSize ); + return aFont; +} + +tools::Long Window::GetDrawPixel( OutputDevice const * pDev, tools::Long nPixels ) const +{ + tools::Long nP = nPixels; + if ( pDev->GetOutDevType() != OUTDEV_WINDOW ) + { + MapMode aMap( MapUnit::Map100thMM ); + Size aSz( nP, 0 ); + aSz = PixelToLogic( aSz, aMap ); + aSz = pDev->LogicToPixel( aSz, aMap ); + nP = aSz.Width(); + } + return nP; +} + +// returns how much was actually scrolled (so that abs(retval) <= abs(nN)) +static double lcl_HandleScrollHelper( ScrollBar* pScrl, double nN, bool isMultiplyByLineSize ) +{ + if ( !pScrl || !nN || !pScrl->IsEnabled() || !pScrl->IsInputEnabled() || pScrl->IsInModalMode() ) + return 0.0; + + tools::Long nNewPos = pScrl->GetThumbPos(); + double scrolled = nN; + + if ( nN == double(-LONG_MAX) ) + nNewPos += pScrl->GetPageSize(); + else if ( nN == double(LONG_MAX) ) + nNewPos -= pScrl->GetPageSize(); + else + { + // allowing both chunked and continuous scrolling + if(isMultiplyByLineSize){ + nN*=pScrl->GetLineSize(); + } + + // compute how many quantized units to scroll + tools::Long magnitude = o3tl::saturating_cast<tools::Long>(abs(nN)); + tools::Long change = copysign(magnitude, nN); + + nNewPos = nNewPos - change; + + scrolled = double(change); + // convert back to chunked/continuous + if(isMultiplyByLineSize){ + scrolled /= pScrl->GetLineSize(); + } + } + + pScrl->DoScroll( nNewPos ); + + return scrolled; +} + +bool Window::HandleScrollCommand( const CommandEvent& rCmd, + ScrollBar* pHScrl, ScrollBar* pVScrl ) +{ + bool bRet = false; + + if ( pHScrl || pVScrl ) + { + switch( rCmd.GetCommand() ) + { + case CommandEventId::StartAutoScroll: + { + StartAutoScrollFlags nFlags = StartAutoScrollFlags::NONE; + if ( pHScrl ) + { + if ( (pHScrl->GetVisibleSize() < pHScrl->GetRangeMax()) && + pHScrl->IsEnabled() && pHScrl->IsInputEnabled() && ! pHScrl->IsInModalMode() ) + nFlags |= StartAutoScrollFlags::Horz; + } + if ( pVScrl ) + { + if ( (pVScrl->GetVisibleSize() < pVScrl->GetRangeMax()) && + pVScrl->IsEnabled() && pVScrl->IsInputEnabled() && ! pVScrl->IsInModalMode() ) + nFlags |= StartAutoScrollFlags::Vert; + } + + if ( nFlags != StartAutoScrollFlags::NONE ) + { + StartAutoScroll( nFlags ); + bRet = true; + } + } + break; + + case CommandEventId::Wheel: + { + const CommandWheelData* pData = rCmd.GetWheelData(); + + if ( pData && (CommandWheelMode::SCROLL == pData->GetMode()) ) + { + if (!pData->IsDeltaPixel()) + { + double nScrollLines = pData->GetScrollLines(); + double nLines; + double* partialScroll = pData->IsHorz() + ? &mpWindowImpl->mfPartialScrollX + : &mpWindowImpl->mfPartialScrollY; + if ( nScrollLines == COMMAND_WHEEL_PAGESCROLL ) + { + if ( pData->GetDelta() < 0 ) + nLines = double(-LONG_MAX); + else + nLines = double(LONG_MAX); + } + else + nLines = *partialScroll + pData->GetNotchDelta() * nScrollLines; + if ( nLines ) + { + ScrollBar* pScrl = pData->IsHorz() ? pHScrl : pVScrl; + double scrolled = lcl_HandleScrollHelper( pScrl, nLines, true ); + *partialScroll = nLines - scrolled; + bRet = true; + } + } + else + { + // Mobile / touch scrolling section + const Point & deltaPoint = rCmd.GetMousePosPixel(); + + double deltaXInPixels = double(deltaPoint.X()); + double deltaYInPixels = double(deltaPoint.Y()); + Size winSize = GetOutputSizePixel(); + + if(pHScrl) + { + double visSizeX = double(pHScrl->GetVisibleSize()); + double ratioX = deltaXInPixels / double(winSize.getWidth()); + tools::Long deltaXInLogic = tools::Long(visSizeX * ratioX); + // Touch need to work by pixels. Did not apply this to + // Android, as android code may require adaptations + // to work with this scrolling code +#ifndef IOS + tools::Long lineSizeX = pHScrl->GetLineSize(); + + if(lineSizeX) + { + deltaXInLogic /= lineSizeX; + } + else + { + deltaXInLogic = 0; + } +#endif + if ( deltaXInLogic) + { +#ifndef IOS + bool const isMultiplyByLineSize = true; +#else + bool const isMultiplyByLineSize = false; +#endif + lcl_HandleScrollHelper( pHScrl, deltaXInLogic, isMultiplyByLineSize ); + bRet = true; + } + } + if(pVScrl) + { + double visSizeY = double(pVScrl->GetVisibleSize()); + double ratioY = deltaYInPixels / double(winSize.getHeight()); + tools::Long deltaYInLogic = tools::Long(visSizeY * ratioY); + + // Touch need to work by pixels. Did not apply this to + // Android, as android code may require adaptations + // to work with this scrolling code +#ifndef IOS + tools::Long lineSizeY = pVScrl->GetLineSize(); + if(lineSizeY) + { + deltaYInLogic /= lineSizeY; + } + else + { + deltaYInLogic = 0; + } +#endif + if ( deltaYInLogic ) + { +#ifndef IOS + bool const isMultiplyByLineSize = true; +#else + bool const isMultiplyByLineSize = false; +#endif + lcl_HandleScrollHelper( pVScrl, deltaYInLogic, isMultiplyByLineSize ); + + bRet = true; + } + } + } + } + } + break; + + case CommandEventId::Gesture: + { + if (pVScrl) + { + const CommandGestureData* pData = rCmd.GetGestureData(); + if (pData->meEventType == GestureEventType::PanningBegin) + { + mpWindowImpl->mpFrameData->mnTouchPanPosition = pVScrl->GetThumbPos(); + } + else if(pData->meEventType == GestureEventType::PanningUpdate) + { + tools::Long nOriginalPosition = mpWindowImpl->mpFrameData->mnTouchPanPosition; + pVScrl->DoScroll(nOriginalPosition + (pData->mfOffset / pVScrl->GetVisibleSize())); + } + if (pData->meEventType == GestureEventType::PanningEnd) + { + mpWindowImpl->mpFrameData->mnTouchPanPosition = -1; + } + bRet = true; + } + break; + } + + case CommandEventId::AutoScroll: + { + const CommandScrollData* pData = rCmd.GetAutoScrollData(); + if ( pData && (pData->GetDeltaX() || pData->GetDeltaY()) ) + { + ImplHandleScroll( pHScrl, pData->GetDeltaX(), + pVScrl, pData->GetDeltaY() ); + bRet = true; + } + } + break; + + default: + break; + } + } + + return bRet; +} + +void Window::ImplHandleScroll( ScrollBar* pHScrl, double nX, + ScrollBar* pVScrl, double nY ) +{ + lcl_HandleScrollHelper( pHScrl, nX, true ); + lcl_HandleScrollHelper( pVScrl, nY, true ); +} + +DockingManager* Window::GetDockingManager() +{ + return ImplGetDockingManager(); +} + +void Window::EnableDocking( bool bEnable ) +{ + // update list of dockable windows + if( bEnable ) + ImplGetDockingManager()->AddWindow( this ); + else + ImplGetDockingManager()->RemoveWindow( this ); +} + +// retrieves the list of owner draw decorated windows for this window hierarchy +::std::vector<VclPtr<vcl::Window> >& Window::ImplGetOwnerDrawList() +{ + return ImplGetTopmostFrameWindow()->mpWindowImpl->mpFrameData->maOwnerDrawList; +} + +void Window::SetHelpId( const OString& rHelpId ) +{ + mpWindowImpl->maHelpId = rHelpId; +} + +const OString& Window::GetHelpId() const +{ + return mpWindowImpl->maHelpId; +} + +// --------- old inline methods --------------- + +vcl::Window* Window::ImplGetWindow() const +{ + if ( mpWindowImpl->mpClientWindow ) + return mpWindowImpl->mpClientWindow; + else + return const_cast<vcl::Window*>(this); +} + +ImplFrameData* Window::ImplGetFrameData() +{ + return mpWindowImpl ? mpWindowImpl->mpFrameData : nullptr; +} + +SalFrame* Window::ImplGetFrame() const +{ + return mpWindowImpl ? mpWindowImpl->mpFrame : nullptr; +} + +weld::Window* Window::GetFrameWeld() const +{ + SalFrame* pFrame = ImplGetFrame(); + return pFrame ? pFrame->GetFrameWeld() : nullptr; +} + +vcl::Window* Window::GetFrameWindow() const +{ + SalFrame* pFrame = ImplGetFrame(); + return pFrame ? pFrame->GetWindow() : nullptr; +} + +vcl::Window* Window::ImplGetParent() const +{ + return mpWindowImpl ? mpWindowImpl->mpParent.get() : nullptr; +} + +vcl::Window* Window::ImplGetClientWindow() const +{ + return mpWindowImpl ? mpWindowImpl->mpClientWindow.get() : nullptr; +} + +vcl::Window* Window::ImplGetBorderWindow() const +{ + return mpWindowImpl ? mpWindowImpl->mpBorderWindow.get() : nullptr; +} + +vcl::Window* Window::ImplGetFirstOverlapWindow() +{ + if (!mpWindowImpl) + { + return nullptr; + } + + if ( mpWindowImpl->mbOverlapWin ) + return this; + else + return mpWindowImpl->mpOverlapWindow; +} + +const vcl::Window* Window::ImplGetFirstOverlapWindow() const +{ + if (!mpWindowImpl) + { + return nullptr; + } + + if ( mpWindowImpl->mbOverlapWin ) + return this; + else + return mpWindowImpl->mpOverlapWindow; +} + +vcl::Window* Window::ImplGetFrameWindow() const +{ + return mpWindowImpl ? mpWindowImpl->mpFrameWindow.get() : nullptr; +} + +bool Window::IsDockingWindow() const +{ + return mpWindowImpl && mpWindowImpl->mbDockWin; +} + +bool Window::ImplIsFloatingWindow() const +{ + return mpWindowImpl && mpWindowImpl->mbFloatWin; +} + +bool Window::ImplIsSplitter() const +{ + return mpWindowImpl && mpWindowImpl->mbSplitter; +} + +bool Window::ImplIsPushButton() const +{ + return mpWindowImpl && mpWindowImpl->mbPushButton; +} + +bool Window::ImplIsOverlapWindow() const +{ + return mpWindowImpl && mpWindowImpl->mbOverlapWin; +} + +void Window::ImplSetMouseTransparent( bool bTransparent ) +{ + if (mpWindowImpl) + mpWindowImpl->mbMouseTransparent = bTransparent; +} + +Point Window::ImplOutputToFrame( const Point& rPos ) +{ + return Point( rPos.X()+GetOutDev()->mnOutOffX, rPos.Y()+GetOutDev()->mnOutOffY ); +} + +Point Window::ImplFrameToOutput( const Point& rPos ) +{ + return Point( rPos.X()-GetOutDev()->mnOutOffX, rPos.Y()-GetOutDev()->mnOutOffY ); +} + +void Window::SetCompoundControl( bool bCompound ) +{ + if (mpWindowImpl) + mpWindowImpl->mbCompoundControl = bCompound; +} + +WinBits Window::GetStyle() const +{ + return mpWindowImpl ? mpWindowImpl->mnStyle : 0; +} + +WinBits Window::GetPrevStyle() const +{ + return mpWindowImpl ? mpWindowImpl->mnPrevStyle : 0; +} + +WindowExtendedStyle Window::GetExtendedStyle() const +{ + return mpWindowImpl ? mpWindowImpl->mnExtendedStyle : WindowExtendedStyle::NONE; +} + +void Window::SetType( WindowType nType ) +{ + if (mpWindowImpl) + mpWindowImpl->mnType = nType; +} + +WindowType Window::GetType() const +{ + if (mpWindowImpl) + return mpWindowImpl->mnType; + else + return WindowType::NONE; +} + +Dialog* Window::GetParentDialog() const +{ + const vcl::Window *pWindow = this; + + while( pWindow ) + { + if( pWindow->IsDialog() ) + break; + + pWindow = pWindow->GetParent(); + } + + return const_cast<Dialog *>(dynamic_cast<const Dialog*>(pWindow)); +} + +bool Window::IsSystemWindow() const +{ + return mpWindowImpl && mpWindowImpl->mbSysWin; +} + +bool Window::IsDialog() const +{ + return mpWindowImpl && mpWindowImpl->mbDialog; +} + +bool Window::IsMenuFloatingWindow() const +{ + return mpWindowImpl && mpWindowImpl->mbMenuFloatingWindow; +} + +bool Window::IsToolbarFloatingWindow() const +{ + return mpWindowImpl && mpWindowImpl->mbToolbarFloatingWindow; +} + +void Window::EnableAllResize() +{ + mpWindowImpl->mbAllResize = true; +} + +void Window::EnableChildTransparentMode( bool bEnable ) +{ + mpWindowImpl->mbChildTransparent = bEnable; +} + +bool Window::IsChildTransparentModeEnabled() const +{ + return mpWindowImpl && mpWindowImpl->mbChildTransparent; +} + +bool Window::IsMouseTransparent() const +{ + return mpWindowImpl && mpWindowImpl->mbMouseTransparent; +} + +bool Window::IsPaintTransparent() const +{ + return mpWindowImpl && mpWindowImpl->mbPaintTransparent; +} + +void Window::SetDialogControlStart( bool bStart ) +{ + mpWindowImpl->mbDlgCtrlStart = bStart; +} + +bool Window::IsDialogControlStart() const +{ + return mpWindowImpl && mpWindowImpl->mbDlgCtrlStart; +} + +void Window::SetDialogControlFlags( DialogControlFlags nFlags ) +{ + mpWindowImpl->mnDlgCtrlFlags = nFlags; +} + +DialogControlFlags Window::GetDialogControlFlags() const +{ + return mpWindowImpl->mnDlgCtrlFlags; +} + +const InputContext& Window::GetInputContext() const +{ + return mpWindowImpl->maInputContext; +} + +bool Window::IsControlFont() const +{ + return bool(mpWindowImpl->mpControlFont); +} + +const Color& Window::GetControlForeground() const +{ + return mpWindowImpl->maControlForeground; +} + +bool Window::IsControlForeground() const +{ + return mpWindowImpl->mbControlForeground; +} + +const Color& Window::GetControlBackground() const +{ + return mpWindowImpl->maControlBackground; +} + +bool Window::IsControlBackground() const +{ + return mpWindowImpl->mbControlBackground; +} + +bool Window::IsInPaint() const +{ + return mpWindowImpl && mpWindowImpl->mbInPaint; +} + +vcl::Window* Window::GetParent() const +{ + return mpWindowImpl ? mpWindowImpl->mpRealParent.get() : nullptr; +} + +bool Window::IsVisible() const +{ + return mpWindowImpl && mpWindowImpl->mbVisible; +} + +bool Window::IsReallyVisible() const +{ + return mpWindowImpl && mpWindowImpl->mbReallyVisible; +} + +bool Window::IsReallyShown() const +{ + return mpWindowImpl && mpWindowImpl->mbReallyShown; +} + +bool Window::IsInInitShow() const +{ + return mpWindowImpl->mbInInitShow; +} + +bool Window::IsEnabled() const +{ + return mpWindowImpl && !mpWindowImpl->mbDisabled; +} + +bool Window::IsInputEnabled() const +{ + return mpWindowImpl && !mpWindowImpl->mbInputDisabled; +} + +bool Window::IsAlwaysEnableInput() const +{ + return mpWindowImpl->meAlwaysInputMode == AlwaysInputEnabled; +} + +ActivateModeFlags Window::GetActivateMode() const +{ + return mpWindowImpl->mnActivateMode; + +} + +bool Window::IsAlwaysOnTopEnabled() const +{ + return mpWindowImpl->mbAlwaysOnTop; +} + +bool Window::IsDefaultPos() const +{ + return mpWindowImpl->mbDefPos; +} + +bool Window::IsDefaultSize() const +{ + return mpWindowImpl->mbDefSize; +} + +Point Window::GetOffsetPixelFrom(const vcl::Window& rWindow) const +{ + return Point(GetOutOffXPixel() - rWindow.GetOutOffXPixel(), GetOutOffYPixel() - rWindow.GetOutOffYPixel()); +} + +void Window::EnablePaint( bool bEnable ) +{ + mpWindowImpl->mbPaintDisabled = !bEnable; +} + +bool Window::IsPaintEnabled() const +{ + return !mpWindowImpl->mbPaintDisabled; +} + +bool Window::IsUpdateMode() const +{ + return !mpWindowImpl->mbNoUpdate; +} + +void Window::SetParentUpdateMode( bool bUpdate ) +{ + mpWindowImpl->mbNoParentUpdate = !bUpdate; +} + +bool Window::IsActive() const +{ + return mpWindowImpl->mbActive; +} + +GetFocusFlags Window::GetGetFocusFlags() const +{ + return mpWindowImpl->mnGetFocusFlags; +} + +bool Window::IsCompoundControl() const +{ + return mpWindowImpl && mpWindowImpl->mbCompoundControl; +} + +bool Window::IsWait() const +{ + return (mpWindowImpl->mnWaitCount != 0); +} + +vcl::Cursor* Window::GetCursor() const +{ + if (!mpWindowImpl) + return nullptr; + return mpWindowImpl->mpCursor; +} + +const Fraction& Window::GetZoom() const +{ + return mpWindowImpl->maZoom; +} + +bool Window::IsZoom() const +{ + return mpWindowImpl->maZoom.GetNumerator() != mpWindowImpl->maZoom.GetDenominator(); +} + +void Window::SetHelpText( const OUString& rHelpText ) +{ + mpWindowImpl->maHelpText = rHelpText; + mpWindowImpl->mbHelpTextDynamic = true; +} + +void Window::SetQuickHelpText( const OUString& rHelpText ) +{ + if (mpWindowImpl) + mpWindowImpl->maQuickHelpText = rHelpText; +} + +const OUString& Window::GetQuickHelpText() const +{ + return mpWindowImpl->maQuickHelpText; +} + +bool Window::IsCreatedWithToolkit() const +{ + return mpWindowImpl->mbCreatedWithToolkit; +} + +void Window::SetCreatedWithToolkit( bool b ) +{ + mpWindowImpl->mbCreatedWithToolkit = b; +} + +PointerStyle Window::GetPointer() const +{ + return mpWindowImpl->maPointer; +} + +VCLXWindow* Window::GetWindowPeer() const +{ + return mpWindowImpl ? mpWindowImpl->mpVCLXWindow : nullptr; +} + +void Window::SetPosPixel( const Point& rNewPos ) +{ + setPosSizePixel( rNewPos.X(), rNewPos.Y(), 0, 0, PosSizeFlags::Pos ); +} + +void Window::SetSizePixel( const Size& rNewSize ) +{ + setPosSizePixel( 0, 0, rNewSize.Width(), rNewSize.Height(), + PosSizeFlags::Size ); +} + +void Window::SetPosSizePixel( const Point& rNewPos, const Size& rNewSize ) +{ + setPosSizePixel( rNewPos.X(), rNewPos.Y(), + rNewSize.Width(), rNewSize.Height()); +} + +void Window::SetOutputSizePixel( const Size& rNewSize ) +{ + SetSizePixel( Size( rNewSize.Width()+mpWindowImpl->mnLeftBorder+mpWindowImpl->mnRightBorder, + rNewSize.Height()+mpWindowImpl->mnTopBorder+mpWindowImpl->mnBottomBorder ) ); +} + +//When a widget wants to renegotiate layout, get toplevel parent dialog and call +//resize on it. Mark all intermediate containers (or container-alike) widgets +//as dirty for the size remains unchanged, but layout changed circumstances +namespace +{ + bool queue_ungrouped_resize(vcl::Window const *pOrigWindow) + { + bool bSomeoneCares = false; + + vcl::Window *pWindow = pOrigWindow->GetParent(); + if (pWindow) + { + if (isContainerWindow(*pWindow)) + { + bSomeoneCares = true; + } + else if (pWindow->GetType() == WindowType::TABCONTROL) + { + bSomeoneCares = true; + } + pWindow->queue_resize(); + } + + return bSomeoneCares; + } +} + +void Window::InvalidateSizeCache() +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->mnOptimalWidthCache = -1; + pWindowImpl->mnOptimalHeightCache = -1; +} + +static bool HasParentDockingWindow(const vcl::Window* pWindow) +{ + while( pWindow ) + { + if( pWindow->IsDockingWindow() ) + return true; + + pWindow = pWindow->GetParent(); + } + + return false; +} + +void Window::queue_resize(StateChangedType eReason) +{ + if (isDisposed()) + return; + + bool bSomeoneCares = queue_ungrouped_resize(this); + + if (eReason != StateChangedType::Visible) + { + InvalidateSizeCache(); + } + + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + if (pWindowImpl->m_xSizeGroup && pWindowImpl->m_xSizeGroup->get_mode() != VclSizeGroupMode::NONE) + { + std::set<VclPtr<vcl::Window> > &rWindows = pWindowImpl->m_xSizeGroup->get_widgets(); + for (VclPtr<vcl::Window> const & pOther : rWindows) + { + if (pOther == this) + continue; + queue_ungrouped_resize(pOther); + } + } + + if (bSomeoneCares && !isDisposed()) + { + //fdo#57090 force a resync of the borders of the borderwindow onto this + //window in case they have changed + vcl::Window* pBorderWindow = ImplGetBorderWindow(); + if (pBorderWindow) + pBorderWindow->Resize(); + } + if (VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier()) + { + Size aSize = GetSizePixel(); + if (!aSize.IsEmpty() && !pParent->IsInInitShow() + && (GetParentDialog() || HasParentDockingWindow(this))) + LogicInvalidate(nullptr); + } +} + +namespace +{ + VclAlign toAlign(std::u16string_view rValue) + { + VclAlign eRet = VclAlign::Fill; + + if (rValue == u"fill") + eRet = VclAlign::Fill; + else if (rValue == u"start") + eRet = VclAlign::Start; + else if (rValue == u"end") + eRet = VclAlign::End; + else if (rValue == u"center") + eRet = VclAlign::Center; + return eRet; + } +} + +bool Window::set_font_attribute(const OString &rKey, std::u16string_view rValue) +{ + if (rKey == "weight") + { + vcl::Font aFont(GetControlFont()); + if (rValue == u"thin") + aFont.SetWeight(WEIGHT_THIN); + else if (rValue == u"ultralight") + aFont.SetWeight(WEIGHT_ULTRALIGHT); + else if (rValue == u"light") + aFont.SetWeight(WEIGHT_LIGHT); + else if (rValue == u"book") + aFont.SetWeight(WEIGHT_SEMILIGHT); + else if (rValue == u"normal") + aFont.SetWeight(WEIGHT_NORMAL); + else if (rValue == u"medium") + aFont.SetWeight(WEIGHT_MEDIUM); + else if (rValue == u"semibold") + aFont.SetWeight(WEIGHT_SEMIBOLD); + else if (rValue == u"bold") + aFont.SetWeight(WEIGHT_BOLD); + else if (rValue == u"ultrabold") + aFont.SetWeight(WEIGHT_ULTRABOLD); + else + aFont.SetWeight(WEIGHT_BLACK); + SetControlFont(aFont); + } + else if (rKey == "style") + { + vcl::Font aFont(GetControlFont()); + if (rValue == u"normal") + aFont.SetItalic(ITALIC_NONE); + else if (rValue == u"oblique") + aFont.SetItalic(ITALIC_OBLIQUE); + else if (rValue == u"italic") + aFont.SetItalic(ITALIC_NORMAL); + SetControlFont(aFont); + } + else if (rKey == "underline") + { + vcl::Font aFont(GetControlFont()); + aFont.SetUnderline(toBool(rValue) ? LINESTYLE_SINGLE : LINESTYLE_NONE); + SetControlFont(aFont); + } + else if (rKey == "scale") + { + // if no control font was set yet, take the underlying font from the device + vcl::Font aFont(IsControlFont() ? GetControlFont() : GetPointFont(*GetOutDev())); + aFont.SetFontHeight(aFont.GetFontHeight() * o3tl::toDouble(rValue)); + SetControlFont(aFont); + } + else if (rKey == "size") + { + vcl::Font aFont(GetControlFont()); + sal_Int32 nHeight = o3tl::toInt32(rValue) / 1000; + aFont.SetFontHeight(nHeight); + SetControlFont(aFont); + } + else + { + SAL_INFO("vcl.layout", "unhandled font attribute: " << rKey); + return false; + } + return true; +} + +bool Window::set_property(const OString &rKey, const OUString &rValue) +{ + if ((rKey == "label") || (rKey == "title") || (rKey == "text") ) + { + SetText(BuilderUtils::convertMnemonicMarkup(rValue)); + } + else if (rKey == "visible") + Show(toBool(rValue)); + else if (rKey == "sensitive") + Enable(toBool(rValue)); + else if (rKey == "resizable") + { + WinBits nBits = GetStyle(); + nBits &= ~WB_SIZEABLE; + if (toBool(rValue)) + nBits |= WB_SIZEABLE; + SetStyle(nBits); + } + else if (rKey == "xalign") + { + WinBits nBits = GetStyle(); + nBits &= ~(WB_LEFT | WB_CENTER | WB_RIGHT); + + float f = rValue.toFloat(); + assert(f == 0.0 || f == 1.0 || f == 0.5); + if (f == 0.0) + nBits |= WB_LEFT; + else if (f == 1.0) + nBits |= WB_RIGHT; + else if (f == 0.5) + nBits |= WB_CENTER; + + SetStyle(nBits); + } + else if (rKey == "justification") + { + WinBits nBits = GetStyle(); + nBits &= ~(WB_LEFT | WB_CENTER | WB_RIGHT); + + if (rValue == "left") + nBits |= WB_LEFT; + else if (rValue == "right") + nBits |= WB_RIGHT; + else if (rValue == "center") + nBits |= WB_CENTER; + + SetStyle(nBits); + } + else if (rKey == "yalign") + { + WinBits nBits = GetStyle(); + nBits &= ~(WB_TOP | WB_VCENTER | WB_BOTTOM); + + float f = rValue.toFloat(); + assert(f == 0.0 || f == 1.0 || f == 0.5); + if (f == 0.0) + nBits |= WB_TOP; + else if (f == 1.0) + nBits |= WB_BOTTOM; + else if (f == 0.5) + nBits |= WB_VCENTER; + + SetStyle(nBits); + } + else if (rKey == "wrap") + { + WinBits nBits = GetStyle(); + nBits &= ~WB_WORDBREAK; + if (toBool(rValue)) + nBits |= WB_WORDBREAK; + SetStyle(nBits); + } + else if (rKey == "height-request") + set_height_request(rValue.toInt32()); + else if (rKey == "width-request") + set_width_request(rValue.toInt32()); + else if (rKey == "hexpand") + set_hexpand(toBool(rValue)); + else if (rKey == "vexpand") + set_vexpand(toBool(rValue)); + else if (rKey == "halign") + set_halign(toAlign(rValue)); + else if (rKey == "valign") + set_valign(toAlign(rValue)); + else if (rKey == "tooltip-markup") + SetQuickHelpText(rValue); + else if (rKey == "tooltip-text") + SetQuickHelpText(rValue); + else if (rKey == "border-width") + set_border_width(rValue.toInt32()); + else if (rKey == "margin-start" || rKey == "margin-left") + { + assert(rKey == "margin-start" && "margin-left deprecated in favor of margin-start"); + set_margin_start(rValue.toInt32()); + } + else if (rKey == "margin-end" || rKey == "margin-right") + { + assert(rKey == "margin-end" && "margin-right deprecated in favor of margin-end"); + set_margin_end(rValue.toInt32()); + } + else if (rKey == "margin-top") + set_margin_top(rValue.toInt32()); + else if (rKey == "margin-bottom") + set_margin_bottom(rValue.toInt32()); + else if (rKey == "hscrollbar-policy") + { + WinBits nBits = GetStyle(); + nBits &= ~(WB_AUTOHSCROLL|WB_HSCROLL); + if (rValue == "always") + nBits |= WB_HSCROLL; + else if (rValue == "automatic") + nBits |= WB_AUTOHSCROLL; + SetStyle(nBits); + } + else if (rKey == "vscrollbar-policy") + { + WinBits nBits = GetStyle(); + nBits &= ~(WB_AUTOVSCROLL|WB_VSCROLL); + if (rValue == "always") + nBits |= WB_VSCROLL; + else if (rValue == "automatic") + nBits |= WB_AUTOVSCROLL; + SetStyle(nBits); + } + else if (rKey == "accessible-name") + { + SetAccessibleName(rValue); + } + else if (rKey == "accessible-description") + { + SetAccessibleDescription(rValue); + } + else if (rKey == "accessible-role") + { + sal_Int16 role = BuilderUtils::getRoleFromName(rValue.toUtf8()); + if (role != com::sun::star::accessibility::AccessibleRole::UNKNOWN) + SetAccessibleRole(role); + } + else if (rKey == "use-markup") + { + //https://live.gnome.org/GnomeGoals/RemoveMarkupInMessages + SAL_WARN_IF(toBool(rValue), "vcl.layout", "Use pango attributes instead of mark-up"); + } + else if (rKey == "has-focus") + { + if (toBool(rValue)) + GrabFocus(); + } + else if (rKey == "can-focus") + { + WinBits nBits = GetStyle(); + nBits &= ~(WB_TABSTOP|WB_NOTABSTOP); + if (toBool(rValue)) + nBits |= WB_TABSTOP; + else + nBits |= WB_NOTABSTOP; + SetStyle(nBits); + } + else + { + SAL_INFO("vcl.layout", "unhandled property: " << rKey); + return false; + } + return true; +} + +void Window::set_height_request(sal_Int32 nHeightRequest) +{ + if (!mpWindowImpl) + return; + + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + + if ( pWindowImpl->mnHeightRequest != nHeightRequest ) + { + pWindowImpl->mnHeightRequest = nHeightRequest; + queue_resize(); + } +} + +void Window::set_width_request(sal_Int32 nWidthRequest) +{ + if (!mpWindowImpl) + return; + + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + + if ( pWindowImpl->mnWidthRequest != nWidthRequest ) + { + pWindowImpl->mnWidthRequest = nWidthRequest; + queue_resize(); + } +} + +Size Window::get_ungrouped_preferred_size() const +{ + Size aRet(get_width_request(), get_height_request()); + if (aRet.Width() == -1 || aRet.Height() == -1) + { + //cache gets blown away by queue_resize + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + if (pWindowImpl->mnOptimalWidthCache == -1 || pWindowImpl->mnOptimalHeightCache == -1) + { + Size aOptimal(GetOptimalSize()); + pWindowImpl->mnOptimalWidthCache = aOptimal.Width(); + pWindowImpl->mnOptimalHeightCache = aOptimal.Height(); + } + + if (aRet.Width() == -1) + aRet.setWidth( pWindowImpl->mnOptimalWidthCache ); + if (aRet.Height() == -1) + aRet.setHeight( pWindowImpl->mnOptimalHeightCache ); + } + return aRet; +} + +Size Window::get_preferred_size() const +{ + Size aRet(get_ungrouped_preferred_size()); + + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + if (pWindowImpl->m_xSizeGroup) + { + const VclSizeGroupMode eMode = pWindowImpl->m_xSizeGroup->get_mode(); + if (eMode != VclSizeGroupMode::NONE) + { + const bool bIgnoreInHidden = pWindowImpl->m_xSizeGroup->get_ignore_hidden(); + const std::set<VclPtr<vcl::Window> > &rWindows = pWindowImpl->m_xSizeGroup->get_widgets(); + for (auto const& window : rWindows) + { + const vcl::Window *pOther = window; + if (pOther == this) + continue; + if (bIgnoreInHidden && !pOther->IsVisible()) + continue; + Size aOtherSize = pOther->get_ungrouped_preferred_size(); + if (eMode == VclSizeGroupMode::Both || eMode == VclSizeGroupMode::Horizontal) + aRet.setWidth( std::max(aRet.Width(), aOtherSize.Width()) ); + if (eMode == VclSizeGroupMode::Both || eMode == VclSizeGroupMode::Vertical) + aRet.setHeight( std::max(aRet.Height(), aOtherSize.Height()) ); + } + } + } + + return aRet; +} + +VclAlign Window::get_halign() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->meHalign; +} + +void Window::set_halign(VclAlign eAlign) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->meHalign = eAlign; +} + +VclAlign Window::get_valign() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->meValign; +} + +void Window::set_valign(VclAlign eAlign) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->meValign = eAlign; +} + +bool Window::get_hexpand() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mbHexpand; +} + +void Window::set_hexpand(bool bExpand) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->mbHexpand = bExpand; +} + +bool Window::get_vexpand() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mbVexpand; +} + +void Window::set_vexpand(bool bExpand) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->mbVexpand = bExpand; +} + +bool Window::get_expand() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mbExpand; +} + +void Window::set_expand(bool bExpand) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->mbExpand = bExpand; +} + +VclPackType Window::get_pack_type() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mePackType; +} + +void Window::set_pack_type(VclPackType ePackType) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->mePackType = ePackType; +} + +sal_Int32 Window::get_padding() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mnPadding; +} + +void Window::set_padding(sal_Int32 nPadding) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->mnPadding = nPadding; +} + +bool Window::get_fill() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mbFill; +} + +void Window::set_fill(bool bFill) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->mbFill = bFill; +} + +sal_Int32 Window::get_grid_width() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mnGridWidth; +} + +void Window::set_grid_width(sal_Int32 nCols) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->mnGridWidth = nCols; +} + +sal_Int32 Window::get_grid_left_attach() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mnGridLeftAttach; +} + +void Window::set_grid_left_attach(sal_Int32 nAttach) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->mnGridLeftAttach = nAttach; +} + +sal_Int32 Window::get_grid_height() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mnGridHeight; +} + +void Window::set_grid_height(sal_Int32 nRows) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->mnGridHeight = nRows; +} + +sal_Int32 Window::get_grid_top_attach() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mnGridTopAttach; +} + +void Window::set_grid_top_attach(sal_Int32 nAttach) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->mnGridTopAttach = nAttach; +} + +void Window::set_border_width(sal_Int32 nBorderWidth) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->mnBorderWidth = nBorderWidth; +} + +sal_Int32 Window::get_border_width() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mnBorderWidth; +} + +void Window::set_margin_start(sal_Int32 nWidth) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + if (pWindowImpl->mnMarginLeft != nWidth) + { + pWindowImpl->mnMarginLeft = nWidth; + queue_resize(); + } +} + +sal_Int32 Window::get_margin_start() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mnMarginLeft; +} + +void Window::set_margin_end(sal_Int32 nWidth) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + if (pWindowImpl->mnMarginRight != nWidth) + { + pWindowImpl->mnMarginRight = nWidth; + queue_resize(); + } +} + +sal_Int32 Window::get_margin_end() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mnMarginRight; +} + +void Window::set_margin_top(sal_Int32 nWidth) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + if (pWindowImpl->mnMarginTop != nWidth) + { + pWindowImpl->mnMarginTop = nWidth; + queue_resize(); + } +} + +sal_Int32 Window::get_margin_top() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mnMarginTop; +} + +void Window::set_margin_bottom(sal_Int32 nWidth) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + if (pWindowImpl->mnMarginBottom != nWidth) + { + pWindowImpl->mnMarginBottom = nWidth; + queue_resize(); + } +} + +sal_Int32 Window::get_margin_bottom() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mnMarginBottom; +} + +sal_Int32 Window::get_height_request() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mnHeightRequest; +} + +sal_Int32 Window::get_width_request() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mnWidthRequest; +} + +bool Window::get_secondary() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mbSecondary; +} + +void Window::set_secondary(bool bSecondary) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->mbSecondary = bSecondary; +} + +bool Window::get_non_homogeneous() const +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + return pWindowImpl->mbNonHomogeneous; +} + +void Window::set_non_homogeneous(bool bNonHomogeneous) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + pWindowImpl->mbNonHomogeneous = bNonHomogeneous; +} + +void Window::add_to_size_group(const std::shared_ptr<VclSizeGroup>& xGroup) +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + //To-Do, multiple groups + pWindowImpl->m_xSizeGroup = xGroup; + pWindowImpl->m_xSizeGroup->insert(this); + if (VclSizeGroupMode::NONE != pWindowImpl->m_xSizeGroup->get_mode()) + queue_resize(); +} + +void Window::remove_from_all_size_groups() +{ + WindowImpl *pWindowImpl = mpWindowImpl->mpBorderWindow ? mpWindowImpl->mpBorderWindow->mpWindowImpl.get() : mpWindowImpl.get(); + //To-Do, multiple groups + if (pWindowImpl->m_xSizeGroup) + { + if (VclSizeGroupMode::NONE != pWindowImpl->m_xSizeGroup->get_mode()) + queue_resize(); + pWindowImpl->m_xSizeGroup->erase(this); + pWindowImpl->m_xSizeGroup.reset(); + } +} + +void Window::add_mnemonic_label(FixedText *pLabel) +{ + std::vector<VclPtr<FixedText> >& v = mpWindowImpl->m_aMnemonicLabels; + if (std::find(v.begin(), v.end(), VclPtr<FixedText>(pLabel)) != v.end()) + return; + v.emplace_back(pLabel); + pLabel->set_mnemonic_widget(this); +} + +void Window::remove_mnemonic_label(FixedText *pLabel) +{ + std::vector<VclPtr<FixedText> >& v = mpWindowImpl->m_aMnemonicLabels; + auto aFind = std::find(v.begin(), v.end(), VclPtr<FixedText>(pLabel)); + if (aFind == v.end()) + return; + v.erase(aFind); + pLabel->set_mnemonic_widget(nullptr); +} + +const std::vector<VclPtr<FixedText> >& Window::list_mnemonic_labels() const +{ + return mpWindowImpl->m_aMnemonicLabels; +} + +} /* namespace vcl */ + +void InvertFocusRect(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + const int nBorder = 1; + rRenderContext.Invert(tools::Rectangle(Point(rRect.Left(), rRect.Top()), Size(rRect.GetWidth(), nBorder)), InvertFlags::N50); + rRenderContext.Invert(tools::Rectangle(Point(rRect.Left(), rRect.Bottom()-nBorder+1), Size(rRect.GetWidth(), nBorder)), InvertFlags::N50); + rRenderContext.Invert(tools::Rectangle(Point(rRect.Left(), rRect.Top()+nBorder), Size(nBorder, rRect.GetHeight()-(nBorder*2))), InvertFlags::N50); + rRenderContext.Invert(tools::Rectangle(Point(rRect.Right()-nBorder+1, rRect.Top()+nBorder), Size(nBorder, rRect.GetHeight()-(nBorder*2))), InvertFlags::N50); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/window3.cxx b/vcl/source/window/window3.cxx new file mode 100644 index 000000000..5b8dd5cff --- /dev/null +++ b/vcl/source/window/window3.cxx @@ -0,0 +1,220 @@ +/* -*- 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/window.hxx> +#include <window.h> +#include <vcl/cursor.hxx> + +namespace vcl +{ +Size Window::GetOptimalSize() const { return Size(); } + +void Window::ImplAdjustNWFSizes() +{ + for (Window* pWin = GetWindow(GetWindowType::FirstChild); pWin; + pWin = pWin->GetWindow(GetWindowType::Next)) + pWin->ImplAdjustNWFSizes(); +} + +void WindowOutputDevice::ImplClearFontData(bool bNewFontLists) +{ + OutputDevice::ImplClearFontData(bNewFontLists); + for (Window* pChild = mxOwnerWindow->mpWindowImpl->mpFirstChild; pChild; + pChild = pChild->mpWindowImpl->mpNext) + pChild->GetOutDev()->ImplClearFontData(bNewFontLists); +} + +void WindowOutputDevice::ImplRefreshFontData(bool bNewFontLists) +{ + OutputDevice::ImplRefreshFontData(bNewFontLists); + for (Window* pChild = mxOwnerWindow->mpWindowImpl->mpFirstChild; pChild; + pChild = pChild->mpWindowImpl->mpNext) + pChild->GetOutDev()->ImplRefreshFontData(bNewFontLists); +} + +void WindowOutputDevice::ImplInitMapModeObjects() +{ + OutputDevice::ImplInitMapModeObjects(); + if (mxOwnerWindow->mpWindowImpl->mpCursor) + mxOwnerWindow->mpWindowImpl->mpCursor->ImplNew(); +} + +const Font& Window::GetFont() const { return GetOutDev()->GetFont(); } +void Window::SetFont(Font const& font) { return GetOutDev()->SetFont(font); } + +float Window::approximate_char_width() const { return GetOutDev()->approximate_char_width(); } + +const Wallpaper& Window::GetBackground() const { return GetOutDev()->GetBackground(); } +bool Window::IsBackground() const { return GetOutDev()->IsBackground(); } +tools::Long Window::GetTextHeight() const { return GetOutDev()->GetTextHeight(); } +tools::Long Window::GetTextWidth(const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen, + vcl::text::TextLayoutCache const* pCache, + SalLayoutGlyphs const* const pLayoutCache) const +{ + return GetOutDev()->GetTextWidth(rStr, nIndex, nLen, pCache, pLayoutCache); +} +float Window::approximate_digit_width() const { return GetOutDev()->approximate_digit_width(); } + +bool Window::IsNativeControlSupported(ControlType nType, ControlPart nPart) const +{ + return GetOutDev()->IsNativeControlSupported(nType, nPart); +} + +bool Window::GetNativeControlRegion(ControlType nType, ControlPart nPart, + const tools::Rectangle& rControlRegion, ControlState nState, + const ImplControlValue& aValue, + tools::Rectangle& rNativeBoundingRegion, + tools::Rectangle& rNativeContentRegion) const +{ + return GetOutDev()->GetNativeControlRegion(nType, nPart, rControlRegion, nState, aValue, + rNativeBoundingRegion, rNativeContentRegion); +} + +Size Window::GetOutputSizePixel() const { return GetOutDev()->GetOutputSizePixel(); } + +tools::Rectangle Window::GetOutputRectPixel() const { return GetOutDev()->GetOutputRectPixel(); } + +void Window::SetTextLineColor() { GetOutDev()->SetTextLineColor(); } +void Window::SetTextLineColor(const Color& rColor) { GetOutDev()->SetTextLineColor(rColor); } +void Window::SetOverlineColor() { GetOutDev()->SetOverlineColor(); } +void Window::SetOverlineColor(const Color& rColor) { GetOutDev()->SetOverlineColor(rColor); } +void Window::SetTextFillColor() { GetOutDev()->SetTextFillColor(); } +void Window::SetTextFillColor(const Color& rColor) { GetOutDev()->SetTextFillColor(rColor); } +const MapMode& Window::GetMapMode() const { return GetOutDev()->GetMapMode(); } +void Window::SetBackground() { GetOutDev()->SetBackground(); } +void Window::SetBackground(const Wallpaper& rBackground) +{ + GetOutDev()->SetBackground(rBackground); +} +void Window::EnableMapMode(bool bEnable) { GetOutDev()->EnableMapMode(bEnable); } +bool Window::IsMapModeEnabled() const { return GetOutDev()->IsMapModeEnabled(); } + +void Window::SetTextColor(const Color& rColor) { GetOutDev()->SetTextColor(rColor); } +const Color& Window::GetTextColor() const { return GetOutDev()->GetTextColor(); } +const Color& Window::GetTextLineColor() const { return GetOutDev()->GetTextLineColor(); } + +bool Window::IsTextLineColor() const { return GetOutDev()->IsTextLineColor(); } + +Color Window::GetTextFillColor() const { return GetOutDev()->GetTextFillColor(); } + +bool Window::IsTextFillColor() const { return GetOutDev()->IsTextFillColor(); } + +const Color& Window::GetOverlineColor() const { return GetOutDev()->GetOverlineColor(); } +bool Window::IsOverlineColor() const { return GetOutDev()->IsOverlineColor(); } +void Window::SetTextAlign(TextAlign eAlign) { GetOutDev()->SetTextAlign(eAlign); } + +float Window::GetDPIScaleFactor() const { return GetOutDev()->GetDPIScaleFactor(); } +tools::Long Window::GetOutOffXPixel() const { return GetOutDev()->GetOutOffXPixel(); } +tools::Long Window::GetOutOffYPixel() const { return GetOutDev()->GetOutOffYPixel(); } +void Window::SetMapMode() { GetOutDev()->SetMapMode(); } +void Window::SetMapMode(const MapMode& rNewMapMode) { GetOutDev()->SetMapMode(rNewMapMode); } +bool Window::IsRTLEnabled() const { return GetOutDev()->IsRTLEnabled(); } +TextAlign Window::GetTextAlign() const { return GetOutDev()->GetTextAlign(); } +const AllSettings& Window::GetSettings() const { return GetOutDev()->GetSettings(); } + +Point Window::LogicToPixel(const Point& rLogicPt) const +{ + return GetOutDev()->LogicToPixel(rLogicPt); +} +Size Window::LogicToPixel(const Size& rLogicSize) const +{ + return GetOutDev()->LogicToPixel(rLogicSize); +} +tools::Rectangle Window::LogicToPixel(const tools::Rectangle& rLogicRect) const +{ + return GetOutDev()->LogicToPixel(rLogicRect); +} +vcl::Region Window::LogicToPixel(const vcl::Region& rLogicRegion) const +{ + return GetOutDev()->LogicToPixel(rLogicRegion); +} +Point Window::LogicToPixel(const Point& rLogicPt, const MapMode& rMapMode) const +{ + return GetOutDev()->LogicToPixel(rLogicPt, rMapMode); +} +Size Window::LogicToPixel(const Size& rLogicSize, const MapMode& rMapMode) const +{ + return GetOutDev()->LogicToPixel(rLogicSize, rMapMode); +} +tools::Rectangle Window::LogicToPixel(const tools::Rectangle& rLogicRect, + const MapMode& rMapMode) const +{ + return GetOutDev()->LogicToPixel(rLogicRect, rMapMode); +} + +Point Window::PixelToLogic(const Point& rDevicePt) const +{ + return GetOutDev()->PixelToLogic(rDevicePt); +} +Size Window::PixelToLogic(const Size& rDeviceSize) const +{ + return GetOutDev()->PixelToLogic(rDeviceSize); +} +tools::Rectangle Window::PixelToLogic(const tools::Rectangle& rDeviceRect) const +{ + return GetOutDev()->PixelToLogic(rDeviceRect); +} +tools::PolyPolygon Window::PixelToLogic(const tools::PolyPolygon& rDevicePolyPoly) const +{ + return GetOutDev()->PixelToLogic(rDevicePolyPoly); +} +vcl::Region Window::PixelToLogic(const vcl::Region& rDeviceRegion) const +{ + return GetOutDev()->PixelToLogic(rDeviceRegion); +} +Point Window::PixelToLogic(const Point& rDevicePt, const MapMode& rMapMode) const +{ + return GetOutDev()->PixelToLogic(rDevicePt, rMapMode); +} +Size Window::PixelToLogic(const Size& rDeviceSize, const MapMode& rMapMode) const +{ + return GetOutDev()->PixelToLogic(rDeviceSize, rMapMode); +} +tools::Rectangle Window::PixelToLogic(const tools::Rectangle& rDeviceRect, + const MapMode& rMapMode) const +{ + return GetOutDev()->PixelToLogic(rDeviceRect, rMapMode); +} + +Size Window::LogicToLogic(const Size& rSzSource, const MapMode* pMapModeSource, + const MapMode* pMapModeDest) const +{ + return GetOutDev()->LogicToLogic(rSzSource, pMapModeSource, pMapModeDest); +} + +tools::Rectangle Window::GetTextRect(const tools::Rectangle& rRect, const OUString& rStr, + DrawTextFlags nStyle, TextRectInfo* pInfo, + const vcl::ITextLayout* _pTextLayout) const +{ + return GetOutDev()->GetTextRect(rRect, rStr, nStyle, pInfo, _pTextLayout); +} + +void Window::SetSettings(const AllSettings& rSettings) { GetOutDev()->SetSettings(rSettings); } +void Window::SetSettings(const AllSettings& rSettings, bool bChild) +{ + static_cast<vcl::WindowOutputDevice*>(GetOutDev())->SetSettings(rSettings, bChild); +} + +Color Window::GetBackgroundColor() const { return GetOutDev()->GetBackgroundColor(); } + +void Window::EnableRTL(bool bEnable) { GetOutDev()->EnableRTL(bEnable); } + +} /* namespace vcl */ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/winproc.cxx b/vcl/source/window/winproc.cxx new file mode 100644 index 000000000..5a1b36ea8 --- /dev/null +++ b/vcl/source/window/winproc.cxx @@ -0,0 +1,2884 @@ +/* -*- 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 <tools/debug.hxx> +#include <tools/time.hxx> +#include <sal/log.hxx> + +#include <unotools/localedatawrapper.hxx> + +#include <dndeventdispatcher.hxx> +#include <comphelper/lok.hxx> +#include <vcl/QueueInfo.hxx> +#include <vcl/timer.hxx> +#include <vcl/event.hxx> +#include <vcl/GestureEvent.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/cursor.hxx> +#include <vcl/wrkwin.hxx> +#include <vcl/toolkit/floatwin.hxx> +#include <vcl/toolkit/dialog.hxx> +#include <vcl/toolkit/edit.hxx> +#include <vcl/help.hxx> +#include <vcl/dockwin.hxx> +#include <vcl/menu.hxx> +#include <vcl/virdev.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/ptrstyle.hxx> + +#include <svdata.hxx> +#include <salwtype.hxx> +#include <salframe.hxx> +#include <accmgr.hxx> +#include <print.h> +#include <window.h> +#include <helpwin.hxx> +#include <brdwin.hxx> +#include <dndlistenercontainer.hxx> + +#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp> +#include <com/sun/star/datatransfer/dnd/XDragSource.hpp> +#include <com/sun/star/awt/MouseEvent.hpp> + +#define IMPL_MIN_NEEDSYSWIN 49 + +bool ImplCallPreNotify( NotifyEvent& rEvt ) +{ + return rEvt.GetWindow()->CompatPreNotify( rEvt ); +} + +static bool ImplHandleMouseFloatMode( vcl::Window* pChild, const Point& rMousePos, + sal_uInt16 nCode, MouseNotifyEvent nSVEvent, + bool bMouseLeave ) +{ + ImplSVData* pSVData = ImplGetSVData(); + + if (pSVData->mpWinData->mpFirstFloat && !pSVData->mpWinData->mpCaptureWin + && !pSVData->mpWinData->mpFirstFloat->ImplIsFloatPopupModeWindow(pChild)) + { + /* + * #93895# since floats are system windows, coordinates have + * to be converted to float relative for the hittest + */ + bool bHitTestInsideRect = false; + FloatingWindow* pFloat = pSVData->mpWinData->mpFirstFloat->ImplFloatHitTest( pChild, rMousePos, bHitTestInsideRect ); + if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE ) + { + if ( bMouseLeave ) + return true; + + if ( !pFloat || bHitTestInsideRect ) + { + if ( ImplGetSVHelpData().mpHelpWin && !ImplGetSVHelpData().mbKeyboardHelp ) + ImplDestroyHelpWindow( true ); + pChild->ImplGetFrame()->SetPointer( PointerStyle::Arrow ); + return true; + } + } + else + { + if ( nCode & MOUSE_LEFT ) + { + if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN ) + { + if ( !pFloat ) + { + FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat(); + pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll ); + return true; + } + else if ( bHitTestInsideRect ) + { + pFloat->ImplSetMouseDown(); + return true; + } + } + else + { + if ( pFloat ) + { + if ( bHitTestInsideRect ) + { + if ( pFloat->ImplIsMouseDown() ) + pFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel ); + return true; + } + } + else + { + FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat(); + FloatWinPopupFlags nPopupFlags = pLastLevelFloat->GetPopupModeFlags(); + if ( !(nPopupFlags & FloatWinPopupFlags::NoMouseUpClose) ) + { + pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll ); + return true; + } + } + } + } + else + { + if ( !pFloat ) + { + FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat(); + FloatWinPopupFlags nPopupFlags = pLastLevelFloat->GetPopupModeFlags(); + if ( nPopupFlags & FloatWinPopupFlags::AllMouseButtonClose ) + { + if ( (nPopupFlags & FloatWinPopupFlags::NoMouseUpClose) && + (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP) ) + return true; + pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll ); + return true; + } + else + return true; + } + } + } + } + + return false; +} + +static void ImplHandleMouseHelpRequest( vcl::Window* pChild, const Point& rMousePos ) +{ + ImplSVHelpData& aHelpData = ImplGetSVHelpData(); + if ( aHelpData.mpHelpWin && + ( aHelpData.mpHelpWin->IsWindowOrChild( pChild ) || + pChild->IsWindowOrChild( aHelpData.mpHelpWin ) )) + return; + + HelpEventMode nHelpMode = HelpEventMode::NONE; + if ( aHelpData.mbQuickHelp ) + nHelpMode = HelpEventMode::QUICK; + if ( aHelpData.mbBalloonHelp ) + nHelpMode |= HelpEventMode::BALLOON; + if ( !(bool(nHelpMode)) ) + return; + + if ( pChild->IsInputEnabled() && !pChild->IsInModalMode() ) + { + HelpEvent aHelpEvent( rMousePos, nHelpMode ); + aHelpData.mbRequestingHelp = true; + pChild->RequestHelp( aHelpEvent ); + aHelpData.mbRequestingHelp = false; + } + // #104172# do not kill keyboard activated tooltips + else if ( aHelpData.mpHelpWin && !aHelpData.mbKeyboardHelp) + { + ImplDestroyHelpWindow( true ); + } +} + +static void ImplSetMousePointer( vcl::Window const * pChild ) +{ + if ( ImplGetSVHelpData().mbExtHelpMode ) + pChild->ImplGetFrame()->SetPointer( PointerStyle::Help ); + else + pChild->ImplGetFrame()->SetPointer( pChild->ImplGetMousePointer() ); +} + +static bool ImplCallCommand( const VclPtr<vcl::Window>& pChild, CommandEventId nEvt, void const * pData = nullptr, + bool bMouse = false, Point const * pPos = nullptr ) +{ + Point aPos; + if ( pPos ) + aPos = *pPos; + else + { + if( bMouse ) + aPos = pChild->GetPointerPosPixel(); + else + { + // simulate mouseposition at center of window + Size aSize( pChild->GetOutputSizePixel() ); + aPos = Point( aSize.getWidth()/2, aSize.getHeight()/2 ); + } + } + + CommandEvent aCEvt( aPos, nEvt, bMouse, pData ); + NotifyEvent aNCmdEvt( MouseNotifyEvent::COMMAND, pChild, &aCEvt ); + bool bPreNotify = ImplCallPreNotify( aNCmdEvt ); + if ( pChild->isDisposed() ) + return false; + if ( !bPreNotify ) + { + pChild->ImplGetWindowImpl()->mbCommand = false; + pChild->Command( aCEvt ); + + if( pChild->isDisposed() ) + return false; + pChild->ImplNotifyKeyMouseCommandEventListeners( aNCmdEvt ); + if ( pChild->isDisposed() ) + return false; + if ( pChild->ImplGetWindowImpl()->mbCommand ) + return true; + } + + return false; +} + +/* #i34277# delayed context menu activation; +* necessary if there already was a popup menu running. +*/ + +namespace { + +struct ContextMenuEvent +{ + VclPtr<vcl::Window> pWindow; + Point aChildPos; +}; + +} + +static void ContextMenuEventLink( void* pCEvent, void* ) +{ + ContextMenuEvent* pEv = static_cast<ContextMenuEvent*>(pCEvent); + + if( ! pEv->pWindow->isDisposed() ) + { + ImplCallCommand( pEv->pWindow, CommandEventId::ContextMenu, nullptr, true, &pEv->aChildPos ); + } + delete pEv; +} + +bool ImplHandleMouseEvent( const VclPtr<vcl::Window>& xWindow, MouseNotifyEvent nSVEvent, bool bMouseLeave, + tools::Long nX, tools::Long nY, sal_uInt64 nMsgTime, + sal_uInt16 nCode, MouseEventModifiers nMode ) +{ + SAL_INFO( "vcl.debugevent", + "mouse event " + "(MouseNotifyEvent " << static_cast<sal_uInt16>(nSVEvent) << ") " + "(MouseLeave " << bMouseLeave << ") " + "(X, Y " << nX << ", " << nY << ") " + "(Code " << nCode << ") " + "(Modifiers " << static_cast<sal_uInt16>(nMode) << ")"); + ImplSVHelpData& aHelpData = ImplGetSVHelpData(); + ImplSVData* pSVData = ImplGetSVData(); + Point aMousePos( nX, nY ); + VclPtr<vcl::Window> pChild; + bool bRet(false); + sal_uInt16 nClicks(0); + ImplFrameData* pWinFrameData = xWindow->ImplGetFrameData(); + sal_uInt16 nOldCode = pWinFrameData->mnMouseCode; + + if (comphelper::LibreOfficeKit::isActive() && AllSettings::GetLayoutRTL() + && xWindow->GetOutDev() && !xWindow->GetOutDev()->ImplIsAntiparallel()) + { + xWindow->GetOutDev()->ReMirror(aMousePos); + nX = aMousePos.X(); + nY = aMousePos.Y(); + } + + // we need a mousemove event, before we get a mousebuttondown or a + // mousebuttonup event + if ( (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN) || (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP) ) + { + if ( (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP) && aHelpData.mbExtHelpMode ) + Help::EndExtHelp(); + if ( aHelpData.mpHelpWin ) + { + if( xWindow->ImplGetWindow() == aHelpData.mpHelpWin ) + { + ImplDestroyHelpWindow( false ); + return true; // xWindow is dead now - avoid crash! + } + else + ImplDestroyHelpWindow( true ); + } + + if ( (pWinFrameData->mnLastMouseX != nX) || + (pWinFrameData->mnLastMouseY != nY) ) + { + sal_uInt16 nMoveCode = nCode & ~(MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE); + ImplHandleMouseEvent(xWindow, MouseNotifyEvent::MOUSEMOVE, false, nX, nY, nMsgTime, nMoveCode, nMode); + } + } + + // update frame data + pWinFrameData->mnBeforeLastMouseX = pWinFrameData->mnLastMouseX; + pWinFrameData->mnBeforeLastMouseY = pWinFrameData->mnLastMouseY; + pWinFrameData->mnLastMouseX = nX; + pWinFrameData->mnLastMouseY = nY; + pWinFrameData->mnMouseCode = nCode; + MouseEventModifiers const nTmpMask = MouseEventModifiers::SYNTHETIC | MouseEventModifiers::MODIFIERCHANGED; + pWinFrameData->mnMouseMode = nMode & ~nTmpMask; + if ( bMouseLeave ) + { + pWinFrameData->mbMouseIn = false; + if ( ImplGetSVHelpData().mpHelpWin && !ImplGetSVHelpData().mbKeyboardHelp ) + { + ImplDestroyHelpWindow( true ); + + if ( xWindow->isDisposed() ) + return true; // xWindow is dead now - avoid crash! (#122045#) + } + } + else + pWinFrameData->mbMouseIn = true; + + DBG_ASSERT(!pSVData->mpWinData->mpTrackWin + || (pSVData->mpWinData->mpTrackWin == pSVData->mpWinData->mpCaptureWin), + "ImplHandleMouseEvent: TrackWin != CaptureWin"); + + // AutoScrollMode + if (pSVData->mpWinData->mpAutoScrollWin && (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN)) + { + pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll(); + return true; + } + + // find mouse window + if (pSVData->mpWinData->mpCaptureWin) + { + pChild = pSVData->mpWinData->mpCaptureWin; + + SAL_WARN_IF( xWindow != pChild->ImplGetFrameWindow(), "vcl", + "ImplHandleMouseEvent: mouse event is not sent to capture window" ); + + // java client cannot capture mouse correctly + if ( xWindow != pChild->ImplGetFrameWindow() ) + return false; + + if ( bMouseLeave ) + return false; + } + else + { + if ( bMouseLeave ) + pChild = nullptr; + else + pChild = xWindow->ImplFindWindow( aMousePos ); + } + + // test this because mouse events are buffered in the remote version + // and size may not be in sync + if ( !pChild && !bMouseLeave ) + return false; + + // execute a few tests and catch the message or implement the status + if ( pChild ) + { + if( pChild->GetOutDev()->ImplIsAntiparallel() ) + { + // re-mirror frame pos at pChild + const OutputDevice *pChildWinOutDev = pChild->GetOutDev(); + pChildWinOutDev->ReMirror( aMousePos ); + } + + // no mouse messages to disabled windows + // #106845# if the window was disabled during capturing we have to pass the mouse events to release capturing + if (pSVData->mpWinData->mpCaptureWin.get() != pChild + && (!pChild->IsEnabled() || !pChild->IsInputEnabled() || pChild->IsInModalMode())) + { + ImplHandleMouseFloatMode( pChild, aMousePos, nCode, nSVEvent, bMouseLeave ); + if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE ) + { + ImplHandleMouseHelpRequest( pChild, aMousePos ); + if( pWinFrameData->mpMouseMoveWin.get() != pChild ) + nMode |= MouseEventModifiers::ENTERWINDOW; + } + + // Call the hook also, if Window is disabled + + if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN ) + return true; + else + { + // Set normal MousePointer for disabled windows + if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE ) + ImplSetMousePointer( pChild ); + + return false; + } + } + + // End ExtTextInput-Mode, if the user click in the same TopLevel Window + if (pSVData->mpWinData->mpExtTextInputWin + && ((nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN) + || (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP))) + pSVData->mpWinData->mpExtTextInputWin->EndExtTextInput(); + } + + // determine mouse event data + if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE ) + { + // check if MouseMove belongs to same window and if the + // status did not change + if ( pChild ) + { + Point aChildMousePos = pChild->ImplFrameToOutput( aMousePos ); + if ( !bMouseLeave && + (pChild == pWinFrameData->mpMouseMoveWin) && + (aChildMousePos.X() == pWinFrameData->mnLastMouseWinX) && + (aChildMousePos.Y() == pWinFrameData->mnLastMouseWinY) && + (nOldCode == pWinFrameData->mnMouseCode) ) + { + // set mouse pointer anew, as it could have changed + // due to the mode switch + ImplSetMousePointer( pChild ); + return false; + } + + pWinFrameData->mnLastMouseWinX = aChildMousePos.X(); + pWinFrameData->mnLastMouseWinY = aChildMousePos.Y(); + } + + // mouse click + nClicks = pWinFrameData->mnClickCount; + + // call Start-Drag handler if required + // Warning: should be called before Move, as otherwise during + // fast mouse movements the applications move to the selection state + vcl::Window* pMouseDownWin = pWinFrameData->mpMouseDownWin; + if ( pMouseDownWin ) + { + // check for matching StartDrag mode. We only compare + // the status of the mouse buttons, such that e. g. Mod1 can + // change immediately to the copy mode + const MouseSettings& rMSettings = pMouseDownWin->GetSettings().GetMouseSettings(); + if ( (nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) == + (MouseSettings::GetStartDragCode() & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) ) + { + if ( !pMouseDownWin->ImplGetFrameData()->mbStartDragCalled ) + { + tools::Long nDragW = rMSettings.GetStartDragWidth(); + tools::Long nDragH = rMSettings.GetStartDragHeight(); + //long nMouseX = nX; + //long nMouseY = nY; + tools::Long nMouseX = aMousePos.X(); // #106074# use the possibly re-mirrored coordinates (RTL) ! nX,nY are unmodified ! + tools::Long nMouseY = aMousePos.Y(); + if ( (((nMouseX-nDragW) > pMouseDownWin->ImplGetFrameData()->mnFirstMouseX) || + ((nMouseX+nDragW) < pMouseDownWin->ImplGetFrameData()->mnFirstMouseX)) || + (((nMouseY-nDragH) > pMouseDownWin->ImplGetFrameData()->mnFirstMouseY) || + ((nMouseY+nDragH) < pMouseDownWin->ImplGetFrameData()->mnFirstMouseY)) ) + { + pMouseDownWin->ImplGetFrameData()->mbStartDragCalled = true; + + // Check if drag source provides its own recognizer + if( pMouseDownWin->ImplGetFrameData()->mbInternalDragGestureRecognizer ) + { + // query DropTarget from child window + css::uno::Reference< css::datatransfer::dnd::XDragGestureRecognizer > xDragGestureRecognizer( + pMouseDownWin->ImplGetWindowImpl()->mxDNDListenerContainer, + css::uno::UNO_QUERY ); + + if( xDragGestureRecognizer.is() ) + { + // retrieve mouse position relative to mouse down window + Point relLoc = pMouseDownWin->ImplFrameToOutput( Point( + pMouseDownWin->ImplGetFrameData()->mnFirstMouseX, + pMouseDownWin->ImplGetFrameData()->mnFirstMouseY ) ); + + // create a UNO mouse event out of the available data + css::awt::MouseEvent aMouseEvent( static_cast < css::uno::XInterface * > ( nullptr ), +#ifdef MACOSX + nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3), +#else + nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2), +#endif + nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE), + nMouseX, + nMouseY, + nClicks, + false ); + + SolarMutexReleaser aReleaser; + + // FIXME: where do I get Action from ? + css::uno::Reference< css::datatransfer::dnd::XDragSource > xDragSource = pMouseDownWin->GetDragSource(); + + if( xDragSource.is() ) + { + static_cast < DNDListenerContainer * > ( xDragGestureRecognizer.get() )->fireDragGestureEvent( 0, + relLoc.X(), relLoc.Y(), xDragSource, css::uno::Any( aMouseEvent ) ); + } + } + } + } + } + } + else + pMouseDownWin->ImplGetFrameData()->mbStartDragCalled = true; + } + + if (xWindow->isDisposed()) + return true; + // test for mouseleave and mouseenter + VclPtr<vcl::Window> pMouseMoveWin = pWinFrameData->mpMouseMoveWin; + if ( pChild != pMouseMoveWin ) + { + if ( pMouseMoveWin ) + { + Point aLeaveMousePos = pMouseMoveWin->ImplFrameToOutput( aMousePos ); + MouseEvent aMLeaveEvt( aLeaveMousePos, nClicks, nMode | MouseEventModifiers::LEAVEWINDOW, nCode, nCode ); + NotifyEvent aNLeaveEvt( MouseNotifyEvent::MOUSEMOVE, pMouseMoveWin, &aMLeaveEvt ); + pWinFrameData->mbInMouseMove = true; + pMouseMoveWin->ImplGetWinData()->mbMouseOver = false; + + // A MouseLeave can destroy this window + if ( !ImplCallPreNotify( aNLeaveEvt ) ) + { + pMouseMoveWin->MouseMove( aMLeaveEvt ); + if( !pMouseMoveWin->isDisposed() ) + aNLeaveEvt.GetWindow()->ImplNotifyKeyMouseCommandEventListeners( aNLeaveEvt ); + } + + pWinFrameData->mpMouseMoveWin = nullptr; + pWinFrameData->mbInMouseMove = false; + + if ( pChild && pChild->isDisposed() ) + pChild = nullptr; + if ( pMouseMoveWin->isDisposed() ) + return true; + } + + nMode |= MouseEventModifiers::ENTERWINDOW; + } + pWinFrameData->mpMouseMoveWin = pChild; + if( pChild ) + pChild->ImplGetWinData()->mbMouseOver = true; + + // MouseLeave + if ( !pChild ) + return false; + } + else + { + if (pChild) + { + // mouse click + if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN ) + { + const MouseSettings& rMSettings = pChild->GetSettings().GetMouseSettings(); + sal_uInt64 nDblClkTime = rMSettings.GetDoubleClickTime(); + tools::Long nDblClkW = rMSettings.GetDoubleClickWidth(); + tools::Long nDblClkH = rMSettings.GetDoubleClickHeight(); + //long nMouseX = nX; + //long nMouseY = nY; + tools::Long nMouseX = aMousePos.X(); // #106074# use the possibly re-mirrored coordinates (RTL) ! nX,nY are unmodified ! + tools::Long nMouseY = aMousePos.Y(); + + if ( (pChild == pChild->ImplGetFrameData()->mpMouseDownWin) && + (nCode == pChild->ImplGetFrameData()->mnFirstMouseCode) && + ((nMsgTime-pChild->ImplGetFrameData()->mnMouseDownTime) < nDblClkTime) && + ((nMouseX-nDblClkW) <= pChild->ImplGetFrameData()->mnFirstMouseX) && + ((nMouseX+nDblClkW) >= pChild->ImplGetFrameData()->mnFirstMouseX) && + ((nMouseY-nDblClkH) <= pChild->ImplGetFrameData()->mnFirstMouseY) && + ((nMouseY+nDblClkH) >= pChild->ImplGetFrameData()->mnFirstMouseY) ) + { + pChild->ImplGetFrameData()->mnClickCount++; + pChild->ImplGetFrameData()->mbStartDragCalled = true; + } + else + { + pChild->ImplGetFrameData()->mpMouseDownWin = pChild; + pChild->ImplGetFrameData()->mnClickCount = 1; + pChild->ImplGetFrameData()->mnFirstMouseX = nMouseX; + pChild->ImplGetFrameData()->mnFirstMouseY = nMouseY; + pChild->ImplGetFrameData()->mnFirstMouseCode = nCode; + pChild->ImplGetFrameData()->mbStartDragCalled = (nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) != + (MouseSettings::GetStartDragCode() & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)); + } + pChild->ImplGetFrameData()->mnMouseDownTime = nMsgTime; + } + nClicks = pChild->ImplGetFrameData()->mnClickCount; + } + + pSVData->maAppData.mnLastInputTime = tools::Time::GetSystemTicks(); + } + + SAL_WARN_IF( !pChild, "vcl", "ImplHandleMouseEvent: pChild == NULL" ); + + if (!pChild) + return false; + + // create mouse event + Point aChildPos = pChild->ImplFrameToOutput( aMousePos ); + MouseEvent aMEvt( aChildPos, nClicks, nMode, nCode, nCode ); + + + // tracking window gets the mouse events + if (pSVData->mpWinData->mpTrackWin) + pChild = pSVData->mpWinData->mpTrackWin; + + // handle FloatingMode + if (!pSVData->mpWinData->mpTrackWin && pSVData->mpWinData->mpFirstFloat) + { + if ( ImplHandleMouseFloatMode( pChild, aMousePos, nCode, nSVEvent, bMouseLeave ) ) + { + if ( !pChild->isDisposed() ) + { + pChild->ImplGetFrameData()->mbStartDragCalled = true; + } + return true; + } + } + + // call handler + bool bCallHelpRequest = true; + SAL_WARN_IF( !pChild, "vcl", "ImplHandleMouseEvent: pChild is NULL" ); + + if (!pChild) + return false; + + NotifyEvent aNEvt( nSVEvent, pChild, &aMEvt ); + if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE ) + pChild->ImplGetFrameData()->mbInMouseMove = true; + + // bring window into foreground on mouseclick + if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN ) + { + if (!pSVData->mpWinData->mpFirstFloat + && // totop for floating windows in popup would change the focus and would close them immediately + !(pChild->ImplGetFrameWindow()->GetStyle() + & WB_OWNERDRAWDECORATION)) // ownerdrawdecorated windows must never grab focus + pChild->ToTop(); + if ( pChild->isDisposed() ) + return true; + } + + if ( ImplCallPreNotify( aNEvt ) || pChild->isDisposed() ) + bRet = true; + else + { + bRet = false; + if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE ) + { + if (pSVData->mpWinData->mpTrackWin) + { + TrackingEvent aTEvt( aMEvt ); + pChild->Tracking( aTEvt ); + if ( !pChild->isDisposed() ) + { + // When ScrollRepeat, we restart the timer + if (pSVData->mpWinData->mpTrackTimer + && (pSVData->mpWinData->mnTrackFlags & StartTrackingFlags::ScrollRepeat)) + pSVData->mpWinData->mpTrackTimer->Start(); + } + bCallHelpRequest = false; + bRet = true; + } + else + { + if( pChild->isDisposed() ) + bCallHelpRequest = false; + else + { + // if the MouseMove handler changes the help window's visibility + // the HelpRequest handler should not be called anymore + vcl::Window* pOldHelpTextWin = ImplGetSVHelpData().mpHelpWin; + pChild->MouseMove( aMEvt ); + if ( pOldHelpTextWin != ImplGetSVHelpData().mpHelpWin ) + bCallHelpRequest = false; + } + } + } + else if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN ) + { + if ( pSVData->mpWinData->mpTrackWin ) + bRet = true; + else + { + pChild->ImplGetWindowImpl()->mbMouseButtonDown = false; + pChild->MouseButtonDown( aMEvt ); + } + } + else + { + if (pSVData->mpWinData->mpTrackWin) + { + pChild->EndTracking(); + bRet = true; + } + else + { + pChild->ImplGetWindowImpl()->mbMouseButtonUp = false; + pChild->MouseButtonUp( aMEvt ); + } + } + + assert(aNEvt.GetWindow() == pChild); + + if (!pChild->isDisposed()) + pChild->ImplNotifyKeyMouseCommandEventListeners( aNEvt ); + } + + if (pChild->isDisposed()) + return true; + + if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE ) + pChild->ImplGetWindowImpl()->mpFrameData->mbInMouseMove = false; + + if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE ) + { + if ( bCallHelpRequest && !ImplGetSVHelpData().mbKeyboardHelp ) + ImplHandleMouseHelpRequest( pChild, pChild->OutputToScreenPixel( aMEvt.GetPosPixel() ) ); + bRet = true; + } + else if ( !bRet ) + { + if ( nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN ) + { + if ( !pChild->ImplGetWindowImpl()->mbMouseButtonDown ) + bRet = true; + } + else + { + if ( !pChild->ImplGetWindowImpl()->mbMouseButtonUp ) + bRet = true; + } + } + + if ( nSVEvent == MouseNotifyEvent::MOUSEMOVE ) + { + // set new mouse pointer + if ( !bMouseLeave ) + ImplSetMousePointer( pChild ); + } + else if ( (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN) || (nSVEvent == MouseNotifyEvent::MOUSEBUTTONUP) ) + { + // Command-Events + if ( /*!bRet &&*/ (nClicks == 1) && (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN) && + (nCode == MOUSE_MIDDLE) ) + { + MouseMiddleButtonAction nMiddleAction = pChild->GetSettings().GetMouseSettings().GetMiddleButtonAction(); + if ( nMiddleAction == MouseMiddleButtonAction::AutoScroll ) + bRet = !ImplCallCommand( pChild, CommandEventId::StartAutoScroll, nullptr, true, &aChildPos ); + else if ( nMiddleAction == MouseMiddleButtonAction::PasteSelection ) + bRet = !ImplCallCommand( pChild, CommandEventId::PasteSelection, nullptr, true, &aChildPos ); + } + else + { + // ContextMenu + if ( (nCode == MouseSettings::GetContextMenuCode()) && + (nClicks == MouseSettings::GetContextMenuClicks()) ) + { + bool bContextMenu = (nSVEvent == MouseNotifyEvent::MOUSEBUTTONDOWN); + if ( bContextMenu ) + { + if( pSVData->maAppData.mpActivePopupMenu ) + { + /* #i34277# there already is a context menu open + * that was probably just closed with EndPopupMode. + * We need to give the eventual corresponding + * PopupMenu::Execute a chance to end properly. + * Therefore delay context menu command and + * issue only after popping one frame of the + * Yield stack. + */ + ContextMenuEvent* pEv = new ContextMenuEvent; + pEv->pWindow = pChild; + pEv->aChildPos = aChildPos; + Application::PostUserEvent( Link<void*,void>( pEv, ContextMenuEventLink ) ); + } + else + bRet = ! ImplCallCommand( pChild, CommandEventId::ContextMenu, nullptr, true, &aChildPos ); + } + } + } + } + + return bRet; +} + +bool ImplLOKHandleMouseEvent(const VclPtr<vcl::Window>& xWindow, MouseNotifyEvent nEvent, bool /*bMouseLeave*/, + tools::Long nX, tools::Long nY, sal_uInt64 /*nMsgTime*/, + sal_uInt16 nCode, MouseEventModifiers nMode, sal_uInt16 nClicks) +{ + Point aMousePos(nX, nY); + + if (!xWindow) + return false; + + if (xWindow->isDisposed()) + return false; + + ImplFrameData* pFrameData = xWindow->ImplGetFrameData(); + if (!pFrameData) + return false; + + Point aWinPos = xWindow->ImplFrameToOutput(aMousePos); + + pFrameData->mnLastMouseX = nX; + pFrameData->mnLastMouseY = nY; + pFrameData->mnClickCount = nClicks; + pFrameData->mnMouseCode = nCode; + pFrameData->mbMouseIn = false; + + vcl::Window* pDragWin = pFrameData->mpMouseDownWin; + if (pDragWin && + nEvent == MouseNotifyEvent::MOUSEMOVE && + pFrameData->mbDragging) + { + css::uno::Reference<css::datatransfer::dnd::XDropTargetDragContext> xDropTargetDragContext = + new GenericDropTargetDragContext(); + css::uno::Reference<css::datatransfer::dnd::XDropTarget> xDropTarget( + pDragWin->ImplGetWindowImpl()->mxDNDListenerContainer, css::uno::UNO_QUERY); + + if (!xDropTarget.is() || + !xDropTargetDragContext.is() || + (nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) != + (MouseSettings::GetStartDragCode() & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE))) + { + pFrameData->mbStartDragCalled = pFrameData->mbDragging = false; + return false; + } + + static_cast<DNDListenerContainer *>(xDropTarget.get())->fireDragOverEvent( + xDropTargetDragContext, + css::datatransfer::dnd::DNDConstants::ACTION_MOVE, + aWinPos.X(), + aWinPos.Y(), + (css::datatransfer::dnd::DNDConstants::ACTION_COPY | + css::datatransfer::dnd::DNDConstants::ACTION_MOVE | + css::datatransfer::dnd::DNDConstants::ACTION_LINK)); + + return true; + } + + if (pDragWin && + nEvent == MouseNotifyEvent::MOUSEBUTTONUP && + pFrameData->mbDragging) + { + css::uno::Reference<css::datatransfer::XTransferable> xTransfer; + css::uno::Reference<css::datatransfer::dnd::XDropTargetDropContext> xDropTargetDropContext = + new GenericDropTargetDropContext(); + css::uno::Reference<css::datatransfer::dnd::XDropTarget> xDropTarget( + pDragWin->ImplGetWindowImpl()->mxDNDListenerContainer, css::uno::UNO_QUERY); + + if (!xDropTarget.is() || !xDropTargetDropContext.is()) + { + pFrameData->mbStartDragCalled = pFrameData->mbDragging = false; + return false; + } + + Point dragOverPos = pDragWin->ImplFrameToOutput(aMousePos); + static_cast<DNDListenerContainer *>(xDropTarget.get())->fireDropEvent( + xDropTargetDropContext, + css::datatransfer::dnd::DNDConstants::ACTION_MOVE, + dragOverPos.X(), + dragOverPos.Y(), + (css::datatransfer::dnd::DNDConstants::ACTION_COPY | + css::datatransfer::dnd::DNDConstants::ACTION_MOVE | + css::datatransfer::dnd::DNDConstants::ACTION_LINK), + xTransfer); + + pFrameData->mbStartDragCalled = pFrameData->mbDragging = false; + return true; + } + + if (pFrameData->mbDragging) + { + // wrong status, reset + pFrameData->mbStartDragCalled = pFrameData->mbDragging = false; + return false; + } + + vcl::Window* pDownWin = pFrameData->mpMouseDownWin; + if (pDownWin && nEvent == MouseNotifyEvent::MOUSEMOVE) + { + const MouseSettings& aSettings = pDownWin->GetSettings().GetMouseSettings(); + if ((nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) == + (MouseSettings::GetStartDragCode() & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)) ) + { + if (!pFrameData->mbStartDragCalled) + { + tools::Long nDragWidth = aSettings.GetStartDragWidth(); + tools::Long nDragHeight = aSettings.GetStartDragHeight(); + tools::Long nMouseX = aMousePos.X(); + tools::Long nMouseY = aMousePos.Y(); + + if ((((nMouseX - nDragWidth) > pFrameData->mnFirstMouseX) || + ((nMouseX + nDragWidth) < pFrameData->mnFirstMouseX)) || + (((nMouseY - nDragHeight) > pFrameData->mnFirstMouseY) || + ((nMouseY + nDragHeight) < pFrameData->mnFirstMouseY))) + { + pFrameData->mbStartDragCalled = true; + + if (pFrameData->mbInternalDragGestureRecognizer) + { + // query DropTarget from child window + css::uno::Reference< css::datatransfer::dnd::XDragGestureRecognizer > xDragGestureRecognizer( + pDownWin->ImplGetWindowImpl()->mxDNDListenerContainer, + css::uno::UNO_QUERY ); + + if (xDragGestureRecognizer.is()) + { + // create a UNO mouse event out of the available data + css::awt::MouseEvent aEvent( + static_cast < css::uno::XInterface * > ( nullptr ), + #ifdef MACOSX + nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3), + #else + nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2), + #endif + nCode & (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE), + nMouseX, + nMouseY, + nClicks, + false); + css::uno::Reference< css::datatransfer::dnd::XDragSource > xDragSource = + pDownWin->GetDragSource(); + + if (xDragSource.is()) + { + static_cast<DNDListenerContainer *>(xDragGestureRecognizer.get())-> + fireDragGestureEvent( + 0, + aWinPos.X(), + aWinPos.Y(), + xDragSource, + css::uno::Any(aEvent)); + } + } + } + } + } + } + } + + MouseEvent aMouseEvent(aWinPos, nClicks, nMode, nCode, nCode); + if (nEvent == MouseNotifyEvent::MOUSEMOVE) + { + if (pFrameData->mpTrackWin) + { + TrackingEvent aTrackingEvent(aMouseEvent); + pFrameData->mpTrackWin->Tracking(aTrackingEvent); + } + else + xWindow->MouseMove(aMouseEvent); + } + else if (nEvent == MouseNotifyEvent::MOUSEBUTTONDOWN && + !pFrameData->mpTrackWin) + { + pFrameData->mpMouseDownWin = xWindow; + pFrameData->mnFirstMouseX = aMousePos.X(); + pFrameData->mnFirstMouseY = aMousePos.Y(); + + xWindow->MouseButtonDown(aMouseEvent); + } + else + { + if (pFrameData->mpTrackWin) + { + pFrameData->mpTrackWin->EndTracking(); + } + + pFrameData->mpMouseDownWin = nullptr; + pFrameData->mpMouseMoveWin = nullptr; + pFrameData->mbStartDragCalled = false; + xWindow->MouseButtonUp(aMouseEvent); + } + + if (nEvent == MouseNotifyEvent::MOUSEBUTTONDOWN) + { + // ContextMenu + if ( (nCode == MouseSettings::GetContextMenuCode()) && + (nClicks == MouseSettings::GetContextMenuClicks()) ) + { + ImplCallCommand(xWindow, CommandEventId::ContextMenu, nullptr, true, &aWinPos); + } + } + + return true; +} + +static vcl::Window* ImplGetKeyInputWindow( vcl::Window* pWindow ) +{ + ImplSVData* pSVData = ImplGetSVData(); + + // determine last input time + pSVData->maAppData.mnLastInputTime = tools::Time::GetSystemTicks(); + + // #127104# workaround for destroyed windows + if( pWindow->ImplGetWindowImpl() == nullptr ) + return nullptr; + + // find window - is every time the window which has currently the + // focus or the last time the focus. + + // the first floating window always has the focus, try it, or any parent floating windows, first + vcl::Window* pChild = pSVData->mpWinData->mpFirstFloat; + while (pChild) + { + if (pChild->ImplGetWindowImpl()) + { + if (pChild->ImplGetWindowImpl()->mbFloatWin) + { + if (static_cast<FloatingWindow *>(pChild)->GrabsFocus()) + break; + } + else if (pChild->ImplGetWindowImpl()->mbDockWin) + { + vcl::Window* pParent = pChild->GetWindow(GetWindowType::RealParent); + if (pParent && pParent->ImplGetWindowImpl()->mbFloatWin && + static_cast<FloatingWindow *>(pParent)->GrabsFocus()) + break; + } + } + pChild = pChild->GetParent(); + } + + if (!pChild) + pChild = pWindow; + + pChild = pChild->ImplGetWindowImpl() && pChild->ImplGetWindowImpl()->mpFrameData ? pChild->ImplGetWindowImpl()->mpFrameData->mpFocusWin.get() : nullptr; + + // no child - then no input + if ( !pChild ) + return nullptr; + + // We call also KeyInput if we haven't the focus, because on Unix + // system this is often the case when a Lookup Choice Window has + // the focus - because this windows send the KeyInput directly to + // the window without resetting the focus + + // no keyinput to disabled windows + if ( !pChild->IsEnabled() || !pChild->IsInputEnabled() || pChild->IsInModalMode() ) + return nullptr; + + return pChild; +} + +static bool ImplHandleKey( vcl::Window* pWindow, MouseNotifyEvent nSVEvent, + sal_uInt16 nKeyCode, sal_uInt16 nCharCode, sal_uInt16 nRepeat, bool bForward ) +{ + ImplSVData* pSVData = ImplGetSVData(); + vcl::KeyCode aKeyCode( nKeyCode, nKeyCode ); + sal_uInt16 nEvCode = aKeyCode.GetCode(); + + // allow application key listeners to remove the key event + // but make sure we're not forwarding external KeyEvents, (ie where bForward is false) + // because those are coming back from the listener itself and MUST be processed + if( bForward ) + { + VclEventId nVCLEvent; + switch( nSVEvent ) + { + case MouseNotifyEvent::KEYINPUT: + nVCLEvent = VclEventId::WindowKeyInput; + break; + case MouseNotifyEvent::KEYUP: + nVCLEvent = VclEventId::WindowKeyUp; + break; + default: + nVCLEvent = VclEventId::NONE; + break; + } + KeyEvent aKeyEvent(static_cast<sal_Unicode>(nCharCode), aKeyCode, nRepeat); + if (nVCLEvent != VclEventId::NONE && Application::HandleKey(nVCLEvent, pWindow, &aKeyEvent)) + return true; + } + + bool bCtrlF6 = (aKeyCode.GetCode() == KEY_F6) && aKeyCode.IsMod1(); + + // determine last input time + pSVData->maAppData.mnLastInputTime = tools::Time::GetSystemTicks(); + + // handle tracking window + if ( nSVEvent == MouseNotifyEvent::KEYINPUT ) + { + if ( ImplGetSVHelpData().mbExtHelpMode ) + { + Help::EndExtHelp(); + if ( nEvCode == KEY_ESCAPE ) + return true; + } + if ( ImplGetSVHelpData().mpHelpWin ) + ImplDestroyHelpWindow( false ); + + // AutoScrollMode + if (pSVData->mpWinData->mpAutoScrollWin) + { + pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll(); + if ( nEvCode == KEY_ESCAPE ) + return true; + } + + if (pSVData->mpWinData->mpTrackWin) + { + sal_uInt16 nOrigCode = aKeyCode.GetCode(); + + if ( nOrigCode == KEY_ESCAPE ) + { + pSVData->mpWinData->mpTrackWin->EndTracking( TrackingEventFlags::Cancel | TrackingEventFlags::Key ); + if (pSVData->mpWinData->mpFirstFloat) + { + FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat(); + if ( !(pLastLevelFloat->GetPopupModeFlags() & FloatWinPopupFlags::NoKeyClose) ) + { + sal_uInt16 nEscCode = aKeyCode.GetCode(); + + if ( nEscCode == KEY_ESCAPE ) + pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll ); + } + } + return true; + } + else if ( nOrigCode == KEY_RETURN ) + { + pSVData->mpWinData->mpTrackWin->EndTracking( TrackingEventFlags::Key ); + return true; + } + else + return true; + } + + // handle FloatingMode + if (pSVData->mpWinData->mpFirstFloat) + { + FloatingWindow* pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat(); + if ( !(pLastLevelFloat->GetPopupModeFlags() & FloatWinPopupFlags::NoKeyClose) ) + { + sal_uInt16 nCode = aKeyCode.GetCode(); + + if ( (nCode == KEY_ESCAPE) || bCtrlF6) + { + pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll ); + if( !bCtrlF6 ) + return true; + } + } + } + + // test for accel + if ( pSVData->maAppData.mpAccelMgr ) + { + if ( pSVData->maAppData.mpAccelMgr->IsAccelKey( aKeyCode ) ) + return true; + } + } + + // find window + VclPtr<vcl::Window> pChild = ImplGetKeyInputWindow( pWindow ); + if ( !pChild ) + return false; + + // #i1820# use locale specific decimal separator + if (nEvCode == KEY_DECIMAL) + { + // tdf#138932: don't modify the meaning of the key for password box + bool bPass = false; + if (auto pEdit = dynamic_cast<Edit*>(pChild.get())) + bPass = pEdit->IsPassword(); + if (!bPass && Application::GetSettings().GetMiscSettings().GetEnableLocalizedDecimalSep()) + { + OUString aSep(pWindow->GetSettings().GetLocaleDataWrapper().getNumDecimalSep()); + nCharCode = static_cast<sal_uInt16>(aSep[0]); + } + } + + // RTL: mirror cursor keys + if( (aKeyCode.GetCode() == KEY_LEFT || aKeyCode.GetCode() == KEY_RIGHT) && + pChild->IsRTLEnabled() && pChild->GetOutDev()->HasMirroredGraphics() ) + aKeyCode = vcl::KeyCode( aKeyCode.GetCode() == KEY_LEFT ? KEY_RIGHT : KEY_LEFT, aKeyCode.GetModifier() ); + + KeyEvent aKeyEvt( static_cast<sal_Unicode>(nCharCode), aKeyCode, nRepeat ); + NotifyEvent aNotifyEvt( nSVEvent, pChild, &aKeyEvt ); + bool bKeyPreNotify = ImplCallPreNotify( aNotifyEvt ); + bool bRet = true; + + if ( !bKeyPreNotify && !pChild->isDisposed() ) + { + if ( nSVEvent == MouseNotifyEvent::KEYINPUT ) + { + UITestLogger::getInstance().logKeyInput(pChild, aKeyEvt); + pChild->ImplGetWindowImpl()->mbKeyInput = false; + pChild->KeyInput( aKeyEvt ); + } + else + { + pChild->ImplGetWindowImpl()->mbKeyUp = false; + pChild->KeyUp( aKeyEvt ); + } + if( !pChild->isDisposed() ) + aNotifyEvt.GetWindow()->ImplNotifyKeyMouseCommandEventListeners( aNotifyEvt ); + } + + if ( pChild->isDisposed() ) + return true; + + if ( nSVEvent == MouseNotifyEvent::KEYINPUT ) + { + if ( !bKeyPreNotify && pChild->ImplGetWindowImpl()->mbKeyInput ) + { + sal_uInt16 nCode = aKeyCode.GetCode(); + + // #101999# is focus in or below toolbox + bool bToolboxFocus=false; + if( (nCode == KEY_F1) && aKeyCode.IsShift() ) + { + vcl::Window *pWin = pWindow->ImplGetWindowImpl()->mpFrameData->mpFocusWin; + while( pWin ) + { + if( pWin->ImplGetWindowImpl()->mbToolBox ) + { + bToolboxFocus = true; + break; + } + else + pWin = pWin->GetParent(); + } + } + + // ContextMenu + if ( (nCode == KEY_CONTEXTMENU) || ((nCode == KEY_F10) && aKeyCode.IsShift() && !aKeyCode.IsMod1() && !aKeyCode.IsMod2() ) ) + bRet = !ImplCallCommand( pChild, CommandEventId::ContextMenu ); + else if ( ( (nCode == KEY_F2) && aKeyCode.IsShift() ) || ( (nCode == KEY_F1) && aKeyCode.IsMod1() ) || + // #101999# no active help when focus in toolbox, simulate BalloonHelp instead + ( (nCode == KEY_F1) && aKeyCode.IsShift() && bToolboxFocus ) ) + { + // TipHelp via Keyboard (Shift-F2 or Ctrl-F1) + // simulate mouseposition at center of window + + Size aSize = pChild->GetOutDev()->GetOutputSize(); + Point aPos( aSize.getWidth()/2, aSize.getHeight()/2 ); + aPos = pChild->OutputToScreenPixel( aPos ); + + HelpEvent aHelpEvent( aPos, HelpEventMode::BALLOON ); + aHelpEvent.SetKeyboardActivated( true ); + ImplGetSVHelpData().mbSetKeyboardHelp = true; + pChild->RequestHelp( aHelpEvent ); + ImplGetSVHelpData().mbSetKeyboardHelp = false; + } + else if ( (nCode == KEY_F1) || (nCode == KEY_HELP) ) + { + if ( !aKeyCode.GetModifier() ) + { + if ( ImplGetSVHelpData().mbContextHelp ) + { + Point aMousePos = pChild->OutputToScreenPixel( pChild->GetPointerPosPixel() ); + HelpEvent aHelpEvent( aMousePos, HelpEventMode::CONTEXT ); + pChild->RequestHelp( aHelpEvent ); + } + else + bRet = false; + } + else if ( aKeyCode.IsShift() ) + { + if ( ImplGetSVHelpData().mbExtHelp ) + Help::StartExtHelp(); + else + bRet = false; + } + } + else + bRet = false; + } + } + else + { + if ( !bKeyPreNotify && pChild->ImplGetWindowImpl()->mbKeyUp ) + bRet = false; + } + + // #105591# send keyinput to parent if we are a floating window and the key was not processed yet + if( !bRet && pWindow->ImplGetWindowImpl() && pWindow->ImplGetWindowImpl()->mbFloatWin && pWindow->GetParent() && (pWindow->ImplGetWindowImpl()->mpFrame != pWindow->GetParent()->ImplGetWindowImpl()->mpFrame) ) + { + pChild = pWindow->GetParent(); + + // call handler + NotifyEvent aNEvt( nSVEvent, pChild, &aKeyEvt ); + bool bPreNotify = ImplCallPreNotify( aNEvt ); + if ( pChild->isDisposed() ) + return true; + + if ( !bPreNotify ) + { + if ( nSVEvent == MouseNotifyEvent::KEYINPUT ) + { + pChild->ImplGetWindowImpl()->mbKeyInput = false; + pChild->KeyInput( aKeyEvt ); + } + else + { + pChild->ImplGetWindowImpl()->mbKeyUp = false; + pChild->KeyUp( aKeyEvt ); + } + + if( !pChild->isDisposed() ) + aNEvt.GetWindow()->ImplNotifyKeyMouseCommandEventListeners( aNEvt ); + if ( pChild->isDisposed() ) + return true; + } + + if( bPreNotify || !pChild->ImplGetWindowImpl()->mbKeyInput ) + bRet = true; + } + + return bRet; +} + +static bool ImplHandleExtTextInput( vcl::Window* pWindow, + const OUString& rText, + const ExtTextInputAttr* pTextAttr, + sal_Int32 nCursorPos, sal_uInt16 nCursorFlags ) +{ + ImplSVData* pSVData = ImplGetSVData(); + vcl::Window* pChild = nullptr; + + int nTries = 200; + while( nTries-- ) + { + pChild = pSVData->mpWinData->mpExtTextInputWin; + if ( !pChild ) + { + pChild = ImplGetKeyInputWindow( pWindow ); + if ( !pChild ) + return false; + } + if( !pChild->ImplGetWindowImpl()->mpFrameData->mnFocusId ) + break; + + if (comphelper::LibreOfficeKit::isActive()) + { + SAL_WARN("vcl", "Failed to get ext text input context"); + break; + } + Application::Yield(); + } + + // If it is the first ExtTextInput call, we inform the information + // and allocate the data, which we must store in this mode + ImplWinData* pWinData = pChild->ImplGetWinData(); + if ( !pChild->ImplGetWindowImpl()->mbExtTextInput ) + { + pChild->ImplGetWindowImpl()->mbExtTextInput = true; + pWinData->mpExtOldText = OUString(); + pWinData->mpExtOldAttrAry.reset(); + pSVData->mpWinData->mpExtTextInputWin = pChild; + ImplCallCommand( pChild, CommandEventId::StartExtTextInput ); + } + + // be aware of being recursively called in StartExtTextInput + if ( !pChild->ImplGetWindowImpl()->mbExtTextInput ) + return false; + + // Test for changes + bool bOnlyCursor = false; + sal_Int32 nMinLen = std::min( pWinData->mpExtOldText->getLength(), rText.getLength() ); + sal_Int32 nDeltaStart = 0; + while ( nDeltaStart < nMinLen ) + { + if ( (*pWinData->mpExtOldText)[nDeltaStart] != rText[nDeltaStart] ) + break; + nDeltaStart++; + } + if ( pWinData->mpExtOldAttrAry || pTextAttr ) + { + if ( !pWinData->mpExtOldAttrAry || !pTextAttr ) + nDeltaStart = 0; + else + { + sal_Int32 i = 0; + while ( i < nDeltaStart ) + { + if ( pWinData->mpExtOldAttrAry[i] != pTextAttr[i] ) + { + nDeltaStart = i; + break; + } + i++; + } + } + } + if ( (nDeltaStart >= nMinLen) && + (pWinData->mpExtOldText->getLength() == rText.getLength()) ) + bOnlyCursor = true; + + // Call Event and store the information + CommandExtTextInputData aData( rText, pTextAttr, + nCursorPos, nCursorFlags, + bOnlyCursor ); + *pWinData->mpExtOldText = rText; + pWinData->mpExtOldAttrAry.reset(); + if ( pTextAttr ) + { + pWinData->mpExtOldAttrAry.reset( new ExtTextInputAttr[rText.getLength()] ); + memcpy( pWinData->mpExtOldAttrAry.get(), pTextAttr, rText.getLength()*sizeof( ExtTextInputAttr ) ); + } + return !ImplCallCommand( pChild, CommandEventId::ExtTextInput, &aData ); +} + +static bool ImplHandleEndExtTextInput() +{ + ImplSVData* pSVData = ImplGetSVData(); + vcl::Window* pChild = pSVData->mpWinData->mpExtTextInputWin; + bool bRet = false; + + if ( pChild ) + { + pChild->ImplGetWindowImpl()->mbExtTextInput = false; + pSVData->mpWinData->mpExtTextInputWin = nullptr; + ImplWinData* pWinData = pChild->ImplGetWinData(); + pWinData->mpExtOldText.reset(); + pWinData->mpExtOldAttrAry.reset(); + bRet = !ImplCallCommand( pChild, CommandEventId::EndExtTextInput ); + } + + return bRet; +} + +static void ImplHandleExtTextInputPos( vcl::Window* pWindow, + tools::Rectangle& rRect, tools::Long& rInputWidth, + bool * pVertical ) +{ + ImplSVData* pSVData = ImplGetSVData(); + vcl::Window* pChild = pSVData->mpWinData->mpExtTextInputWin; + + if ( !pChild ) + pChild = ImplGetKeyInputWindow( pWindow ); + else + { + // Test, if the Window is related to the frame + if ( !pWindow->ImplIsWindowOrChild( pChild ) ) + pChild = ImplGetKeyInputWindow( pWindow ); + } + + if ( pChild ) + { + const OutputDevice *pChildOutDev = pChild->GetOutDev(); + ImplCallCommand( pChild, CommandEventId::CursorPos ); + const tools::Rectangle* pRect = pChild->GetCursorRect(); + if ( pRect ) + rRect = pChildOutDev->ImplLogicToDevicePixel( *pRect ); + else + { + vcl::Cursor* pCursor = pChild->GetCursor(); + if ( pCursor ) + { + Point aPos = pChildOutDev->ImplLogicToDevicePixel( pCursor->GetPos() ); + Size aSize = pChild->LogicToPixel( pCursor->GetSize() ); + if ( !aSize.Width() ) + aSize.setWidth( pChild->GetSettings().GetStyleSettings().GetCursorSize() ); + rRect = tools::Rectangle( aPos, aSize ); + } + else + rRect = tools::Rectangle( Point( pChild->GetOutOffXPixel(), pChild->GetOutOffYPixel() ), Size() ); + } + rInputWidth = pChild->GetOutDev()->ImplLogicWidthToDevicePixel( pChild->GetCursorExtTextInputWidth() ); + if ( !rInputWidth ) + rInputWidth = rRect.GetWidth(); + } + if (pVertical != nullptr) + *pVertical + = pChild != nullptr && pChild->GetInputContext().GetFont().IsVertical(); +} + +static bool ImplHandleInputContextChange( vcl::Window* pWindow ) +{ + vcl::Window* pChild = ImplGetKeyInputWindow( pWindow ); + CommandInputContextData aData; + return !ImplCallCommand( pChild, CommandEventId::InputContextChange, &aData ); +} + +static bool ImplCallWheelCommand( const VclPtr<vcl::Window>& pWindow, const Point& rPos, + const CommandWheelData* pWheelData ) +{ + Point aCmdMousePos = pWindow->ImplFrameToOutput( rPos ); + CommandEvent aCEvt( aCmdMousePos, CommandEventId::Wheel, true, pWheelData ); + NotifyEvent aNCmdEvt( MouseNotifyEvent::COMMAND, pWindow, &aCEvt ); + bool bPreNotify = ImplCallPreNotify( aNCmdEvt ); + if ( pWindow->isDisposed() ) + return false; + if ( !bPreNotify ) + { + pWindow->ImplGetWindowImpl()->mbCommand = false; + pWindow->Command( aCEvt ); + if ( pWindow->isDisposed() ) + return false; + if ( pWindow->ImplGetWindowImpl()->mbCommand ) + return true; + } + return false; +} + +static bool acceptableWheelScrollTarget(const vcl::Window *pMouseWindow) +{ + return (pMouseWindow && !pMouseWindow->isDisposed() && pMouseWindow->IsInputEnabled() && !pMouseWindow->IsInModalMode()); +} + +//If the last event at the same absolute screen position was handled by a +//different window then reuse that window if the event occurs within 1/2 a +//second, i.e. so scrolling down something like the calc sidebar that contains +//widgets that respond to wheel events will continue to send the event to the +//scrolling widget in favour of the widget that happens to end up under the +//mouse. +static bool shouldReusePreviousMouseWindow(const SalWheelMouseEvent& rPrevEvt, const SalWheelMouseEvent& rEvt) +{ + return (rEvt.mnX == rPrevEvt.mnX && rEvt.mnY == rPrevEvt.mnY && rEvt.mnTime-rPrevEvt.mnTime < 500/*ms*/); +} + +namespace { + +class HandleGestureEventBase +{ +protected: + ImplSVData* m_pSVData; + VclPtr<vcl::Window> m_pWindow; + Point m_aMousePos; + +public: + HandleGestureEventBase(vcl::Window *pWindow, const Point &rMousePos) + : m_pSVData(ImplGetSVData()) + , m_pWindow(pWindow) + , m_aMousePos(rMousePos) + { + } + bool Setup(); + vcl::Window* FindTarget(); + vcl::Window* Dispatch(vcl::Window* pTarget); + virtual bool CallCommand(vcl::Window *pWindow, const Point &rMousePos) = 0; + virtual ~HandleGestureEventBase() {} +}; + +} + +bool HandleGestureEventBase::Setup() +{ + + if (m_pSVData->mpWinData->mpAutoScrollWin) + m_pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll(); + if (ImplGetSVHelpData().mpHelpWin) + ImplDestroyHelpWindow( true ); + return !m_pWindow->isDisposed(); +} + +vcl::Window* HandleGestureEventBase::FindTarget() +{ + // first check any floating window ( eg. drop down listboxes) + vcl::Window *pMouseWindow = nullptr; + + if (m_pSVData->mpWinData->mpFirstFloat && !m_pSVData->mpWinData->mpCaptureWin && + !m_pSVData->mpWinData->mpFirstFloat->ImplIsFloatPopupModeWindow( m_pWindow ) ) + { + bool bHitTestInsideRect = false; + pMouseWindow = m_pSVData->mpWinData->mpFirstFloat->ImplFloatHitTest( m_pWindow, m_aMousePos, bHitTestInsideRect ); + if (!pMouseWindow) + pMouseWindow = m_pSVData->mpWinData->mpFirstFloat; + } + // then try the window directly beneath the mouse + if( !pMouseWindow ) + { + pMouseWindow = m_pWindow->ImplFindWindow( m_aMousePos ); + } + else + { + // transform coordinates to float window frame coordinates + pMouseWindow = pMouseWindow->ImplFindWindow( + pMouseWindow->OutputToScreenPixel( + pMouseWindow->AbsoluteScreenToOutputPixel( + m_pWindow->OutputToAbsoluteScreenPixel( + m_pWindow->ScreenToOutputPixel( m_aMousePos ) ) ) ) ); + } + + while (acceptableWheelScrollTarget(pMouseWindow)) + { + if (pMouseWindow->IsEnabled()) + break; + //try the parent if this one is disabled + pMouseWindow = pMouseWindow->GetParent(); + } + + return pMouseWindow; +} + +vcl::Window *HandleGestureEventBase::Dispatch(vcl::Window* pMouseWindow) +{ + vcl::Window *pDispatchedTo = nullptr; + + if (acceptableWheelScrollTarget(pMouseWindow) && pMouseWindow->IsEnabled()) + { + // transform coordinates to float window frame coordinates + Point aRelMousePos( pMouseWindow->OutputToScreenPixel( + pMouseWindow->AbsoluteScreenToOutputPixel( + m_pWindow->OutputToAbsoluteScreenPixel( + m_pWindow->ScreenToOutputPixel( m_aMousePos ) ) ) ) ); + bool bPropagate = CallCommand(pMouseWindow, aRelMousePos); + if (!bPropagate) + pDispatchedTo = pMouseWindow; + } + + // if the command was not handled try the focus window + if (!pDispatchedTo) + { + vcl::Window* pFocusWindow = m_pWindow->ImplGetWindowImpl()->mpFrameData->mpFocusWin; + if ( pFocusWindow && (pFocusWindow != pMouseWindow) && + (pFocusWindow == m_pSVData->mpWinData->mpFocusWin) ) + { + // no wheel-messages to disabled windows + if ( pFocusWindow->IsEnabled() && pFocusWindow->IsInputEnabled() && ! pFocusWindow->IsInModalMode() ) + { + // transform coordinates to focus window frame coordinates + Point aRelMousePos( pFocusWindow->OutputToScreenPixel( + pFocusWindow->AbsoluteScreenToOutputPixel( + m_pWindow->OutputToAbsoluteScreenPixel( + m_pWindow->ScreenToOutputPixel( m_aMousePos ) ) ) ) ); + bool bPropagate = CallCommand(pFocusWindow, aRelMousePos); + if (!bPropagate) + pDispatchedTo = pMouseWindow; + } + } + } + return pDispatchedTo; +} + +namespace { + +class HandleWheelEvent : public HandleGestureEventBase +{ +private: + CommandWheelData m_aWheelData; +public: + HandleWheelEvent(vcl::Window *pWindow, const SalWheelMouseEvent& rEvt) + : HandleGestureEventBase(pWindow, Point(rEvt.mnX, rEvt.mnY)) + { + CommandWheelMode nMode; + sal_uInt16 nCode = rEvt.mnCode; + bool bHorz = rEvt.mbHorz; + bool bPixel = rEvt.mbDeltaIsPixel; + if ( nCode & KEY_MOD1 ) + nMode = CommandWheelMode::ZOOM; + else if ( nCode & KEY_MOD2 ) + nMode = CommandWheelMode::DATAZOOM; + else + { + nMode = CommandWheelMode::SCROLL; + // #i85450# interpret shift-wheel as horizontal wheel action + if( (nCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)) == KEY_SHIFT ) + bHorz = true; + } + + m_aWheelData = CommandWheelData(rEvt.mnDelta, rEvt.mnNotchDelta, rEvt.mnScrollLines, nMode, nCode, bHorz, bPixel); + + } + virtual bool CallCommand(vcl::Window *pWindow, const Point &rMousePos) override + { + return ImplCallWheelCommand(pWindow, rMousePos, &m_aWheelData); + } + bool HandleEvent(const SalWheelMouseEvent& rEvt); +}; + +} + +bool HandleWheelEvent::HandleEvent(const SalWheelMouseEvent& rEvt) +{ + if (!Setup()) + return false; + + VclPtr<vcl::Window> xMouseWindow = FindTarget(); + + ImplSVData* pSVData = ImplGetSVData(); + + // avoid the problem that scrolling via wheel to this point brings a widget + // under the mouse that also accepts wheel commands, so stick with the old + // widget if the time gap is very small + if (shouldReusePreviousMouseWindow(pSVData->mpWinData->maLastWheelEvent, rEvt) && + acceptableWheelScrollTarget(pSVData->mpWinData->mpLastWheelWindow)) + { + xMouseWindow = pSVData->mpWinData->mpLastWheelWindow; + } + + pSVData->mpWinData->maLastWheelEvent = rEvt; + + pSVData->mpWinData->mpLastWheelWindow = Dispatch(xMouseWindow); + + return pSVData->mpWinData->mpLastWheelWindow; +} + +namespace { + +class HandleGestureEvent : public HandleGestureEventBase +{ +public: + HandleGestureEvent(vcl::Window *pWindow, const Point &rMousePos) + : HandleGestureEventBase(pWindow, rMousePos) + { + } + bool HandleEvent(); +}; + +} + +bool HandleGestureEvent::HandleEvent() +{ + if (!Setup()) + return false; + + vcl::Window *pTarget = FindTarget(); + + bool bHandled = Dispatch(pTarget) != nullptr; + return bHandled; +} + +static bool ImplHandleWheelEvent(vcl::Window* pWindow, const SalWheelMouseEvent& rEvt) +{ + HandleWheelEvent aHandler(pWindow, rEvt); + return aHandler.HandleEvent(rEvt); +} + +namespace { + +class HandleSwipeEvent : public HandleGestureEvent +{ +private: + CommandSwipeData m_aSwipeData; +public: + HandleSwipeEvent(vcl::Window *pWindow, const SalSwipeEvent& rEvt) + : HandleGestureEvent(pWindow, Point(rEvt.mnX, rEvt.mnY)), + m_aSwipeData(rEvt.mnVelocityX) + { + } + virtual bool CallCommand(vcl::Window *pWindow, const Point &/*rMousePos*/) override + { + return ImplCallCommand(pWindow, CommandEventId::Swipe, &m_aSwipeData); + } +}; + +} + +static bool ImplHandleSwipe(vcl::Window *pWindow, const SalSwipeEvent& rEvt) +{ + HandleSwipeEvent aHandler(pWindow, rEvt); + return aHandler.HandleEvent(); +} + +namespace { + +class HandleLongPressEvent : public HandleGestureEvent +{ +private: + CommandLongPressData m_aLongPressData; +public: + HandleLongPressEvent(vcl::Window *pWindow, const SalLongPressEvent& rEvt) + : HandleGestureEvent(pWindow, Point(rEvt.mnX, rEvt.mnY)), + m_aLongPressData(rEvt.mnX, rEvt.mnY) + { + } + virtual bool CallCommand(vcl::Window *pWindow, const Point &/*rMousePos*/) override + { + return ImplCallCommand(pWindow, CommandEventId::LongPress, &m_aLongPressData); + } +}; + +} + +static bool ImplHandleLongPress(vcl::Window *pWindow, const SalLongPressEvent& rEvt) +{ + HandleLongPressEvent aHandler(pWindow, rEvt); + return aHandler.HandleEvent(); +} + +namespace { + +class HandleGeneralGestureEvent : public HandleGestureEvent +{ +private: + CommandGestureData m_aGestureData; + +public: + HandleGeneralGestureEvent(vcl::Window* pWindow, const SalGestureEvent& rEvent) + : HandleGestureEvent(pWindow, Point(rEvent.mnX, rEvent.mnY)) + , m_aGestureData(rEvent.mnX, rEvent.mnY, rEvent.meEventType, rEvent.mfOffset, rEvent.meOrientation) + { + } + + virtual bool CallCommand(vcl::Window* pWindow, const Point& /*rMousePos*/) override + { + return ImplCallCommand(pWindow, CommandEventId::Gesture, &m_aGestureData); + } +}; + +} + +static bool ImplHandleGestureEvent(vcl::Window* pWindow, const SalGestureEvent& rEvent) +{ + HandleGeneralGestureEvent aHandler(pWindow, rEvent); + return aHandler.HandleEvent(); +} + +static void ImplHandlePaint( vcl::Window* pWindow, const tools::Rectangle& rBoundRect, bool bImmediateUpdate ) +{ + // system paint events must be checked for re-mirroring + pWindow->ImplGetWindowImpl()->mnPaintFlags |= ImplPaintFlags::CheckRtl; + + // trigger paint for all windows that live in the new paint region + vcl::Region aRegion( rBoundRect ); + pWindow->ImplInvalidateOverlapFrameRegion( aRegion ); + if( bImmediateUpdate ) + { + // #i87663# trigger possible pending resize notifications + // (GetSizePixel does that for us) + pWindow->GetSizePixel(); + // force drawing immediately + pWindow->PaintImmediately(); + } +} + +static void KillOwnPopups( vcl::Window const * pWindow ) +{ + ImplSVData* pSVData = ImplGetSVData(); + vcl::Window *pParent = pWindow->ImplGetWindowImpl()->mpFrameWindow; + vcl::Window *pChild = pSVData->mpWinData->mpFirstFloat; + if ( pChild && pParent->ImplIsWindowOrChild( pChild, true ) ) + { + if (!(pSVData->mpWinData->mpFirstFloat->GetPopupModeFlags() + & FloatWinPopupFlags::NoAppFocusClose)) + pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel + | FloatWinPopupEndFlags::CloseAll); + } +} + +void ImplHandleResize( vcl::Window* pWindow, tools::Long nNewWidth, tools::Long nNewHeight ) +{ + const bool bChanged = (nNewWidth != pWindow->GetOutputSizePixel().Width()) || (nNewHeight != pWindow->GetOutDev()->GetOutputHeightPixel()); + if (bChanged && pWindow->GetStyle() & (WB_MOVEABLE|WB_SIZEABLE)) + { + KillOwnPopups( pWindow ); + if( pWindow->ImplGetWindow() != ImplGetSVHelpData().mpHelpWin ) + ImplDestroyHelpWindow( true ); + } + + if ( + (nNewWidth > 0 && nNewHeight > 0) || + pWindow->ImplGetWindow()->ImplGetWindowImpl()->mbAllResize + ) + { + if (bChanged) + { + pWindow->GetOutDev()->mnOutWidth = nNewWidth; + pWindow->GetOutDev()->mnOutHeight = nNewHeight; + pWindow->ImplGetWindowImpl()->mbWaitSystemResize = false; + if ( pWindow->IsReallyVisible() ) + pWindow->ImplSetClipFlag(); + if ( pWindow->IsVisible() || pWindow->ImplGetWindow()->ImplGetWindowImpl()->mbAllResize || + ( pWindow->ImplGetWindowImpl()->mbFrame && pWindow->ImplGetWindowImpl()->mpClientWindow ) ) // propagate resize for system border windows + { + bool bStartTimer = true; + // use resize buffering for user resizes + // ownerdraw decorated windows and floating windows can be resized immediately (i.e. synchronously) + if( pWindow->ImplGetWindowImpl()->mbFrame && (pWindow->GetStyle() & WB_SIZEABLE) + && !(pWindow->GetStyle() & WB_OWNERDRAWDECORATION) // synchronous resize for ownerdraw decorated windows (toolbars) + && !pWindow->ImplGetWindowImpl()->mbFloatWin ) // synchronous resize for floating windows, #i43799# + { + if( pWindow->ImplGetWindowImpl()->mpClientWindow ) + { + // #i42750# presentation wants to be informed about resize + // as early as possible + WorkWindow* pWorkWindow = dynamic_cast<WorkWindow*>(pWindow->ImplGetWindowImpl()->mpClientWindow.get()); + if( ! pWorkWindow || pWorkWindow->IsPresentationMode() ) + bStartTimer = false; + } + else + { + WorkWindow* pWorkWindow = dynamic_cast<WorkWindow*>(pWindow); + if( ! pWorkWindow || pWorkWindow->IsPresentationMode() ) + bStartTimer = false; + } + } + else + bStartTimer = false; + + if( bStartTimer ) + pWindow->ImplGetWindowImpl()->mpFrameData->maResizeIdle.Start(); + else + pWindow->ImplCallResize(); // otherwise menus cannot be positioned + } + else + pWindow->ImplGetWindowImpl()->mbCallResize = true; + + if (pWindow->SupportsDoubleBuffering() && pWindow->ImplGetWindowImpl()->mbFrame) + { + // Propagate resize for the frame's buffer. + pWindow->ImplGetWindowImpl()->mpFrameData->mpBuffer->SetOutputSizePixel(pWindow->GetOutputSizePixel()); + } + } + } + + pWindow->ImplGetWindowImpl()->mpFrameData->mbNeedSysWindow = (nNewWidth < IMPL_MIN_NEEDSYSWIN) || + (nNewHeight < IMPL_MIN_NEEDSYSWIN); + bool bMinimized = (nNewWidth <= 0) || (nNewHeight <= 0); + if( bMinimized != pWindow->ImplGetWindowImpl()->mpFrameData->mbMinimized ) + pWindow->ImplGetWindowImpl()->mpFrameWindow->ImplNotifyIconifiedState( bMinimized ); + pWindow->ImplGetWindowImpl()->mpFrameData->mbMinimized = bMinimized; +} + +static void ImplHandleMove( vcl::Window* pWindow ) +{ + if( pWindow->ImplGetWindowImpl()->mbFrame && pWindow->ImplIsFloatingWindow() && pWindow->IsReallyVisible() ) + { + static_cast<FloatingWindow*>(pWindow)->EndPopupMode( FloatWinPopupEndFlags::TearOff ); + pWindow->ImplCallMove(); + } + + if( pWindow->GetStyle() & (WB_MOVEABLE|WB_SIZEABLE) ) + { + KillOwnPopups( pWindow ); + if( pWindow->ImplGetWindow() != ImplGetSVHelpData().mpHelpWin ) + ImplDestroyHelpWindow( true ); + } + + if ( pWindow->IsVisible() ) + pWindow->ImplCallMove(); + else + pWindow->ImplGetWindowImpl()->mbCallMove = true; // make sure the framepos will be updated on the next Show() + + if ( pWindow->ImplGetWindowImpl()->mbFrame && pWindow->ImplGetWindowImpl()->mpClientWindow ) + pWindow->ImplGetWindowImpl()->mpClientWindow->ImplCallMove(); // notify client to update geometry + +} + +static void ImplHandleMoveResize( vcl::Window* pWindow, tools::Long nNewWidth, tools::Long nNewHeight ) +{ + ImplHandleMove( pWindow ); + ImplHandleResize( pWindow, nNewWidth, nNewHeight ); +} + +static void ImplActivateFloatingWindows( vcl::Window const * pWindow, bool bActive ) +{ + // First check all overlapping windows + vcl::Window* pTempWindow = pWindow->ImplGetWindowImpl()->mpFirstOverlap; + while ( pTempWindow ) + { + if ( pTempWindow->GetActivateMode() == ActivateModeFlags::NONE ) + { + if ( (pTempWindow->GetType() == WindowType::BORDERWINDOW) && + (pTempWindow->ImplGetWindow()->GetType() == WindowType::FLOATINGWINDOW) ) + static_cast<ImplBorderWindow*>(pTempWindow)->SetDisplayActive( bActive ); + } + + ImplActivateFloatingWindows( pTempWindow, bActive ); + pTempWindow = pTempWindow->ImplGetWindowImpl()->mpNext; + } +} + +IMPL_LINK_NOARG(vcl::Window, ImplAsyncFocusHdl, void*, void) +{ + if (!ImplGetWindowImpl() || !ImplGetWindowImpl()->mpFrameData) + return; + + ImplGetWindowImpl()->mpFrameData->mnFocusId = nullptr; + + // If the status has been preserved, because we got back the focus + // in the meantime, we do nothing + bool bHasFocus = ImplGetWindowImpl()->mpFrameData->mbHasFocus || ImplGetWindowImpl()->mpFrameData->mbSysObjFocus; + + // next execute the delayed functions + if ( bHasFocus ) + { + // redraw all floating windows inactive + if ( ImplGetWindowImpl()->mpFrameData->mbStartFocusState != bHasFocus ) + ImplActivateFloatingWindows( this, bHasFocus ); + + if ( ImplGetWindowImpl()->mpFrameData->mpFocusWin ) + { + bool bHandled = false; + if ( ImplGetWindowImpl()->mpFrameData->mpFocusWin->IsInputEnabled() && + ! ImplGetWindowImpl()->mpFrameData->mpFocusWin->IsInModalMode() ) + { + if ( ImplGetWindowImpl()->mpFrameData->mpFocusWin->IsEnabled() ) + { + ImplGetWindowImpl()->mpFrameData->mpFocusWin->GrabFocus(); + bHandled = true; + } + else if( ImplGetWindowImpl()->mpFrameData->mpFocusWin->ImplHasDlgCtrl() ) + { + // #109094# if the focus is restored to a disabled dialog control (was disabled meanwhile) + // try to move it to the next control + ImplGetWindowImpl()->mpFrameData->mpFocusWin->ImplDlgCtrlNextWindow(); + bHandled = true; + } + } + if ( !bHandled ) + { + ImplSVData* pSVData = ImplGetSVData(); + vcl::Window* pTopLevelWindow = ImplGetWindowImpl()->mpFrameData->mpFocusWin->ImplGetFirstOverlapWindow(); + + if ((!pTopLevelWindow->IsInputEnabled() || pTopLevelWindow->IsInModalMode()) + && !pSVData->mpWinData->mpExecuteDialogs.empty()) + pSVData->mpWinData->mpExecuteDialogs.back()->ToTop(ToTopFlags::RestoreWhenMin | ToTopFlags::GrabFocusOnly); + else + pTopLevelWindow->GrabFocus(); + } + } + else + GrabFocus(); + } + else + { + vcl::Window* pFocusWin = ImplGetWindowImpl()->mpFrameData->mpFocusWin; + if ( pFocusWin ) + { + ImplSVData* pSVData = ImplGetSVData(); + + if (pSVData->mpWinData->mpFocusWin == pFocusWin) + { + // transfer the FocusWindow + vcl::Window* pOverlapWindow = pFocusWin->ImplGetFirstOverlapWindow(); + if ( pOverlapWindow && pOverlapWindow->ImplGetWindowImpl() ) + pOverlapWindow->ImplGetWindowImpl()->mpLastFocusWindow = pFocusWin; + pSVData->mpWinData->mpFocusWin = nullptr; + + if ( pFocusWin->ImplGetWindowImpl() && pFocusWin->ImplGetWindowImpl()->mpCursor ) + pFocusWin->ImplGetWindowImpl()->mpCursor->ImplHide(); + + // call the Deactivate + vcl::Window* pOldOverlapWindow = pFocusWin->ImplGetFirstOverlapWindow(); + vcl::Window* pOldRealWindow = pOldOverlapWindow->ImplGetWindow(); + + if (pOldOverlapWindow && pOldOverlapWindow->ImplGetWindowImpl() && + pOldRealWindow && pOldRealWindow->ImplGetWindowImpl()) + { + pOldOverlapWindow->ImplGetWindowImpl()->mbActive = false; + pOldOverlapWindow->Deactivate(); + if ( pOldRealWindow != pOldOverlapWindow ) + { + pOldRealWindow->ImplGetWindowImpl()->mbActive = false; + pOldRealWindow->Deactivate(); + } + } + + // TrackingMode is ended in ImplHandleLoseFocus +#ifdef _WIN32 + // To avoid problems with the Unix IME + pFocusWin->EndExtTextInput(); +#endif + + NotifyEvent aNEvt(MouseNotifyEvent::LOSEFOCUS, pFocusWin); + if (!ImplCallPreNotify(aNEvt)) + pFocusWin->CompatLoseFocus(); + pFocusWin->ImplCallDeactivateListeners(nullptr); + } + } + + // Redraw all floating window inactive + if ( ImplGetWindowImpl()->mpFrameData->mbStartFocusState != bHasFocus ) + ImplActivateFloatingWindows( this, bHasFocus ); + } +} + +static void ImplHandleGetFocus( vcl::Window* pWindow ) +{ + if (!pWindow || !pWindow->ImplGetWindowImpl() || !pWindow->ImplGetWindowImpl()->mpFrameData) + return; + + pWindow->ImplGetWindowImpl()->mpFrameData->mbHasFocus = true; + + // execute Focus-Events after a delay, such that SystemChildWindows + // do not blink when they receive focus + if ( !pWindow->ImplGetWindowImpl()->mpFrameData->mnFocusId ) + { + pWindow->ImplGetWindowImpl()->mpFrameData->mbStartFocusState = !pWindow->ImplGetWindowImpl()->mpFrameData->mbHasFocus; + pWindow->ImplGetWindowImpl()->mpFrameData->mnFocusId = Application::PostUserEvent( LINK( pWindow, vcl::Window, ImplAsyncFocusHdl ), nullptr, true); + vcl::Window* pFocusWin = pWindow->ImplGetWindowImpl()->mpFrameData->mpFocusWin; + if ( pFocusWin && pFocusWin->ImplGetWindowImpl()->mpCursor ) + pFocusWin->ImplGetWindowImpl()->mpCursor->ImplShow(); + } +} + +static void ImplHandleLoseFocus( vcl::Window* pWindow ) +{ + if (!pWindow) + return; + + ImplSVData* pSVData = ImplGetSVData(); + + // Abort the autoscroll if the frame loses focus + if (pSVData->mpWinData->mpAutoScrollWin) + pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll(); + + // Abort tracking if the frame loses focus + if (pSVData->mpWinData->mpTrackWin) + { + if (pSVData->mpWinData->mpTrackWin->ImplGetWindowImpl() && + pSVData->mpWinData->mpTrackWin->ImplGetWindowImpl()->mpFrameWindow == pWindow) + pSVData->mpWinData->mpTrackWin->EndTracking(TrackingEventFlags::Cancel); + } + + if (pWindow->ImplGetWindowImpl() && pWindow->ImplGetWindowImpl()->mpFrameData) + { + pWindow->ImplGetWindowImpl()->mpFrameData->mbHasFocus = false; + + // execute Focus-Events after a delay, such that SystemChildWindows + // do not flicker when they receive focus + if ( !pWindow->ImplGetWindowImpl()->mpFrameData->mnFocusId ) + { + pWindow->ImplGetWindowImpl()->mpFrameData->mbStartFocusState = !pWindow->ImplGetWindowImpl()->mpFrameData->mbHasFocus; + pWindow->ImplGetWindowImpl()->mpFrameData->mnFocusId = Application::PostUserEvent( LINK( pWindow, vcl::Window, ImplAsyncFocusHdl ), nullptr, true ); + } + + vcl::Window* pFocusWin = pWindow->ImplGetWindowImpl()->mpFrameData->mpFocusWin; + if ( pFocusWin && pFocusWin->ImplGetWindowImpl()->mpCursor ) + pFocusWin->ImplGetWindowImpl()->mpCursor->ImplHide(); + } + + // Make sure that no menu is visible when a toplevel window loses focus. + VclPtr<FloatingWindow> pFirstFloat = pSVData->mpWinData->mpFirstFloat; + if (pFirstFloat && pFirstFloat->IsMenuFloatingWindow() && !pWindow->GetParent()) + { + pFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll); + } +} + +namespace { + +struct DelayedCloseEvent +{ + VclPtr<vcl::Window> pWindow; +}; + +} + +static void DelayedCloseEventLink( void* pCEvent, void* ) +{ + DelayedCloseEvent* pEv = static_cast<DelayedCloseEvent*>(pCEvent); + + if( ! pEv->pWindow->isDisposed() ) + { + // dispatch to correct window type + if( pEv->pWindow->IsSystemWindow() ) + static_cast<SystemWindow*>(pEv->pWindow.get())->Close(); + else if( pEv->pWindow->IsDockingWindow() ) + static_cast<DockingWindow*>(pEv->pWindow.get())->Close(); + } + delete pEv; +} + +static void ImplHandleClose( const vcl::Window* pWindow ) +{ + ImplSVData* pSVData = ImplGetSVData(); + + bool bWasPopup = false; + if( pWindow->ImplIsFloatingWindow() && + static_cast<const FloatingWindow*>(pWindow)->ImplIsInPrivatePopupMode() ) + { + bWasPopup = true; + } + + // on Close stop all floating modes and end popups + if (pSVData->mpWinData->mpFirstFloat) + { + FloatingWindow* pLastLevelFloat; + pLastLevelFloat = pSVData->mpWinData->mpFirstFloat->ImplFindLastLevelFloat(); + pLastLevelFloat->EndPopupMode( FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll ); + } + if ( ImplGetSVHelpData().mbExtHelpMode ) + Help::EndExtHelp(); + if ( ImplGetSVHelpData().mpHelpWin ) + ImplDestroyHelpWindow( false ); + // AutoScrollMode + if (pSVData->mpWinData->mpAutoScrollWin) + pSVData->mpWinData->mpAutoScrollWin->EndAutoScroll(); + + if (pSVData->mpWinData->mpTrackWin) + pSVData->mpWinData->mpTrackWin->EndTracking( TrackingEventFlags::Cancel | TrackingEventFlags::Key ); + + if (bWasPopup) + return; + + vcl::Window *pWin = pWindow->ImplGetWindow(); + SystemWindow* pSysWin = dynamic_cast<SystemWindow*>(pWin); + if (pSysWin) + { + // See if the custom close handler is set. + const Link<SystemWindow&,void>& rLink = pSysWin->GetCloseHdl(); + if (rLink.IsSet()) + { + rLink.Call(*pSysWin); + return; + } + } + + // check whether close is allowed + if ( pWin->IsEnabled() && pWin->IsInputEnabled() && !pWin->IsInModalMode() ) + { + DelayedCloseEvent* pEv = new DelayedCloseEvent; + pEv->pWindow = pWin; + Application::PostUserEvent( Link<void*,void>( pEv, DelayedCloseEventLink ) ); + } +} + +static void ImplHandleUserEvent( ImplSVEvent* pSVEvent ) +{ + if ( pSVEvent ) + { + if ( pSVEvent->mbCall ) + { + pSVEvent->maLink.Call( pSVEvent->mpData ); + } + + delete pSVEvent; + } +} + +MouseEventModifiers ImplGetMouseMoveMode( SalMouseEvent const * pEvent ) +{ + MouseEventModifiers nMode = MouseEventModifiers::NONE; + if ( !pEvent->mnCode ) + nMode |= MouseEventModifiers::SIMPLEMOVE; + if ( (pEvent->mnCode & MOUSE_LEFT) && !(pEvent->mnCode & KEY_MOD1) ) + nMode |= MouseEventModifiers::DRAGMOVE; + if ( (pEvent->mnCode & MOUSE_LEFT) && (pEvent->mnCode & KEY_MOD1) ) + nMode |= MouseEventModifiers::DRAGCOPY; + return nMode; +} + +MouseEventModifiers ImplGetMouseButtonMode( SalMouseEvent const * pEvent ) +{ + MouseEventModifiers nMode = MouseEventModifiers::NONE; + if ( pEvent->mnButton == MOUSE_LEFT ) + nMode |= MouseEventModifiers::SIMPLECLICK; + if ( (pEvent->mnButton == MOUSE_LEFT) && !(pEvent->mnCode & (MOUSE_MIDDLE | MOUSE_RIGHT)) ) + nMode |= MouseEventModifiers::SELECT; + if ( (pEvent->mnButton == MOUSE_LEFT) && (pEvent->mnCode & KEY_MOD1) && + !(pEvent->mnCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_SHIFT)) ) + nMode |= MouseEventModifiers::MULTISELECT; + if ( (pEvent->mnButton == MOUSE_LEFT) && (pEvent->mnCode & KEY_SHIFT) && + !(pEvent->mnCode & (MOUSE_MIDDLE | MOUSE_RIGHT | KEY_MOD1)) ) + nMode |= MouseEventModifiers::RANGESELECT; + return nMode; +} + +static bool ImplHandleSalMouseLeave( vcl::Window* pWindow, SalMouseEvent const * pEvent ) +{ + return ImplHandleMouseEvent( pWindow, MouseNotifyEvent::MOUSEMOVE, true, + pEvent->mnX, pEvent->mnY, + pEvent->mnTime, pEvent->mnCode, + ImplGetMouseMoveMode( pEvent ) ); +} + +static bool ImplHandleSalMouseMove( vcl::Window* pWindow, SalMouseEvent const * pEvent ) +{ + return ImplHandleMouseEvent( pWindow, MouseNotifyEvent::MOUSEMOVE, false, + pEvent->mnX, pEvent->mnY, + pEvent->mnTime, pEvent->mnCode, + ImplGetMouseMoveMode( pEvent ) ); +} + +static bool ImplHandleSalMouseButtonDown( vcl::Window* pWindow, SalMouseEvent const * pEvent ) +{ + return ImplHandleMouseEvent( pWindow, MouseNotifyEvent::MOUSEBUTTONDOWN, false, + pEvent->mnX, pEvent->mnY, + pEvent->mnTime, +#ifdef MACOSX + pEvent->mnButton | (pEvent->mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)), +#else + pEvent->mnButton | (pEvent->mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)), +#endif + ImplGetMouseButtonMode( pEvent ) ); +} + +static bool ImplHandleSalMouseButtonUp( vcl::Window* pWindow, SalMouseEvent const * pEvent ) +{ + return ImplHandleMouseEvent( pWindow, MouseNotifyEvent::MOUSEBUTTONUP, false, + pEvent->mnX, pEvent->mnY, + pEvent->mnTime, +#ifdef MACOSX + pEvent->mnButton | (pEvent->mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3)), +#else + pEvent->mnButton | (pEvent->mnCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2)), +#endif + ImplGetMouseButtonMode( pEvent ) ); +} + +static bool ImplHandleMenuEvent( vcl::Window const * pWindow, SalMenuEvent* pEvent, SalEvent nEvent ) +{ + // Find SystemWindow and its Menubar and let it dispatch the command + bool bRet = false; + vcl::Window *pWin = pWindow->ImplGetWindowImpl()->mpFirstChild; + while ( pWin ) + { + if ( pWin->ImplGetWindowImpl()->mbSysWin ) + break; + pWin = pWin->ImplGetWindowImpl()->mpNext; + } + if( pWin ) + { + MenuBar *pMenuBar = static_cast<SystemWindow*>(pWin)->GetMenuBar(); + if( pMenuBar ) + { + switch( nEvent ) + { + case SalEvent::MenuActivate: + pMenuBar->HandleMenuActivateEvent( static_cast<Menu*>(pEvent->mpMenu) ); + bRet = true; + break; + case SalEvent::MenuDeactivate: + pMenuBar->HandleMenuDeActivateEvent( static_cast<Menu*>(pEvent->mpMenu) ); + bRet = true; + break; + case SalEvent::MenuHighlight: + bRet = pMenuBar->HandleMenuHighlightEvent( static_cast<Menu*>(pEvent->mpMenu), pEvent->mnId ); + break; + case SalEvent::MenuButtonCommand: + bRet = pMenuBar->HandleMenuButtonEvent( pEvent->mnId ); + break; + case SalEvent::MenuCommand: + bRet = pMenuBar->HandleMenuCommandEvent( static_cast<Menu*>(pEvent->mpMenu), pEvent->mnId ); + break; + default: + break; + } + } + } + return bRet; +} + +static void ImplHandleSalKeyMod( vcl::Window* pWindow, SalKeyModEvent const * pEvent ) +{ + ImplSVData* pSVData = ImplGetSVData(); + vcl::Window* pTrackWin = pSVData->mpWinData->mpTrackWin; + if ( pTrackWin ) + pWindow = pTrackWin; +#ifdef MACOSX + sal_uInt16 nOldCode = pWindow->ImplGetWindowImpl()->mpFrameData->mnMouseCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3); +#else + sal_uInt16 nOldCode = pWindow->ImplGetWindowImpl()->mpFrameData->mnMouseCode & (KEY_SHIFT | KEY_MOD1 | KEY_MOD2); +#endif + sal_uInt16 nNewCode = pEvent->mnCode; + if ( nOldCode != nNewCode ) + { +#ifdef MACOSX + nNewCode |= pWindow->ImplGetWindowImpl()->mpFrameData->mnMouseCode & ~(KEY_SHIFT | KEY_MOD1 | KEY_MOD2 | KEY_MOD3); +#else + nNewCode |= pWindow->ImplGetWindowImpl()->mpFrameData->mnMouseCode & ~(KEY_SHIFT | KEY_MOD1 | KEY_MOD2); +#endif + pWindow->ImplGetWindowImpl()->mpFrameWindow->ImplCallMouseMove( nNewCode, true ); + } + + // #105224# send commandevent to allow special treatment of Ctrl-LeftShift/Ctrl-RightShift etc. + // + auto-accelerator feature, tdf#92630 + + // try to find a key input window... + vcl::Window* pChild = ImplGetKeyInputWindow( pWindow ); + //...otherwise fail safe... + if (!pChild) + pChild = pWindow; + + CommandModKeyData data( pEvent->mnModKeyCode, pEvent->mbDown ); + ImplCallCommand( pChild, CommandEventId::ModKeyChange, &data ); +} + +static void ImplHandleInputLanguageChange( vcl::Window* pWindow ) +{ + // find window + vcl::Window* pChild = ImplGetKeyInputWindow( pWindow ); + if ( !pChild ) + return; + + ImplCallCommand( pChild, CommandEventId::InputLanguageChange ); +} + +static void ImplHandleSalSettings( SalEvent nEvent ) +{ + Application* pApp = GetpApp(); + if ( !pApp ) + return; + + if ( nEvent == SalEvent::SettingsChanged ) + { + AllSettings aSettings = Application::GetSettings(); + Application::MergeSystemSettings( aSettings ); + pApp->OverrideSystemSettings( aSettings ); + Application::SetSettings( aSettings ); + } + else + { + DataChangedEventType nType; + switch ( nEvent ) + { + case SalEvent::PrinterChanged: + ImplDeletePrnQueueList(); + nType = DataChangedEventType::PRINTER; + break; + case SalEvent::DisplayChanged: + nType = DataChangedEventType::DISPLAY; + break; + case SalEvent::FontChanged: + OutputDevice::ImplUpdateAllFontData( true ); + nType = DataChangedEventType::FONTS; + break; + default: + nType = DataChangedEventType::NONE; + break; + } + + if ( nType != DataChangedEventType::NONE ) + { + DataChangedEvent aDCEvt( nType ); + Application::ImplCallEventListenersApplicationDataChanged(&aDCEvt); + Application::NotifyAllWindows( aDCEvt ); + } + } +} + +static void ImplHandleSalExtTextInputPos( vcl::Window* pWindow, SalExtTextInputPosEvent* pEvt ) +{ + tools::Rectangle aCursorRect; + ImplHandleExtTextInputPos( pWindow, aCursorRect, pEvt->mnExtWidth, &pEvt->mbVertical ); + if ( aCursorRect.IsEmpty() ) + { + pEvt->mnX = -1; + pEvt->mnY = -1; + pEvt->mnWidth = -1; + pEvt->mnHeight = -1; + } + else + { + pEvt->mnX = aCursorRect.Left(); + pEvt->mnY = aCursorRect.Top(); + pEvt->mnWidth = aCursorRect.GetWidth(); + pEvt->mnHeight = aCursorRect.GetHeight(); + } +} + +static bool ImplHandleShowDialog( vcl::Window* pWindow, ShowDialogId nDialogId ) +{ + if( ! pWindow ) + return false; + + if( pWindow->GetType() == WindowType::BORDERWINDOW ) + { + vcl::Window* pWrkWin = pWindow->GetWindow( GetWindowType::Client ); + if( pWrkWin ) + pWindow = pWrkWin; + } + CommandDialogData aCmdData( nDialogId ); + return ImplCallCommand( pWindow, CommandEventId::ShowDialog, &aCmdData ); +} + +static void ImplHandleSurroundingTextRequest( vcl::Window *pWindow, + OUString& rText, + Selection &rSelRange ) +{ + vcl::Window* pChild = ImplGetKeyInputWindow( pWindow ); + + if ( !pChild ) + { + rText.clear(); + rSelRange.setMin( 0 ); + rSelRange.setMax( 0 ); + } + else + { + rText = pChild->GetSurroundingText(); + Selection aSel = pChild->GetSurroundingTextSelection(); + rSelRange.setMin( aSel.Min() ); + rSelRange.setMax( aSel.Max() ); + } +} + +static void ImplHandleSalSurroundingTextRequest( vcl::Window *pWindow, + SalSurroundingTextRequestEvent *pEvt ) +{ + Selection aSelRange; + ImplHandleSurroundingTextRequest( pWindow, pEvt->maText, aSelRange ); + + aSelRange.Justify(); + + if( aSelRange.Min() < 0 ) + pEvt->mnStart = 0; + else if( aSelRange.Min() > pEvt->maText.getLength() ) + pEvt->mnStart = pEvt->maText.getLength(); + else + pEvt->mnStart = aSelRange.Min(); + + if( aSelRange.Max() < 0 ) + pEvt->mnStart = 0; + else if( aSelRange.Max() > pEvt->maText.getLength() ) + pEvt->mnEnd = pEvt->maText.getLength(); + else + pEvt->mnEnd = aSelRange.Max(); +} + +static void ImplHandleSalDeleteSurroundingTextRequest( vcl::Window *pWindow, + SalSurroundingTextSelectionChangeEvent *pEvt ) +{ + vcl::Window* pChild = ImplGetKeyInputWindow( pWindow ); + + Selection aSelection(pEvt->mnStart, pEvt->mnEnd); + if (pChild && pChild->DeleteSurroundingText(aSelection)) + { + pEvt->mnStart = aSelection.Min(); + pEvt->mnEnd = aSelection.Max(); + } + else + { + pEvt->mnStart = pEvt->mnEnd = SAL_MAX_UINT32; + } +} + +static void ImplHandleSurroundingTextSelectionChange( vcl::Window *pWindow, + sal_uLong nStart, + sal_uLong nEnd ) +{ + vcl::Window* pChild = ImplGetKeyInputWindow( pWindow ); + if( pChild ) + { + CommandSelectionChangeData data( nStart, nEnd ); + ImplCallCommand( pChild, CommandEventId::SelectionChange, &data ); + } +} + +static void ImplHandleStartReconversion( vcl::Window *pWindow ) +{ + vcl::Window* pChild = ImplGetKeyInputWindow( pWindow ); + if( pChild ) + ImplCallCommand( pChild, CommandEventId::PrepareReconversion ); +} + +static void ImplHandleSalQueryCharPosition( vcl::Window *pWindow, + SalQueryCharPositionEvent *pEvt ) +{ + pEvt->mbValid = false; + pEvt->mbVertical = false; + pEvt->mnCursorBoundX = 0; + pEvt->mnCursorBoundY = 0; + pEvt->mnCursorBoundWidth = 0; + pEvt->mnCursorBoundHeight = 0; + + ImplSVData* pSVData = ImplGetSVData(); + vcl::Window* pChild = pSVData->mpWinData->mpExtTextInputWin; + + if ( !pChild ) + pChild = ImplGetKeyInputWindow( pWindow ); + else + { + // Test, if the Window is related to the frame + if ( !pWindow->ImplIsWindowOrChild( pChild ) ) + pChild = ImplGetKeyInputWindow( pWindow ); + } + + if( !pChild ) + return; + + ImplCallCommand( pChild, CommandEventId::QueryCharPosition ); + + ImplWinData* pWinData = pChild->ImplGetWinData(); + if ( !(pWinData->mpCompositionCharRects && pEvt->mnCharPos < o3tl::make_unsigned( pWinData->mnCompositionCharRects )) ) + return; + + const OutputDevice *pChildOutDev = pChild->GetOutDev(); + const tools::Rectangle& aRect = pWinData->mpCompositionCharRects[ pEvt->mnCharPos ]; + tools::Rectangle aDeviceRect = pChildOutDev->ImplLogicToDevicePixel( aRect ); + Point aAbsScreenPos = pChild->OutputToAbsoluteScreenPixel( pChild->ScreenToOutputPixel(aDeviceRect.TopLeft()) ); + pEvt->mnCursorBoundX = aAbsScreenPos.X(); + pEvt->mnCursorBoundY = aAbsScreenPos.Y(); + pEvt->mnCursorBoundWidth = aDeviceRect.GetWidth(); + pEvt->mnCursorBoundHeight = aDeviceRect.GetHeight(); + pEvt->mbVertical = pWinData->mbVertical; + pEvt->mbValid = true; +} + +bool ImplWindowFrameProc( vcl::Window* _pWindow, SalEvent nEvent, const void* pEvent ) +{ + DBG_TESTSOLARMUTEX(); + + // Ensure the window survives during this method. + VclPtr<vcl::Window> pWindow( _pWindow ); + + bool bRet = false; + + // #119709# for some unknown reason it is possible to receive events (in this case key events) + // although the corresponding VCL window must have been destroyed already + // at least ImplGetWindowImpl() was NULL in these cases, so check this here + if( pWindow->ImplGetWindowImpl() == nullptr ) + return false; + + switch ( nEvent ) + { + case SalEvent::MouseMove: + bRet = ImplHandleSalMouseMove( pWindow, static_cast<SalMouseEvent const *>(pEvent) ); + break; + case SalEvent::ExternalMouseMove: + { + MouseEvent const * pMouseEvt = static_cast<MouseEvent const *>(pEvent); + SalMouseEvent aSalMouseEvent; + + aSalMouseEvent.mnTime = tools::Time::GetSystemTicks(); + aSalMouseEvent.mnX = pMouseEvt->GetPosPixel().X(); + aSalMouseEvent.mnY = pMouseEvt->GetPosPixel().Y(); + aSalMouseEvent.mnButton = 0; + aSalMouseEvent.mnCode = pMouseEvt->GetButtons() | pMouseEvt->GetModifier(); + + bRet = ImplHandleSalMouseMove( pWindow, &aSalMouseEvent ); + } + break; + case SalEvent::MouseLeave: + bRet = ImplHandleSalMouseLeave( pWindow, static_cast<SalMouseEvent const *>(pEvent) ); + break; + case SalEvent::MouseButtonDown: + bRet = ImplHandleSalMouseButtonDown( pWindow, static_cast<SalMouseEvent const *>(pEvent) ); + break; + case SalEvent::ExternalMouseButtonDown: + { + MouseEvent const * pMouseEvt = static_cast<MouseEvent const *>(pEvent); + SalMouseEvent aSalMouseEvent; + + aSalMouseEvent.mnTime = tools::Time::GetSystemTicks(); + aSalMouseEvent.mnX = pMouseEvt->GetPosPixel().X(); + aSalMouseEvent.mnY = pMouseEvt->GetPosPixel().Y(); + aSalMouseEvent.mnButton = pMouseEvt->GetButtons(); + aSalMouseEvent.mnCode = pMouseEvt->GetButtons() | pMouseEvt->GetModifier(); + + bRet = ImplHandleSalMouseButtonDown( pWindow, &aSalMouseEvent ); + } + break; + case SalEvent::MouseButtonUp: + bRet = ImplHandleSalMouseButtonUp( pWindow, static_cast<SalMouseEvent const *>(pEvent) ); + break; + case SalEvent::ExternalMouseButtonUp: + { + MouseEvent const * pMouseEvt = static_cast<MouseEvent const *>(pEvent); + SalMouseEvent aSalMouseEvent; + + aSalMouseEvent.mnTime = tools::Time::GetSystemTicks(); + aSalMouseEvent.mnX = pMouseEvt->GetPosPixel().X(); + aSalMouseEvent.mnY = pMouseEvt->GetPosPixel().Y(); + aSalMouseEvent.mnButton = pMouseEvt->GetButtons(); + aSalMouseEvent.mnCode = pMouseEvt->GetButtons() | pMouseEvt->GetModifier(); + + bRet = ImplHandleSalMouseButtonUp( pWindow, &aSalMouseEvent ); + } + break; + case SalEvent::MouseActivate: + bRet = false; + break; + case SalEvent::KeyInput: + { + SalKeyEvent const * pKeyEvt = static_cast<SalKeyEvent const *>(pEvent); + bRet = ImplHandleKey( pWindow, MouseNotifyEvent::KEYINPUT, + pKeyEvt->mnCode, pKeyEvt->mnCharCode, pKeyEvt->mnRepeat, true ); + } + break; + case SalEvent::ExternalKeyInput: + { + KeyEvent const * pKeyEvt = static_cast<KeyEvent const *>(pEvent); + bRet = ImplHandleKey( pWindow, MouseNotifyEvent::KEYINPUT, + pKeyEvt->GetKeyCode().GetFullCode(), pKeyEvt->GetCharCode(), pKeyEvt->GetRepeat(), false ); + } + break; + case SalEvent::KeyUp: + { + SalKeyEvent const * pKeyEvt = static_cast<SalKeyEvent const *>(pEvent); + bRet = ImplHandleKey( pWindow, MouseNotifyEvent::KEYUP, + pKeyEvt->mnCode, pKeyEvt->mnCharCode, pKeyEvt->mnRepeat, true ); + } + break; + case SalEvent::ExternalKeyUp: + { + KeyEvent const * pKeyEvt = static_cast<KeyEvent const *>(pEvent); + bRet = ImplHandleKey( pWindow, MouseNotifyEvent::KEYUP, + pKeyEvt->GetKeyCode().GetFullCode(), pKeyEvt->GetCharCode(), pKeyEvt->GetRepeat(), false ); + } + break; + case SalEvent::KeyModChange: + ImplHandleSalKeyMod( pWindow, static_cast<SalKeyModEvent const *>(pEvent) ); + break; + + case SalEvent::InputLanguageChange: + ImplHandleInputLanguageChange( pWindow ); + break; + + case SalEvent::MenuActivate: + case SalEvent::MenuDeactivate: + case SalEvent::MenuHighlight: + case SalEvent::MenuCommand: + case SalEvent::MenuButtonCommand: + bRet = ImplHandleMenuEvent( pWindow, const_cast<SalMenuEvent *>(static_cast<SalMenuEvent const *>(pEvent)), nEvent ); + break; + + case SalEvent::WheelMouse: + bRet = ImplHandleWheelEvent( pWindow, *static_cast<const SalWheelMouseEvent*>(pEvent)); + break; + + case SalEvent::Paint: + { + SalPaintEvent const * pPaintEvt = static_cast<SalPaintEvent const *>(pEvent); + + if( AllSettings::GetLayoutRTL() ) + { + SalFrame* pSalFrame = pWindow->ImplGetWindowImpl()->mpFrame; + const_cast<SalPaintEvent *>(pPaintEvt)->mnBoundX = pSalFrame->maGeometry.nWidth-pPaintEvt->mnBoundWidth-pPaintEvt->mnBoundX; + } + + tools::Rectangle aBoundRect( Point( pPaintEvt->mnBoundX, pPaintEvt->mnBoundY ), + Size( pPaintEvt->mnBoundWidth, pPaintEvt->mnBoundHeight ) ); + ImplHandlePaint( pWindow, aBoundRect, pPaintEvt->mbImmediateUpdate ); + } + break; + + case SalEvent::Move: + ImplHandleMove( pWindow ); + break; + + case SalEvent::Resize: + { + tools::Long nNewWidth; + tools::Long nNewHeight; + pWindow->ImplGetWindowImpl()->mpFrame->GetClientSize( nNewWidth, nNewHeight ); + ImplHandleResize( pWindow, nNewWidth, nNewHeight ); + } + break; + + case SalEvent::MoveResize: + { + SalFrameGeometry g = pWindow->ImplGetWindowImpl()->mpFrame->GetGeometry(); + ImplHandleMoveResize( pWindow, g.nWidth, g.nHeight ); + } + break; + + case SalEvent::ClosePopups: + { + KillOwnPopups( pWindow ); + } + break; + + case SalEvent::GetFocus: + ImplHandleGetFocus( pWindow ); + break; + case SalEvent::LoseFocus: + ImplHandleLoseFocus( pWindow ); + break; + + case SalEvent::Close: + ImplHandleClose( pWindow ); + break; + + case SalEvent::Shutdown: + { + static bool bInQueryExit = false; + if( !bInQueryExit ) + { + bInQueryExit = true; + if ( GetpApp()->QueryExit() ) + { + // end the message loop + Application::Quit(); + return false; + } + else + { + bInQueryExit = false; + return true; + } + } + return false; + } + + case SalEvent::SettingsChanged: + case SalEvent::PrinterChanged: + case SalEvent::DisplayChanged: + case SalEvent::FontChanged: + ImplHandleSalSettings( nEvent ); + break; + + case SalEvent::UserEvent: + ImplHandleUserEvent( const_cast<ImplSVEvent *>(static_cast<ImplSVEvent const *>(pEvent)) ); + break; + + case SalEvent::ExtTextInput: + { + SalExtTextInputEvent const * pEvt = static_cast<SalExtTextInputEvent const *>(pEvent); + bRet = ImplHandleExtTextInput( pWindow, + pEvt->maText, pEvt->mpTextAttr, + pEvt->mnCursorPos, pEvt->mnCursorFlags ); + } + break; + case SalEvent::EndExtTextInput: + bRet = ImplHandleEndExtTextInput(); + break; + case SalEvent::ExtTextInputPos: + ImplHandleSalExtTextInputPos( pWindow, const_cast<SalExtTextInputPosEvent *>(static_cast<SalExtTextInputPosEvent const *>(pEvent)) ); + break; + case SalEvent::InputContextChange: + bRet = ImplHandleInputContextChange( pWindow ); + break; + case SalEvent::ShowDialog: + { + ShowDialogId nLOKWindowId = static_cast<ShowDialogId>(reinterpret_cast<sal_IntPtr>(pEvent)); + bRet = ImplHandleShowDialog( pWindow, nLOKWindowId ); + } + break; + case SalEvent::SurroundingTextRequest: + ImplHandleSalSurroundingTextRequest( pWindow, const_cast<SalSurroundingTextRequestEvent *>(static_cast<SalSurroundingTextRequestEvent const *>(pEvent)) ); + break; + case SalEvent::DeleteSurroundingTextRequest: + ImplHandleSalDeleteSurroundingTextRequest( pWindow, const_cast<SalSurroundingTextSelectionChangeEvent *>(static_cast<SalSurroundingTextSelectionChangeEvent const *>(pEvent)) ); + break; + case SalEvent::SurroundingTextSelectionChange: + { + SalSurroundingTextSelectionChangeEvent const * pEvt + = static_cast<SalSurroundingTextSelectionChangeEvent const *>(pEvent); + ImplHandleSurroundingTextSelectionChange( pWindow, + pEvt->mnStart, + pEvt->mnEnd ); + [[fallthrough]]; // TODO: Fallthrough really intended? + } + case SalEvent::StartReconversion: + ImplHandleStartReconversion( pWindow ); + break; + + case SalEvent::QueryCharPosition: + ImplHandleSalQueryCharPosition( pWindow, const_cast<SalQueryCharPositionEvent *>(static_cast<SalQueryCharPositionEvent const *>(pEvent)) ); + break; + + case SalEvent::Swipe: + bRet = ImplHandleSwipe(pWindow, *static_cast<const SalSwipeEvent*>(pEvent)); + break; + + case SalEvent::LongPress: + bRet = ImplHandleLongPress(pWindow, *static_cast<const SalLongPressEvent*>(pEvent)); + break; + + case SalEvent::ExternalGesture: + { + auto const * pGestureEvent = static_cast<GestureEvent const *>(pEvent); + + SalGestureEvent aSalGestureEvent; + aSalGestureEvent.mfOffset = pGestureEvent->mnOffset; + aSalGestureEvent.mnX = pGestureEvent->mnX; + aSalGestureEvent.mnY = pGestureEvent->mnY; + aSalGestureEvent.meEventType = pGestureEvent->meEventType; + aSalGestureEvent.meOrientation = pGestureEvent->meOrientation; + + bRet = ImplHandleGestureEvent(pWindow, aSalGestureEvent); + } + break; + case SalEvent::Gesture: + { + auto const * aSalGestureEvent = static_cast<SalGestureEvent const *>(pEvent); + bRet = ImplHandleGestureEvent(pWindow, *aSalGestureEvent); + } + break; + default: + SAL_WARN( "vcl.layout", "ImplWindowFrameProc(): unknown event (" << static_cast<int>(nEvent) << ")" ); + break; + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/window/wrkwin.cxx b/vcl/source/window/wrkwin.cxx new file mode 100644 index 000000000..6dfdd4c0d --- /dev/null +++ b/vcl/source/window/wrkwin.cxx @@ -0,0 +1,272 @@ +/* -*- 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 <vcl/svapp.hxx> +#include <vcl/wrkwin.hxx> +// declare system types in sysdata.hxx +#include <vcl/sysdata.hxx> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> + +#include <svdata.hxx> +#include <salframe.hxx> +#include <brdwin.hxx> +#include <window.h> + +void WorkWindow::ImplInitWorkWindowData() +{ + mnIcon = 0; // Should be removed in the next top level update - now in SystemWindow + + mnPresentationFlags = PresentationFlags::NONE; + mbPresentationMode = false; + mbPresentationVisible = false; + mbPresentationFull = false; + mbFullScreenMode = false; +} + +void WorkWindow::ImplInit( vcl::Window* pParent, WinBits nStyle, SystemParentData* pSystemParentData ) +{ + BorderWindowStyle nFrameStyle = BorderWindowStyle::Frame; + if ( nStyle & WB_APP ) + nFrameStyle |= BorderWindowStyle::App; + + VclPtrInstance<ImplBorderWindow> pBorderWin( pParent, pSystemParentData, nStyle, nFrameStyle ); + Window::ImplInit( pBorderWin, nStyle & (WB_3DLOOK | WB_CLIPCHILDREN | WB_DIALOGCONTROL | WB_SYSTEMFLOATWIN), nullptr ); + pBorderWin->mpWindowImpl->mpClientWindow = this; + pBorderWin->GetBorder( mpWindowImpl->mnLeftBorder, mpWindowImpl->mnTopBorder, mpWindowImpl->mnRightBorder, mpWindowImpl->mnBottomBorder ); + mpWindowImpl->mpBorderWindow = pBorderWin; + + // mpWindowImpl->mpRealParent = pParent; // should actually be set, but is not set due to errors with the menubar!! + + if ( nStyle & WB_APP ) + { + ImplSVData* pSVData = ImplGetSVData(); + SAL_WARN_IF(pSVData->maFrameData.mpAppWin, "vcl", + "WorkWindow::WorkWindow(): More than one window with style WB_APP"); + pSVData->maFrameData.mpAppWin = this; + } + + SetActivateMode( ActivateModeFlags::GrabFocus ); +} + +void WorkWindow::ImplInit( vcl::Window* pParent, WinBits nStyle, const css::uno::Any& aSystemWorkWindowToken ) +{ + if( aSystemWorkWindowToken.hasValue() ) + { + css::uno::Sequence< sal_Int8 > aSeq; + aSystemWorkWindowToken >>= aSeq; + SystemParentData* pData = reinterpret_cast<SystemParentData*>(aSeq.getArray()); + SAL_WARN_IF( aSeq.getLength() != sizeof( SystemParentData ) || pData->nSize != sizeof( SystemParentData ), "vcl", "WorkWindow::WorkWindow( vcl::Window*, const Any&, WinBits ) called with invalid Any" ); + // init with style 0 as does WorkWindow::WorkWindow( SystemParentData* ); + ImplInit( pParent, 0, pData ); + } + else + ImplInit( pParent, nStyle ); +} + +WorkWindow::WorkWindow( WindowType nType ) : + SystemWindow( nType, "vcl::WorkWindow maLayoutIdle" ) +{ + ImplInitWorkWindowData(); +} + +WorkWindow::WorkWindow( vcl::Window* pParent, WinBits nStyle ) : + SystemWindow( WindowType::WORKWINDOW, "vcl::WorkWindow maLayoutIdle" ) +{ + ImplInitWorkWindowData(); + ImplInit( pParent, nStyle ); +} + +WorkWindow::WorkWindow( vcl::Window* pParent, const css::uno::Any& aSystemWorkWindowToken, WinBits nStyle ) : + SystemWindow( WindowType::WORKWINDOW, "vcl::WorkWindow maLayoutIdle" ) +{ + ImplInitWorkWindowData(); + mbSysChild = true; + ImplInit( pParent, nStyle, aSystemWorkWindowToken ); +} + +WorkWindow::WorkWindow( SystemParentData* pParent ) : + SystemWindow( WindowType::WORKWINDOW, "vcl::WorkWindow maLayoutIdle" ) +{ + ImplInitWorkWindowData(); + mbSysChild = true; + ImplInit( nullptr, 0, pParent ); +} + +WorkWindow::~WorkWindow() +{ + disposeOnce(); +} + +void WorkWindow::dispose() +{ + ImplSVData* pSVData = ImplGetSVData(); + if (pSVData->maFrameData.mpAppWin == this) + { + pSVData->maFrameData.mpAppWin = nullptr; + Application::Quit(); + } + SystemWindow::dispose(); +} + +void WorkWindow::ShowFullScreenMode( bool bFullScreenMode ) +{ + return ShowFullScreenMode( bFullScreenMode, GetScreenNumber()); +} + +void WorkWindow::ShowFullScreenMode( bool bFullScreenMode, sal_Int32 nDisplayScreen ) +{ + if ( !mbFullScreenMode == !bFullScreenMode ) + return; + + mbFullScreenMode = bFullScreenMode; + if ( mbSysChild ) + return; + + // Dispose of the canvas implementation, which might rely on + // screen-specific system data. + GetOutDev()->ImplDisposeCanvas(); + + mpWindowImpl->mpFrameWindow->mpWindowImpl->mbWaitSystemResize = true; + ImplGetFrame()->ShowFullScreen( bFullScreenMode, nDisplayScreen ); +} + +void WorkWindow::StartPresentationMode( PresentationFlags nFlags ) +{ + return StartPresentationMode( false/*bPresentation*/, nFlags, GetScreenNumber()); +} + +void WorkWindow::StartPresentationMode( bool bPresentation, PresentationFlags nFlags, sal_Int32 nDisplayScreen ) +{ + if ( !bPresentation == !mbPresentationMode ) + return; + + if ( bPresentation ) + { + mbPresentationMode = true; + mbPresentationVisible = IsVisible(); + mbPresentationFull = mbFullScreenMode; + mnPresentationFlags = nFlags; + + ShowFullScreenMode( true, nDisplayScreen ); + if ( !mbSysChild ) + { + if ( mnPresentationFlags & PresentationFlags::HideAllApps ) + mpWindowImpl->mpFrame->SetAlwaysOnTop( true ); + ToTop(); + mpWindowImpl->mpFrame->StartPresentation( true ); + } + + Show(); + } + else + { + Show( mbPresentationVisible ); + if ( !mbSysChild ) + { + mpWindowImpl->mpFrame->StartPresentation( false ); + if ( mnPresentationFlags & PresentationFlags::HideAllApps ) + mpWindowImpl->mpFrame->SetAlwaysOnTop( false ); + } + ShowFullScreenMode( mbPresentationFull, nDisplayScreen ); + + mbPresentationMode = false; + mbPresentationVisible = false; + mbPresentationFull = false; + mnPresentationFlags = PresentationFlags::NONE; + } +} + +bool WorkWindow::IsMinimized() const +{ + //return mpWindowImpl->mpFrameData->mbMinimized; + SalFrameState aState; + if (mpWindowImpl->mpFrame->GetWindowState(&aState)) + return bool(aState.mnState & WindowStateState::Minimized); + else + return false; +} + +void WorkWindow::SetPluginParent( SystemParentData* pParent ) +{ + SAL_WARN_IF( mbPresentationMode || mbFullScreenMode, "vcl", "SetPluginParent in fullscreen or presentation mode !" ); + + bool bWasDnd = Window::ImplStopDnd(); + + bool bShown = IsVisible(); + Show( false ); + mpWindowImpl->mpFrame->SetPluginParent( pParent ); + Show( bShown ); + + if( bWasDnd ) + Window::ImplStartDnd(); +} + +void WorkWindow::ImplSetFrameState( WindowStateState aFrameState ) +{ + SalFrameState aState; + aState.mnMask = WindowStateMask::State; + aState.mnState = aFrameState; + mpWindowImpl->mpFrame->SetWindowState( &aState ); +} + +void WorkWindow::Minimize() +{ + ImplSetFrameState( WindowStateState::Minimized ); +} + +void WorkWindow::Restore() +{ + ImplSetFrameState( WindowStateState::Normal ); +} + +bool WorkWindow::Close() +{ + bool bCanClose = SystemWindow::Close(); + + // if it's the application window then close the application + if (bCanClose && (ImplGetSVData()->maFrameData.mpAppWin == this)) + Application::Quit(); + + return bCanClose; +} + +void WorkWindow::Maximize( bool bMaximize ) +{ + ImplSetFrameState( bMaximize ? WindowStateState::Maximized : WindowStateState::Normal ); +} + +bool WorkWindow::IsMaximized() const +{ + bool bRet = false; + + SalFrameState aState; + if( mpWindowImpl->mpFrame->GetWindowState( &aState ) ) + { + if( aState.mnState & (WindowStateState::Maximized | + WindowStateState::MaximizedHorz | + WindowStateState::MaximizedVert ) ) + bRet = true; + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |