summaryrefslogtreecommitdiffstats
path: root/vcl/source/control
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/source/control')
-rw-r--r--vcl/source/control/ContextVBox.cxx62
-rw-r--r--vcl/source/control/DropdownBox.cxx115
-rw-r--r--vcl/source/control/InterimItemWindow.cxx208
-rw-r--r--vcl/source/control/NotebookbarPopup.cxx161
-rw-r--r--vcl/source/control/PriorityHBox.cxx197
-rw-r--r--vcl/source/control/PriorityMergedHBox.cxx199
-rw-r--r--vcl/source/control/WeldedTabbedNotebookbar.cxx23
-rw-r--r--vcl/source/control/button.cxx3842
-rw-r--r--vcl/source/control/calendar.cxx1747
-rw-r--r--vcl/source/control/combobox.cxx1608
-rw-r--r--vcl/source/control/ctrl.cxx513
-rw-r--r--vcl/source/control/edit.cxx2937
-rw-r--r--vcl/source/control/field.cxx1882
-rw-r--r--vcl/source/control/field2.cxx3188
-rw-r--r--vcl/source/control/fixed.cxx995
-rw-r--r--vcl/source/control/fixedhyper.cxx186
-rw-r--r--vcl/source/control/fmtfield.cxx1366
-rw-r--r--vcl/source/control/hyperlabel.cxx176
-rw-r--r--vcl/source/control/imgctrl.cxx184
-rw-r--r--vcl/source/control/imivctl.hxx510
-rw-r--r--vcl/source/control/imivctl1.cxx2927
-rw-r--r--vcl/source/control/imivctl2.cxx715
-rw-r--r--vcl/source/control/imp_listbox.cxx3029
-rw-r--r--vcl/source/control/ivctrl.cxx679
-rw-r--r--vcl/source/control/listbox.cxx1457
-rw-r--r--vcl/source/control/longcurr.cxx429
-rw-r--r--vcl/source/control/managedmenubutton.cxx100
-rw-r--r--vcl/source/control/menubtn.cxx309
-rw-r--r--vcl/source/control/notebookbar.cxx375
-rw-r--r--vcl/source/control/prgsbar.cxx239
-rw-r--r--vcl/source/control/quickselectionengine.cxx158
-rw-r--r--vcl/source/control/roadmap.cxx845
-rw-r--r--vcl/source/control/roadmapwizard.cxx837
-rw-r--r--vcl/source/control/scrbar.cxx1473
-rw-r--r--vcl/source/control/slider.cxx906
-rw-r--r--vcl/source/control/spinbtn.cxx468
-rw-r--r--vcl/source/control/spinfld.cxx1028
-rw-r--r--vcl/source/control/tabctrl.cxx2402
-rw-r--r--vcl/source/control/throbber.cxx234
-rw-r--r--vcl/source/control/thumbpos.hxx21
-rw-r--r--vcl/source/control/wizardmachine.cxx1436
-rw-r--r--vcl/source/control/wizimpldata.hxx108
42 files changed, 40274 insertions, 0 deletions
diff --git a/vcl/source/control/ContextVBox.cxx b/vcl/source/control/ContextVBox.cxx
new file mode 100644
index 0000000000..94090d5cd6
--- /dev/null
+++ b/vcl/source/control/ContextVBox.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 <sal/config.h>
+
+#include <vcl/NotebookbarContextControl.hxx>
+#include <ContextVBox.hxx>
+
+ContextVBox::ContextVBox( vcl::Window *pParent )
+ : VclVBox( pParent )
+{
+}
+
+ContextVBox::~ContextVBox()
+{
+ disposeOnce();
+}
+
+void ContextVBox::SetContext( vcl::EnumContext::Context eContext )
+{
+ for (int nChild = 0; nChild < GetChildCount(); ++nChild)
+ {
+ if ( GetChild( nChild )->GetType() == WindowType::CONTAINER )
+ {
+ VclContainer* pChild = static_cast<VclContainer*>( GetChild( nChild ) );
+
+ if ( pChild->HasContext( eContext ) || pChild->HasContext( vcl::EnumContext::Context::Any ) )
+ {
+ Size aSize( pChild->GetOptimalSize() );
+ aSize.AdjustHeight(6 );
+ pChild->Show();
+ pChild->SetSizePixel( aSize );
+ }
+ else
+ {
+ pChild->Hide();
+ pChild->SetSizePixel( Size( 0, 0 ) );
+ }
+ }
+ }
+ Size aSize( GetOptimalSize() );
+ aSize.AdjustWidth(6 );
+ SetSizePixel( aSize );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/DropdownBox.cxx b/vcl/source/control/DropdownBox.cxx
new file mode 100644
index 0000000000..6aaf2e553a
--- /dev/null
+++ b/vcl/source/control/DropdownBox.cxx
@@ -0,0 +1,115 @@
+/* -*- 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/button.hxx>
+#include <vcl/layout.hxx>
+#include <DropdownBox.hxx>
+
+#define NOTEBOOK_HEADER_HEIGHT 30
+
+/*
+ * DropdownBox - shows content or moves it to the popup
+ * which can be opened by clicking on a button
+ */
+
+DropdownBox::DropdownBox(vcl::Window* pParent)
+ : VclHBox(pParent)
+ , m_bInFullView(true)
+{
+ m_pButton = VclPtr<PushButton>::Create(this, WB_FLATBUTTON);
+ m_pButton->SetClickHdl(LINK(this, DropdownBox, PBClickHdl));
+ m_pButton->SetSymbol(SymbolType::MENU);
+ m_pButton->set_width_request(15);
+ m_pButton->SetQuickHelpText(GetQuickHelpText());
+ m_pButton->Resize();
+}
+
+DropdownBox::~DropdownBox() { disposeOnce(); }
+
+void DropdownBox::dispose()
+{
+ m_pButton.disposeAndClear();
+ if (m_pPopup)
+ m_pPopup.disposeAndClear();
+
+ VclHBox::dispose();
+}
+
+void DropdownBox::HideContent()
+{
+ if (m_bInFullView)
+ {
+ m_bInFullView = false;
+
+ for (int i = 0; i < GetChildCount(); i++)
+ GetChild(i)->Hide();
+
+ m_pButton->Show();
+ SetOutputSizePixel(Size(m_pButton->GetSizePixel().Width(), GetSizePixel().Height()));
+ }
+}
+
+bool DropdownBox::IsHidden() { return !m_bInFullView; }
+
+void DropdownBox::ShowContent()
+{
+ if (!m_bInFullView)
+ {
+ m_bInFullView = true;
+
+ for (int i = 0; i < GetChildCount(); i++)
+ GetChild(i)->Show();
+
+ m_pButton->Hide();
+ }
+}
+
+IMPL_LINK(DropdownBox, PBClickHdl, Button*, /*pButton*/, void)
+{
+ if (m_pPopup)
+ m_pPopup.disposeAndClear();
+
+ m_pPopup = VclPtr<NotebookbarPopup>::Create(this);
+
+ for (int i = 0; i < GetChildCount(); i++)
+ {
+ if (GetChild(i) != m_pButton)
+ {
+ Window* pChild = GetChild(i);
+ pChild->Show();
+
+ pChild->SetParent(m_pPopup->getBox());
+ // count is decreased because we moved child
+ i--;
+ }
+ }
+
+ m_pPopup->hideSeparators(true);
+
+ m_pPopup->getBox()->set_height_request(GetSizePixel().Height());
+
+ tools::Long x = GetPosPixel().getX();
+ tools::Long y = GetPosPixel().getY() + NOTEBOOK_HEADER_HEIGHT + GetSizePixel().Height();
+ tools::Rectangle aRect(x, y, x, y);
+
+ m_pPopup->StartPopupMode(aRect, FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus
+ | FloatWinPopupFlags::AllMouseButtonClose);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/InterimItemWindow.cxx b/vcl/source/control/InterimItemWindow.cxx
new file mode 100644
index 0000000000..0769245eb4
--- /dev/null
+++ b/vcl/source/control/InterimItemWindow.cxx
@@ -0,0 +1,208 @@
+/* -*- 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/.
+ */
+
+#include <vcl/InterimItemWindow.hxx>
+#include <vcl/layout.hxx>
+#include <salobj.hxx>
+#include <window.h>
+
+InterimItemWindow::InterimItemWindow(vcl::Window* pParent, const OUString& rUIXMLDescription,
+ const OUString& rID, bool bAllowCycleFocusOut,
+ sal_uInt64 nLOKWindowId)
+ : Control(pParent, WB_TABSTOP)
+ , m_pWidget(nullptr) // inheritors are expected to call InitControlBase
+ , m_aLayoutIdle("InterimItemWindow m_aLayoutIdle")
+{
+ m_aLayoutIdle.SetPriority(TaskPriority::RESIZE);
+ m_aLayoutIdle.SetInvokeHandler(LINK(this, InterimItemWindow, DoLayout));
+
+ m_xVclContentArea = VclPtr<VclVBox>::Create(this);
+ m_xVclContentArea->Show();
+ m_xBuilder = Application::CreateInterimBuilder(m_xVclContentArea, rUIXMLDescription,
+ bAllowCycleFocusOut, nLOKWindowId);
+ m_xContainer = m_xBuilder->weld_container(rID);
+
+ SetBackground();
+ SetPaintTransparent(true);
+}
+
+void InterimItemWindow::StateChanged(StateChangedType nStateChange)
+{
+ if (nStateChange == StateChangedType::Enable)
+ m_xContainer->set_sensitive(IsEnabled());
+ Control::StateChanged(nStateChange);
+}
+
+InterimItemWindow::~InterimItemWindow() { disposeOnce(); }
+
+void InterimItemWindow::dispose()
+{
+ m_pWidget = nullptr;
+
+ m_xContainer.reset();
+ m_xBuilder.reset();
+ m_xVclContentArea.disposeAndClear();
+
+ m_aLayoutIdle.Stop();
+
+ Control::dispose();
+}
+
+void InterimItemWindow::StartIdleLayout()
+{
+ if (!m_xVclContentArea)
+ return;
+ if (m_aLayoutIdle.IsActive())
+ return;
+ m_aLayoutIdle.Start();
+}
+
+void InterimItemWindow::queue_resize(StateChangedType eReason)
+{
+ Control::queue_resize(eReason);
+ StartIdleLayout();
+}
+
+void InterimItemWindow::Resize() { Layout(); }
+
+void InterimItemWindow::UnclipVisibleSysObj()
+{
+ if (!IsVisible())
+ return;
+ vcl::Window* pChild = m_xVclContentArea->GetWindow(GetWindowType::FirstChild);
+ if (!pChild)
+ return;
+ WindowImpl* pWindowImpl = pChild->ImplGetWindowImpl();
+ if (!pWindowImpl)
+ return;
+ if (!pWindowImpl->mpSysObj)
+ return;
+ pWindowImpl->mpSysObj->Show(true);
+ pWindowImpl->mpSysObj->ResetClipRegion();
+ // flag that sysobj clip is dirty and needs to be recalculated on next use
+ pWindowImpl->mbInitWinClipRegion = true;
+}
+
+IMPL_LINK_NOARG(InterimItemWindow, DoLayout, Timer*, void) { Layout(); }
+
+void InterimItemWindow::Layout()
+{
+ m_aLayoutIdle.Stop();
+ vcl::Window* pChild = GetWindow(GetWindowType::FirstChild);
+ assert(pChild);
+ VclContainer::setLayoutAllocation(*pChild, Point(0, 0), GetSizePixel());
+ Control::Resize();
+}
+
+Size InterimItemWindow::GetOptimalSize() const
+{
+ return VclContainer::getLayoutRequisition(*GetWindow(GetWindowType::FirstChild));
+}
+
+void InterimItemWindow::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();
+}
+
+bool InterimItemWindow::ControlHasFocus() const
+{
+ if (!m_pWidget)
+ return false;
+ return m_pWidget->has_focus();
+}
+
+void InterimItemWindow::InitControlBase(weld::Widget* pWidget) { m_pWidget = pWidget; }
+
+void InterimItemWindow::GetFocus()
+{
+ if (m_pWidget)
+ m_pWidget->grab_focus();
+
+ /* let toolbox know this item window has focus so it updates its mnHighItemId to point
+ to this toolitem in case tab means to move to another toolitem within
+ the toolbox
+ */
+ vcl::Window* pToolBox = GetParent();
+ NotifyEvent aNEvt(NotifyEventType::GETFOCUS, this);
+ pToolBox->EventNotify(aNEvt);
+}
+
+bool InterimItemWindow::ChildKeyInput(const KeyEvent& rKEvt)
+{
+ sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
+ if (nCode != KEY_TAB)
+ return false;
+
+ /* if the native widget has focus, then no vcl window has focus.
+
+ We want to grab focus to this vcl widget so that pressing tab will traverse
+ to the next vcl widget.
+
+ But just using GrabFocus will, because no vcl widget has focus, trigger
+ bringing the toplevel to front with the expectation that a suitable widget
+ will be picked for focus when that happen, which is no use to us here.
+
+ SetFakeFocus avoids the problem, allowing GrabFocus to do the expected thing
+ then sending the Tab to our parent will do the right traversal
+ */
+ SetFakeFocus(true);
+ GrabFocus();
+
+ /* now give focus to our toolbox parent */
+ vcl::Window* pToolBox = GetParent();
+ pToolBox->GrabFocus();
+
+ /* let toolbox know this item window has focus so it updates its mnHighItemId to point
+ to this toolitem in case tab means to move to another toolitem within
+ the toolbox
+ */
+ NotifyEvent aNEvt(NotifyEventType::GETFOCUS, this);
+ pToolBox->EventNotify(aNEvt);
+
+ /* send parent the tab */
+ pToolBox->KeyInput(rKEvt);
+
+ return true;
+}
+
+void InterimItemWindow::Draw(OutputDevice* pDevice, const Point& rPos,
+ SystemTextColorFlags /*nFlags*/)
+{
+ m_xContainer->draw(*pDevice, rPos, GetSizePixel());
+}
+
+void InterimItemWindow::SetPriority(TaskPriority nPriority)
+{
+ // Eliminate warning when changing timer's priority
+ // Task::SetPriority() expects the timer to be stopped while
+ // changing the timer's priority.
+ bool bActive = m_aLayoutIdle.IsActive();
+ if (bActive)
+ m_aLayoutIdle.Stop();
+ m_aLayoutIdle.SetPriority(nPriority);
+ if (bActive)
+ m_aLayoutIdle.Start();
+}
+
+void InterimItemWindow::ImplPaintToDevice(::OutputDevice* pTargetOutDev, const Point& rPos)
+{
+ Draw(pTargetOutDev, rPos, SystemTextColorFlags::NONE);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/control/NotebookbarPopup.cxx b/vcl/source/control/NotebookbarPopup.cxx
new file mode 100644
index 0000000000..3cc21815ab
--- /dev/null
+++ b/vcl/source/control/NotebookbarPopup.cxx
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <vcl/bitmapex.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/layout.hxx>
+#include <IPrioritable.hxx>
+#include <NotebookbarPopup.hxx>
+
+NotebookbarPopup::NotebookbarPopup(const VclPtr<VclHBox>& pParent)
+ : FloatingWindow(pParent, "Popup", "sfx/ui/notebookbarpopup.ui")
+ , m_pParent(pParent)
+{
+ m_pUIBuilder->get(m_pBox, "box");
+ m_pBox->SetSizePixel(Size(100, 75));
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ const BitmapEx& aPersona = rStyleSettings.GetPersonaHeader();
+
+ if (!aPersona.IsEmpty())
+ m_pBox->SetBackground(Wallpaper(aPersona));
+ else
+ m_pBox->SetBackground(rStyleSettings.GetDialogColor());
+}
+
+NotebookbarPopup::~NotebookbarPopup() { disposeOnce(); }
+
+VclHBox* NotebookbarPopup::getBox() { return m_pBox.get(); }
+
+void NotebookbarPopup::PopupModeEnd()
+{
+ hideSeparators(false);
+ while (m_pBox->GetChildCount())
+ {
+ vcl::IPrioritable* pChild = dynamic_cast<vcl::IPrioritable*>(GetChild(0));
+ if (pChild)
+ pChild->HideContent();
+
+ vcl::Window* pWindow = m_pBox->GetChild(0);
+ pWindow->SetParent(m_pParent);
+
+ // resize after all children of box are empty
+ if (m_pParent && !m_pBox->GetChildCount())
+ m_pParent->Resize();
+ }
+
+ FloatingWindow::PopupModeEnd();
+}
+
+void NotebookbarPopup::hideSeparators(bool bHide)
+{
+ // separator on the beginning
+ vcl::Window* pWindow = m_pBox->GetChild(0);
+ while (pWindow && pWindow->GetType() == WindowType::CONTAINER)
+ {
+ pWindow = pWindow->GetChild(0);
+ }
+ if (pWindow && pWindow->GetType() == WindowType::FIXEDLINE)
+ {
+ if (bHide)
+ pWindow->Hide();
+ else
+ pWindow->Show();
+ }
+
+ // separator on the end
+ pWindow = m_pBox->GetChild(m_pBox->GetChildCount() - 1);
+ while (pWindow && pWindow->GetType() == WindowType::CONTAINER)
+ {
+ pWindow = pWindow->GetChild(pWindow->GetChildCount() - 1);
+ }
+ if (pWindow && pWindow->GetType() == WindowType::FIXEDLINE)
+ {
+ if (bHide)
+ pWindow->Hide();
+ else
+ pWindow->Show();
+ }
+
+ if (bHide)
+ {
+ sal_Int32 BoxId = 0;
+ while (BoxId <= m_pBox->GetChildCount() - 1)
+ {
+ if (m_pBox->GetChild(BoxId))
+ {
+ pWindow = m_pBox->GetChild(BoxId);
+ ApplyBackground(pWindow);
+ }
+ BoxId++;
+ }
+ }
+ else
+ {
+ sal_Int32 BoxId = m_pBox->GetChildCount() - 1;
+ while (BoxId >= 0)
+ {
+ if (m_pBox->GetChild(BoxId))
+ {
+ pWindow = m_pBox->GetChild(BoxId);
+ RemoveBackground(pWindow);
+ }
+ BoxId--;
+ }
+ }
+}
+
+void NotebookbarPopup::dispose()
+{
+ PopupModeEnd();
+ m_pBox.disposeAndClear();
+ m_pParent.clear();
+
+ FloatingWindow::dispose();
+}
+
+void NotebookbarPopup::ApplyBackground(vcl::Window* pWindow)
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ const BitmapEx& aPersona = rStyleSettings.GetPersonaHeader();
+
+ if (!aPersona.IsEmpty())
+ pWindow->SetBackground(Wallpaper(aPersona));
+ else
+ pWindow->SetBackground(rStyleSettings.GetDialogColor());
+
+ sal_Int32 nNext = 0;
+ VclPtr<vcl::Window> pChild = pWindow->GetChild(nNext);
+ while (pChild && pWindow->GetType() == WindowType::CONTAINER)
+ {
+ ApplyBackground(pChild);
+ nNext++;
+ if (pWindow->GetChild(nNext) && pWindow->GetType() == WindowType::CONTAINER)
+ pChild = pWindow->GetChild(nNext);
+ else
+ break;
+ }
+}
+
+void NotebookbarPopup::RemoveBackground(vcl::Window* pWindow)
+{
+ pWindow->SetBackground(Wallpaper(COL_TRANSPARENT));
+
+ sal_Int32 nNext = 0;
+ VclPtr<vcl::Window> pChild = pWindow->GetChild(nNext);
+ while (pChild && pWindow->GetType() == WindowType::CONTAINER)
+ {
+ RemoveBackground(pChild);
+ nNext++;
+ if (pWindow->GetChild(nNext) && pWindow->GetType() == WindowType::CONTAINER)
+ pChild = pWindow->GetChild(nNext);
+ else
+ break;
+ }
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/PriorityHBox.cxx b/vcl/source/control/PriorityHBox.cxx
new file mode 100644
index 0000000000..c893cc8332
--- /dev/null
+++ b/vcl/source/control/PriorityHBox.cxx
@@ -0,0 +1,197 @@
+/* -*- 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 <PriorityHBox.hxx>
+#include <comphelper/lok.hxx>
+
+namespace
+{
+bool lcl_comparePriority(const vcl::IPrioritable* a, const vcl::IPrioritable* b)
+{
+ return a->GetPriority() < b->GetPriority();
+}
+}
+
+PriorityHBox::PriorityHBox(vcl::Window* pParent)
+ : VclHBox(pParent)
+ , m_bInitialized(false)
+{
+}
+
+PriorityHBox::~PriorityHBox() { disposeOnce(); }
+
+void PriorityHBox::Initialize()
+{
+ m_bInitialized = true;
+
+ GetChildrenWithPriorities();
+ SetSizeFromParent();
+}
+
+int PriorityHBox::GetHiddenCount() const
+{
+ int nCount = 0;
+
+ for (auto pWindow : m_aSortedChildren)
+ if (pWindow->IsHidden())
+ nCount++;
+
+ return nCount;
+}
+
+void PriorityHBox::SetSizeFromParent()
+{
+ vcl::Window* pParent = GetParent();
+ if (pParent)
+ {
+ Size aParentSize = pParent->GetSizePixel();
+ SetSizePixel(Size(aParentSize.getWidth(), aParentSize.getHeight()));
+ }
+}
+
+Size PriorityHBox::calculateRequisition() const
+{
+ if (!m_bInitialized)
+ {
+ return VclHBox::calculateRequisition();
+ }
+
+ 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);
+
+ bool bAlwaysExpanded = true;
+
+ vcl::IPrioritable* pPrioritable = dynamic_cast<vcl::IPrioritable*>(pChild);
+ if (pPrioritable && pPrioritable->GetPriority() != VCL_PRIORITY_DEFAULT)
+ bAlwaysExpanded = false;
+
+ if (bAlwaysExpanded)
+ {
+ tools::Long nPrimaryDimension = getPrimaryDimension(aChildSize);
+ nPrimaryDimension += pChild->get_padding() * 2;
+ setPrimaryDimension(aChildSize, nPrimaryDimension);
+ }
+ else
+ setPrimaryDimension(aChildSize, 0);
+
+ accumulateMaxes(aChildSize, aSize);
+ }
+
+ return finalizeMaxes(aSize, nVisibleChildren);
+}
+
+void PriorityHBox::Resize()
+{
+ if (!m_bInitialized)
+ Initialize();
+
+ if (!m_bInitialized || comphelper::LibreOfficeKit::isActive())
+ {
+ return VclHBox::Resize();
+ }
+
+ tools::Long nWidth = GetSizePixel().Width();
+ tools::Long nCurrentWidth = VclHBox::calculateRequisition().getWidth();
+
+ // Hide lower priority controls
+ for (vcl::IPrioritable* pPrioritable : m_aSortedChildren)
+ {
+ if (nCurrentWidth <= nWidth)
+ break;
+
+ vcl::Window* pWindow = dynamic_cast<vcl::Window*>(pPrioritable);
+
+ if (pWindow && pWindow->GetParent() == this)
+ {
+ nCurrentWidth -= pWindow->GetOutputSizePixel().Width() + get_spacing();
+ pWindow->Show();
+ pPrioritable->HideContent();
+ nCurrentWidth += pWindow->GetOutputSizePixel().Width() + get_spacing();
+ }
+ }
+
+ auto pChildR = m_aSortedChildren.rbegin();
+ // Show higher priority controls if we already have enough space
+ while (pChildR != m_aSortedChildren.rend())
+ {
+ vcl::Window* pWindow = dynamic_cast<vcl::Window*>(*pChildR);
+ vcl::IPrioritable* pPrioritable = *pChildR;
+
+ if (pWindow->GetParent() != this)
+ {
+ pChildR++;
+ continue;
+ }
+
+ if (pWindow)
+ {
+ nCurrentWidth -= pWindow->GetOutputSizePixel().Width() + get_spacing();
+ pWindow->Show();
+ pPrioritable->ShowContent();
+ nCurrentWidth += getLayoutRequisition(*pWindow).Width() + get_spacing();
+
+ if (nCurrentWidth > nWidth)
+ {
+ pPrioritable->HideContent();
+ break;
+ }
+ }
+
+ pChildR++;
+ }
+
+ VclHBox::Resize();
+}
+
+void PriorityHBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (!m_bInitialized)
+ Initialize();
+
+ VclHBox::Paint(rRenderContext, rRect);
+}
+
+void PriorityHBox::GetChildrenWithPriorities()
+{
+ for (sal_uInt16 i = 0; i < GetChildCount(); ++i)
+ {
+ vcl::Window* pChild = GetChild(i);
+
+ // Add only containers which have explicitly assigned priority.
+ vcl::IPrioritable* pPrioritable = dynamic_cast<vcl::IPrioritable*>(pChild);
+ if (pPrioritable && pPrioritable->GetPriority() != VCL_PRIORITY_DEFAULT)
+ m_aSortedChildren.push_back(pPrioritable);
+ }
+
+ if (m_aSortedChildren.empty())
+ m_bInitialized = false;
+
+ std::sort(m_aSortedChildren.begin(), m_aSortedChildren.end(), lcl_comparePriority);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/PriorityMergedHBox.cxx b/vcl/source/control/PriorityMergedHBox.cxx
new file mode 100644
index 0000000000..fd5aa5814d
--- /dev/null
+++ b/vcl/source/control/PriorityMergedHBox.cxx
@@ -0,0 +1,199 @@
+/* -*- 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/button.hxx>
+#include <vcl/layout.hxx>
+#include <bitmaps.hlst>
+#include <NotebookbarPopup.hxx>
+#include <PriorityHBox.hxx>
+#include <PriorityMergedHBox.hxx>
+#include <comphelper/lok.hxx>
+
+#define DUMMY_WIDTH 50
+#define BUTTON_WIDTH 30
+
+/*
+* PriorityMergedHBox is a VclHBox which hides its own children if there is no sufficient space.
+*/
+
+PriorityMergedHBox::PriorityMergedHBox(vcl::Window* pParent)
+ : PriorityHBox(pParent)
+{
+ m_pButton = VclPtr<PushButton>::Create(this, WB_FLATBUTTON);
+ m_pButton->SetClickHdl(LINK(this, PriorityMergedHBox, PBClickHdl));
+ m_pButton->SetModeImage(Image(StockImage::Yes, CHEVRON));
+ m_pButton->set_width_request(25);
+ m_pButton->set_pack_type(VclPackType::End);
+ m_pButton->Show();
+}
+
+void PriorityMergedHBox::Resize()
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return VclHBox::Resize();
+
+ if (!m_bInitialized)
+ Initialize();
+
+ if (!m_bInitialized)
+ {
+ return VclHBox::Resize();
+ }
+
+ tools::Long nWidth = GetSizePixel().Width();
+ tools::Long nCurrentWidth = VclHBox::calculateRequisition().getWidth() + BUTTON_WIDTH;
+
+ // Hide lower priority controls
+ for (int i = GetChildCount() - 1; i >= 0; i--)
+ {
+ vcl::Window* pWindow = GetChild(i);
+
+ if (nCurrentWidth <= nWidth)
+ break;
+
+ if (pWindow && pWindow->GetParent() == this && pWindow->IsVisible())
+ {
+ tools::Long nWindowWidth = pWindow->GetOutputSizePixel().Width();
+ if (!nWindowWidth)
+ nWindowWidth = getLayoutRequisition(*pWindow).Width() + get_spacing();
+
+ if (nWindowWidth)
+ nCurrentWidth -= nWindowWidth;
+ else
+ nCurrentWidth -= DUMMY_WIDTH;
+ pWindow->Hide();
+ }
+ }
+
+ // Show higher priority controls if we already have enough space
+ for (int i = 0; i < GetChildCount(); i++)
+ {
+ vcl::Window* pWindow = GetChild(i);
+
+ if (pWindow->GetParent() != this)
+ {
+ continue;
+ }
+
+ if (pWindow && !pWindow->IsVisible())
+ {
+ pWindow->Show();
+ nCurrentWidth += getLayoutRequisition(*pWindow).Width() + get_spacing();
+
+ if (nCurrentWidth > nWidth)
+ {
+ pWindow->Hide();
+ break;
+ }
+ }
+ }
+
+ VclHBox::Resize();
+
+ if (GetHiddenCount())
+ m_pButton->Show();
+ else
+ m_pButton->Hide();
+}
+
+void PriorityMergedHBox::dispose()
+{
+ m_pButton.disposeAndClear();
+ if (m_pPopup)
+ m_pPopup.disposeAndClear();
+ PriorityHBox::dispose();
+}
+
+int PriorityMergedHBox::GetHiddenCount() const
+{
+ int nCount = 0;
+
+ for (int i = GetChildCount() - 1; i >= 0; i--)
+ {
+ vcl::Window* pWindow = GetChild(i);
+ if (pWindow && pWindow->GetParent() == this && !pWindow->IsVisible())
+ nCount++;
+ }
+
+ return nCount;
+}
+
+Size PriorityMergedHBox::calculateRequisition() const
+{
+ if (!m_bInitialized)
+ {
+ return VclHBox::calculateRequisition();
+ }
+
+ sal_uInt16 nVisibleChildren = 0;
+
+ Size aSize;
+ // find max height and total width
+ for (vcl::Window* pChild = GetWindow(GetWindowType::FirstChild); pChild;
+ pChild = pChild->GetWindow(GetWindowType::Next))
+ {
+ Size aChildSize = getLayoutRequisition(*pChild);
+ if (!pChild->IsVisible())
+ setPrimaryDimension(aChildSize, 0);
+ else
+ {
+ ++nVisibleChildren;
+ tools::Long nPrimaryDimension = getPrimaryDimension(aChildSize);
+ nPrimaryDimension += pChild->get_padding() * 2;
+ setPrimaryDimension(aChildSize, nPrimaryDimension);
+ }
+ accumulateMaxes(aChildSize, aSize);
+ }
+
+ return finalizeMaxes(aSize, nVisibleChildren);
+}
+
+IMPL_LINK(PriorityMergedHBox, PBClickHdl, Button*, /*pButton*/, void)
+{
+ if (m_pPopup)
+ m_pPopup.disposeAndClear();
+
+ m_pPopup = VclPtr<NotebookbarPopup>::Create(this);
+
+ for (int i = 0; i < GetChildCount(); i++)
+ {
+ vcl::Window* pWindow = GetChild(i);
+ if (pWindow != m_pButton)
+ {
+ if (!pWindow->IsVisible())
+ {
+ pWindow->Show();
+ pWindow->SetParent(m_pPopup->getBox());
+ // count is decreased because we moved child
+ i--;
+ }
+ }
+ }
+
+ m_pPopup->hideSeparators(true);
+
+ tools::Long x = m_pButton->GetPosPixel().getX();
+ tools::Long y = m_pButton->GetPosPixel().getY() + GetSizePixel().Height();
+ tools::Rectangle aRect(x, y, x, y);
+
+ m_pPopup->StartPopupMode(aRect, FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus
+ | FloatWinPopupFlags::AllMouseButtonClose);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/WeldedTabbedNotebookbar.cxx b/vcl/source/control/WeldedTabbedNotebookbar.cxx
new file mode 100644
index 0000000000..1a3311de9f
--- /dev/null
+++ b/vcl/source/control/WeldedTabbedNotebookbar.cxx
@@ -0,0 +1,23 @@
+/* -*- 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 <vcl/WeldedTabbedNotebookbar.hxx>
+#include <vcl/svapp.hxx>
+#include <jsdialog/jsdialogbuilder.hxx>
+
+WeldedTabbedNotebookbar::WeldedTabbedNotebookbar(
+ const VclPtr<vcl::Window>& pContainerWindow, const OUString& rUIFilePath,
+ const css::uno::Reference<css::frame::XFrame>& rFrame, sal_uInt64 nWindowId)
+ : m_xBuilder(JSInstanceBuilder::CreateNotebookbarBuilder(
+ pContainerWindow, AllSettings::GetUIRootDir(), rUIFilePath, rFrame, nWindowId))
+{
+ m_xContainer = m_xBuilder->weld_container("NotebookBar");
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/button.cxx b/vcl/source/control/button.cxx
new file mode 100644
index 0000000000..ac06f445e0
--- /dev/null
+++ b/vcl/source/control/button.cxx
@@ -0,0 +1,3842 @@
+/* -*- 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/builder.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/image.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/toolkit/dialog.hxx>
+#include <vcl/toolkit/fixed.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/stdtext.hxx>
+#include <vcl/uitest/uiobject.hxx>
+
+#include <bitmaps.hlst>
+#include <svdata.hxx>
+#include <window.h>
+#include <vclstatuslistener.hxx>
+#include <osl/diagnose.h>
+
+#include <comphelper/base64.hxx>
+#include <comphelper/dispatchcommand.hxx>
+#include <comphelper/lok.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <boost/property_tree/ptree.hpp>
+#include <tools/json_writer.hxx>
+#include <tools/stream.hxx>
+
+
+using namespace css;
+
+constexpr auto PUSHBUTTON_VIEW_STYLE = WB_3DLOOK |
+ WB_LEFT | WB_CENTER | WB_RIGHT |
+ WB_TOP | WB_VCENTER | WB_BOTTOM |
+ WB_WORDBREAK | WB_NOLABEL |
+ WB_DEFBUTTON | WB_NOLIGHTBORDER |
+ WB_RECTSTYLE | WB_SMALLSTYLE |
+ WB_TOGGLE;
+constexpr auto RADIOBUTTON_VIEW_STYLE = WB_3DLOOK |
+ WB_LEFT | WB_CENTER | WB_RIGHT |
+ WB_TOP | WB_VCENTER | WB_BOTTOM |
+ WB_WORDBREAK | WB_NOLABEL;
+constexpr auto CHECKBOX_VIEW_STYLE = WB_3DLOOK |
+ WB_LEFT | WB_CENTER | WB_RIGHT |
+ WB_TOP | WB_VCENTER | WB_BOTTOM |
+ WB_WORDBREAK | WB_NOLABEL;
+
+#define STYLE_RADIOBUTTON_MONO (sal_uInt16(0x0001)) // legacy
+#define STYLE_CHECKBOX_MONO (sal_uInt16(0x0001)) // legacy
+
+class ImplCommonButtonData
+{
+public:
+ ImplCommonButtonData();
+
+ tools::Rectangle maFocusRect;
+ tools::Long mnSeparatorX;
+ DrawButtonFlags mnButtonState;
+ bool mbSmallSymbol;
+ bool mbGeneratedTooltip;
+
+ Image maImage;
+ ImageAlign meImageAlign;
+ SymbolAlign meSymbolAlign;
+
+ Image maCustomContentImage;
+
+ /** StatusListener. Updates the button as the slot state changes */
+ rtl::Reference<VclStatusListener<Button>> mpStatusListener;
+};
+
+ImplCommonButtonData::ImplCommonButtonData() : mnSeparatorX(0), mnButtonState(DrawButtonFlags::NONE),
+mbSmallSymbol(false), mbGeneratedTooltip(false), meImageAlign(ImageAlign::Top), meSymbolAlign(SymbolAlign::LEFT)
+{
+}
+
+Button::Button( WindowType nType ) :
+ Control( nType ),
+ mpButtonData( std::make_unique<ImplCommonButtonData>() )
+{
+}
+
+Button::~Button()
+{
+ disposeOnce();
+}
+
+void Button::dispose()
+{
+ if (mpButtonData->mpStatusListener.is())
+ mpButtonData->mpStatusListener->dispose();
+ Control::dispose();
+}
+
+void Button::SetCommandHandler(const OUString& aCommand, const css::uno::Reference<css::frame::XFrame>& rFrame)
+{
+ maCommand = aCommand;
+ SetClickHdl( LINK( this, Button, dispatchCommandHandler) );
+
+ mpButtonData->mpStatusListener = new VclStatusListener<Button>(this, rFrame, aCommand);
+ mpButtonData->mpStatusListener->startListening();
+}
+
+void Button::Click()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ButtonClick, [this] () { maClickHdl.Call(this); } );
+}
+
+void Button::SetModeImage( const Image& rImage )
+{
+ if ( rImage != mpButtonData->maImage )
+ {
+ mpButtonData->maImage = rImage;
+ StateChanged( StateChangedType::Data );
+ queue_resize();
+ }
+}
+
+Image const & Button::GetModeImage( ) const
+{
+ return mpButtonData->maImage;
+}
+
+bool Button::HasImage() const
+{
+ return !!(mpButtonData->maImage);
+}
+
+void Button::SetImageAlign( ImageAlign eAlign )
+{
+ if ( mpButtonData->meImageAlign != eAlign )
+ {
+ mpButtonData->meImageAlign = eAlign;
+ StateChanged( StateChangedType::Data );
+ }
+}
+
+ImageAlign Button::GetImageAlign() const
+{
+ return mpButtonData->meImageAlign;
+}
+
+void Button::SetCustomButtonImage(const Image& rImage)
+{
+ if (rImage != mpButtonData->maCustomContentImage)
+ {
+ mpButtonData->maCustomContentImage = rImage;
+ StateChanged( StateChangedType::Data );
+ }
+}
+
+Image const & Button::GetCustomButtonImage() const
+{
+ return mpButtonData->maCustomContentImage;
+}
+
+tools::Long Button::ImplGetSeparatorX() const
+{
+ return mpButtonData->mnSeparatorX;
+}
+
+void Button::ImplSetSeparatorX( tools::Long nX )
+{
+ mpButtonData->mnSeparatorX = nX;
+}
+
+DrawTextFlags Button::ImplGetTextStyle( WinBits nWinStyle, SystemTextColorFlags nSystemTextColorFlags ) const
+{
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ DrawTextFlags nTextStyle = FixedText::ImplGetTextStyle(nWinStyle & ~WB_DEFBUTTON);
+
+ if (!IsEnabled())
+ nTextStyle |= DrawTextFlags::Disable;
+
+ if ((nSystemTextColorFlags & SystemTextColorFlags::Mono) ||
+ (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
+ {
+ nTextStyle |= DrawTextFlags::Mono;
+ }
+
+ return nTextStyle;
+}
+
+void Button::ImplDrawAlignedImage(OutputDevice* pDev, Point& rPos,
+ Size& rSize,
+ sal_Int32 nImageSep,
+ DrawTextFlags nTextStyle, tools::Rectangle *pSymbolRect,
+ bool bAddImageSep)
+{
+ OUString aText(GetText());
+ bool bDrawImage = HasImage();
+ bool bDrawText = !aText.isEmpty();
+ bool bHasSymbol = pSymbolRect != nullptr;
+
+ // No text and no image => nothing to do => return
+ if (!bDrawImage && !bDrawText && !bHasSymbol)
+ return;
+
+ WinBits nWinStyle = GetStyle();
+ tools::Rectangle aOutRect( rPos, rSize );
+ ImageAlign eImageAlign = mpButtonData->meImageAlign;
+ Size aImageSize = mpButtonData->maImage.GetSizePixel();
+
+ aImageSize.setWidth( CalcZoom( aImageSize.Width() ) );
+ aImageSize.setHeight( CalcZoom( aImageSize.Height() ) );
+
+ // Drawing text or symbol only is simple, use style and output rectangle
+ if (bHasSymbol && !bDrawImage && !bDrawText)
+ {
+ *pSymbolRect = aOutRect;
+ return;
+ }
+ else if (bDrawText && !bDrawImage && !bHasSymbol)
+ {
+ aOutRect = DrawControlText(*pDev, aOutRect, aText, nTextStyle, nullptr, nullptr);
+ tools::Rectangle textRect = GetTextRect(
+ tools::Rectangle(Point(), Size(0x7fffffff, 0x7fffffff)), aText, nTextStyle);
+ // If the button text doesn't fit into it, put it into a tooltip (might happen in sidebar)
+ if (GetQuickHelpText()!= aText && mpButtonData->mbGeneratedTooltip)
+ SetQuickHelpText("");
+ if (GetQuickHelpText().isEmpty() && textRect.getOpenWidth() > rSize.getWidth())
+ {
+ SetQuickHelpText(aText);
+ mpButtonData->mbGeneratedTooltip = true;
+ }
+
+ ImplSetFocusRect(aOutRect);
+ rSize = aOutRect.GetSize();
+ rPos = aOutRect.TopLeft();
+
+ return;
+ }
+
+ // check for HC mode ( image only! )
+ Image* pImage = &(mpButtonData->maImage);
+
+ Size aTextSize;
+ Size aSymbolSize;
+ Size aDeviceTextSize;
+ Point aImagePos = rPos;
+ Point aTextPos = rPos;
+ tools::Rectangle aUnion(aImagePos, aImageSize);
+ tools::Long nSymbolHeight = 0;
+
+ if (bDrawText || bHasSymbol)
+ {
+ // Get the size of the text output area ( the symbol will be drawn in
+ // this area as well, so the symbol rectangle will be calculated here, too )
+
+ tools::Rectangle aRect(Point(), rSize);
+ Size aTSSize;
+
+ if (bHasSymbol)
+ {
+ tools::Rectangle aSymbol;
+ if (bDrawText)
+ {
+ nSymbolHeight = pDev->GetTextHeight();
+ if (mpButtonData->mbSmallSymbol)
+ nSymbolHeight = nSymbolHeight * 3 / 4;
+
+ aSymbol = tools::Rectangle(Point(), Size(nSymbolHeight, nSymbolHeight));
+ ImplCalcSymbolRect(aSymbol);
+ aRect.AdjustLeft(3 * nSymbolHeight / 2 );
+ aTSSize.setWidth( 3 * nSymbolHeight / 2 );
+ }
+ else
+ {
+ aSymbol = tools::Rectangle(Point(), rSize);
+ ImplCalcSymbolRect(aSymbol);
+ aTSSize.setWidth( aSymbol.GetWidth() );
+ }
+ aTSSize.setHeight( aSymbol.GetHeight() );
+ aSymbolSize = aSymbol.GetSize();
+ }
+
+ if (bDrawText)
+ {
+ if ((eImageAlign == ImageAlign::LeftTop) ||
+ (eImageAlign == ImageAlign::Left ) ||
+ (eImageAlign == ImageAlign::LeftBottom) ||
+ (eImageAlign == ImageAlign::RightTop) ||
+ (eImageAlign == ImageAlign::Right) ||
+ (eImageAlign == ImageAlign::RightBottom))
+ {
+ aRect.AdjustRight( -sal_Int32(aImageSize.Width() + nImageSep) );
+ }
+ else if ((eImageAlign == ImageAlign::TopLeft) ||
+ (eImageAlign == ImageAlign::Top) ||
+ (eImageAlign == ImageAlign::TopRight) ||
+ (eImageAlign == ImageAlign::BottomLeft) ||
+ (eImageAlign == ImageAlign::Bottom) ||
+ (eImageAlign == ImageAlign::BottomRight))
+ {
+ aRect.AdjustBottom( -sal_Int32(aImageSize.Height() + nImageSep) );
+ }
+
+ aRect = GetControlTextRect(*pDev, aRect, aText, nTextStyle, &aDeviceTextSize);
+ aTextSize = aRect.GetSize();
+
+ aTSSize.AdjustWidth(aTextSize.Width() );
+
+ if (aTSSize.Height() < aTextSize.Height())
+ aTSSize.setHeight( aTextSize.Height() );
+
+ if (bAddImageSep && bDrawImage)
+ {
+ tools::Long nDiff = (aImageSize.Height() - aTextSize.Height()) / 3;
+ if (nDiff > 0)
+ nImageSep += nDiff;
+ }
+ }
+
+ Size aMax;
+ aMax.setWidth( std::max(aTSSize.Width(), aImageSize.Width()) );
+ aMax.setHeight( std::max(aTSSize.Height(), aImageSize.Height()) );
+
+ // Now calculate the output area for the image and the text according to the image align flags
+
+ if ((eImageAlign == ImageAlign::Left) ||
+ (eImageAlign == ImageAlign::Right))
+ {
+ aImagePos.setY( rPos.Y() + (aMax.Height() - aImageSize.Height()) / 2 );
+ aTextPos.setY( rPos.Y() + (aMax.Height() - aTSSize.Height()) / 2 );
+ }
+ else if ((eImageAlign == ImageAlign::LeftBottom) ||
+ (eImageAlign == ImageAlign::RightBottom))
+ {
+ aImagePos.setY( rPos.Y() + aMax.Height() - aImageSize.Height() );
+ aTextPos.setY( rPos.Y() + aMax.Height() - aTSSize.Height() );
+ }
+ else if ((eImageAlign == ImageAlign::Top) ||
+ (eImageAlign == ImageAlign::Bottom))
+ {
+ aImagePos.setX( rPos.X() + (aMax.Width() - aImageSize.Width()) / 2 );
+ aTextPos.setX( rPos.X() + (aMax.Width() - aTSSize.Width()) / 2 );
+ }
+ else if ((eImageAlign == ImageAlign::TopRight) ||
+ (eImageAlign == ImageAlign::BottomRight))
+ {
+ aImagePos.setX( rPos.X() + aMax.Width() - aImageSize.Width() );
+ aTextPos.setX( rPos.X() + aMax.Width() - aTSSize.Width() );
+ }
+
+ if ((eImageAlign == ImageAlign::LeftTop) ||
+ (eImageAlign == ImageAlign::Left) ||
+ (eImageAlign == ImageAlign::LeftBottom))
+ {
+ aTextPos.setX( rPos.X() + aImageSize.Width() + nImageSep );
+ }
+ else if ((eImageAlign == ImageAlign::RightTop) ||
+ (eImageAlign == ImageAlign::Right) ||
+ (eImageAlign == ImageAlign::RightBottom))
+ {
+ aImagePos.setX( rPos.X() + aTSSize.Width() + nImageSep );
+ }
+ else if ((eImageAlign == ImageAlign::TopLeft) ||
+ (eImageAlign == ImageAlign::Top) ||
+ (eImageAlign == ImageAlign::TopRight))
+ {
+ aTextPos.setY( rPos.Y() + aImageSize.Height() + nImageSep );
+ }
+ else if ((eImageAlign == ImageAlign::BottomLeft) ||
+ (eImageAlign == ImageAlign::Bottom) ||
+ (eImageAlign == ImageAlign::BottomRight))
+ {
+ aImagePos.setY( rPos.Y() + aTSSize.Height() + nImageSep );
+ }
+ else if (eImageAlign == ImageAlign::Center)
+ {
+ aImagePos.setX( rPos.X() + (aMax.Width() - aImageSize.Width()) / 2 );
+ aImagePos.setY( rPos.Y() + (aMax.Height() - aImageSize.Height()) / 2 );
+ aTextPos.setX( rPos.X() + (aMax.Width() - aTSSize.Width()) / 2 );
+ aTextPos.setY( rPos.Y() + (aMax.Height() - aTSSize.Height()) / 2 );
+ }
+ aUnion = tools::Rectangle(aImagePos, aImageSize);
+ aUnion.Union(tools::Rectangle(aTextPos, aTSSize));
+ }
+
+ // Now place the combination of text and image in the output area of the button
+ // according to the window style (WinBits)
+ tools::Long nXOffset = 0;
+ tools::Long nYOffset = 0;
+
+ if (nWinStyle & WB_CENTER)
+ {
+ nXOffset = (rSize.Width() - aUnion.GetWidth()) / 2;
+ }
+ else if (nWinStyle & WB_RIGHT)
+ {
+ nXOffset = rSize.Width() - aUnion.GetWidth();
+ }
+
+ if (nWinStyle & WB_VCENTER)
+ {
+ nYOffset = (rSize.Height() - aUnion.GetHeight()) / 2;
+ }
+ else if (nWinStyle & WB_BOTTOM)
+ {
+ nYOffset = rSize.Height() - aUnion.GetHeight();
+ }
+
+ // the top left corner should always be visible, so we don't allow negative offsets
+ if (nXOffset < 0) nXOffset = 0;
+ if (nYOffset < 0) nYOffset = 0;
+
+ aImagePos.AdjustX(nXOffset );
+ aImagePos.AdjustY(nYOffset );
+ aTextPos.AdjustX(nXOffset );
+ aTextPos.AdjustY(nYOffset );
+
+ // set rPos and rSize to the union
+ rSize = aUnion.GetSize();
+ rPos.AdjustX(nXOffset );
+ rPos.AdjustY(nYOffset );
+
+ if (bHasSymbol)
+ {
+ if (mpButtonData->meSymbolAlign == SymbolAlign::RIGHT)
+ {
+ Point aRightPos(aTextPos.X() + aTextSize.Width() + aSymbolSize.Width() / 2, aTextPos.Y());
+ *pSymbolRect = tools::Rectangle(aRightPos, aSymbolSize);
+ }
+ else
+ {
+ *pSymbolRect = tools::Rectangle(aTextPos, aSymbolSize);
+ aTextPos.AdjustX(3 * nSymbolHeight / 2 );
+ }
+ if (mpButtonData->mbSmallSymbol)
+ {
+ nYOffset = (aUnion.GetHeight() - aSymbolSize.Height()) / 2;
+ pSymbolRect->SetPosY(aTextPos.Y() + nYOffset);
+ }
+ }
+
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+
+ if (!IsEnabled())
+ {
+ nStyle |= DrawImageFlags::Disable;
+ }
+
+ if (IsZoom())
+ pDev->DrawImage(aImagePos, aImageSize, *pImage, nStyle);
+ else
+ pDev->DrawImage(aImagePos, *pImage, nStyle);
+
+ if (bDrawText)
+ {
+ const tools::Rectangle aTOutRect(aTextPos, aTextSize);
+ ImplSetFocusRect(aTOutRect);
+ DrawControlText(*pDev, aTOutRect, aText, nTextStyle, nullptr, nullptr, &aDeviceTextSize);
+ }
+ else
+ {
+ ImplSetFocusRect(tools::Rectangle(aImagePos, aImageSize));
+ }
+}
+
+void Button::ImplSetFocusRect(const tools::Rectangle &rFocusRect)
+{
+ tools::Rectangle aFocusRect = rFocusRect;
+ tools::Rectangle aOutputRect(Point(), GetOutputSizePixel());
+
+ if (!aFocusRect.IsEmpty())
+ {
+ aFocusRect.AdjustLeft( -1 );
+ aFocusRect.AdjustTop( -1 );
+ aFocusRect.AdjustRight( 1 );
+ aFocusRect.AdjustBottom( 1 );
+ }
+
+ if (aFocusRect.Left() < aOutputRect.Left())
+ aFocusRect.SetLeft( aOutputRect.Left() );
+ if (aFocusRect.Top() < aOutputRect.Top())
+ aFocusRect.SetTop( aOutputRect.Top() );
+ if (aFocusRect.Right() > aOutputRect.Right())
+ aFocusRect.SetRight( aOutputRect.Right() );
+ if (aFocusRect.Bottom() > aOutputRect.Bottom())
+ aFocusRect.SetBottom( aOutputRect.Bottom() );
+
+ mpButtonData->maFocusRect = aFocusRect;
+}
+
+const tools::Rectangle& Button::ImplGetFocusRect() const
+{
+ return mpButtonData->maFocusRect;
+}
+
+DrawButtonFlags& Button::GetButtonState()
+{
+ return mpButtonData->mnButtonState;
+}
+
+DrawButtonFlags Button::GetButtonState() const
+{
+ return mpButtonData->mnButtonState;
+}
+
+void Button::ImplSetSymbolAlign( SymbolAlign eAlign )
+{
+ if ( mpButtonData->meSymbolAlign != eAlign )
+ {
+ mpButtonData->meSymbolAlign = eAlign;
+ StateChanged( StateChangedType::Data );
+ }
+}
+
+void Button::SetSmallSymbol()
+{
+ mpButtonData->mbSmallSymbol = true;
+}
+
+bool Button::IsSmallSymbol () const
+{
+ return mpButtonData->mbSmallSymbol;
+}
+
+bool Button::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "image-position")
+ {
+ ImageAlign eAlign = ImageAlign::Left;
+ if (rValue == "left")
+ eAlign = ImageAlign::Left;
+ else if (rValue == "right")
+ eAlign = ImageAlign::Right;
+ else if (rValue == "top")
+ eAlign = ImageAlign::Top;
+ else if (rValue == "bottom")
+ eAlign = ImageAlign::Bottom;
+ SetImageAlign(eAlign);
+ }
+ else if (rKey == "focus-on-click")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~WB_NOPOINTERFOCUS;
+ if (!toBool(rValue))
+ nBits |= WB_NOPOINTERFOCUS;
+ SetStyle(nBits);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+void Button::statusChanged(const css::frame::FeatureStateEvent& rEvent)
+{
+ Enable(rEvent.IsEnabled);
+}
+
+FactoryFunction Button::GetUITestFactory() const
+{
+ return ButtonUIObject::create;
+}
+
+namespace
+{
+
+std::string_view symbolTypeName(SymbolType eSymbolType)
+{
+ switch (eSymbolType)
+ {
+ case SymbolType::DONTKNOW: return "DONTKNOW";
+ case SymbolType::IMAGE: return "IMAGE";
+ case SymbolType::ARROW_UP: return "ARROW_UP";
+ case SymbolType::ARROW_DOWN: return "ARROW_DOWN";
+ case SymbolType::ARROW_LEFT: return "ARROW_LEFT";
+ case SymbolType::ARROW_RIGHT: return "ARROW_RIGHT";
+ case SymbolType::SPIN_UP: return "SPIN_UP";
+ case SymbolType::SPIN_DOWN: return "SPIN_DOWN";
+ case SymbolType::SPIN_LEFT: return "SPIN_LEFT";
+ case SymbolType::SPIN_RIGHT: return "SPIN_RIGHT";
+ case SymbolType::FIRST: return "FIRST";
+ case SymbolType::LAST: return "LAST";
+ case SymbolType::PREV: return "PREV";
+ case SymbolType::NEXT: return "NEXT";
+ case SymbolType::PAGEUP: return "PAGEUP";
+ case SymbolType::PAGEDOWN: return "PAGEDOWN";
+ case SymbolType::PLAY: return "PLAY";
+ case SymbolType::STOP: return "STOP";
+ case SymbolType::CLOSE: return "CLOSE";
+ case SymbolType::CHECKMARK: return "CHECKMARK";
+ case SymbolType::RADIOCHECKMARK: return "RADIOCHECKMARK";
+ case SymbolType::FLOAT: return "FLOAT";
+ case SymbolType::DOCK: return "DOCK";
+ case SymbolType::HIDE: return "HIDE";
+ case SymbolType::HELP: return "HELP";
+ case SymbolType::PLUS: return "PLUS";
+ }
+
+ return "UNKNOWN";
+}
+
+}
+
+void Button::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("text", GetText());
+ if (HasImage())
+ {
+ SvMemoryStream aOStm(6535, 6535);
+ if(GraphicConverter::Export(aOStm, GetModeImage().GetBitmapEx(), ConvertDataFormat::PNG) == ERRCODE_NONE)
+ {
+ css::uno::Sequence<sal_Int8> aSeq( static_cast<sal_Int8 const *>(aOStm.GetData()), aOStm.Tell());
+ OStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ rJsonWriter.put("image", aBuffer);
+ }
+ }
+
+ if (GetStyle() & WB_DEFBUTTON)
+ rJsonWriter.put("has_default", true);
+}
+
+void PushButton::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Button::DumpAsPropertyTree(rJsonWriter);
+ if (GetSymbol() != SymbolType::DONTKNOW)
+ rJsonWriter.put("symbol", symbolTypeName(GetSymbol()));
+ if (isToggleButton())
+ rJsonWriter.put("isToggle", true);
+}
+
+IMPL_STATIC_LINK( Button, dispatchCommandHandler, Button*, pButton, void )
+{
+ if (pButton == nullptr)
+ return;
+
+ comphelper::dispatchCommand(pButton->maCommand, uno::Sequence<beans::PropertyValue>());
+}
+
+void PushButton::ImplInitPushButtonData()
+{
+ mpWindowImpl->mbPushButton = true;
+
+ meSymbol = SymbolType::DONTKNOW;
+ meState = TRISTATE_FALSE;
+ mnDDStyle = PushButtonDropdownStyle::NONE;
+ mbIsActive = false;
+ mbPressed = false;
+ mbIsAction = false;
+}
+
+namespace
+{
+ vcl::Window* getPreviousSibling(vcl::Window const *pParent)
+ {
+ return pParent ? pParent->GetWindow(GetWindowType::LastChild) : nullptr;
+ }
+}
+
+void PushButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle(getPreviousSibling(pParent), nStyle);
+ Button::ImplInit( pParent, nStyle, nullptr );
+
+ if ( nStyle & WB_NOLIGHTBORDER )
+ GetButtonState() |= DrawButtonFlags::NoLightBorder;
+
+ ImplInitSettings( true );
+}
+
+WinBits PushButton::ImplInitStyle( const vcl::Window* pPrevWindow, WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+
+ // if no alignment is given, default to "vertically centered". This is because since
+ // #i26046#, we respect the vertical alignment flags (previously we didn't completely),
+ // but we of course want to look as before when no vertical alignment is specified
+ if ( ( nStyle & ( WB_TOP | WB_VCENTER | WB_BOTTOM ) ) == 0 )
+ nStyle |= WB_VCENTER;
+
+ if ( !(nStyle & WB_NOGROUP) &&
+ (!pPrevWindow ||
+ ((pPrevWindow->GetType() != WindowType::PUSHBUTTON ) &&
+ (pPrevWindow->GetType() != WindowType::OKBUTTON ) &&
+ (pPrevWindow->GetType() != WindowType::CANCELBUTTON) &&
+ (pPrevWindow->GetType() != WindowType::HELPBUTTON )) ) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+const vcl::Font& PushButton::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetPushButtonFont();
+}
+
+const Color& PushButton::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetButtonTextColor();
+}
+
+void PushButton::ImplInitSettings( bool bBackground )
+{
+ Button::ImplInitSettings();
+
+ if ( !bBackground )
+ return;
+
+ SetBackground();
+ // #i38498#: do not check for GetParent()->IsChildTransparentModeEnabled()
+ // otherwise the formcontrol button will be overdrawn due to ParentClipMode::NoClip
+ // for radio and checkbox this is ok as they should appear transparent in documents
+ if ( IsNativeControlSupported( ControlType::Pushbutton, ControlPart::Entire ) ||
+ (GetStyle() & WB_FLATBUTTON) != 0 )
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode( ParentClipMode::NoClip );
+ SetPaintTransparent( true );
+
+ if ((GetStyle() & WB_FLATBUTTON) == 0)
+ mpWindowImpl->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
+ else
+ mpWindowImpl->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRectsForFlatButtons;
+ }
+ else
+ {
+ EnableChildTransparentMode( false );
+ SetParentClipMode();
+ SetPaintTransparent( false );
+ }
+}
+
+void PushButton::ImplDrawPushButtonFrame(vcl::RenderContext& rRenderContext,
+ tools::Rectangle& rRect, DrawButtonFlags nStyle)
+{
+ if (!(GetStyle() & (WB_RECTSTYLE | WB_SMALLSTYLE)))
+ {
+ StyleSettings aStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ if (IsControlBackground())
+ aStyleSettings.Set3DColors(GetControlBackground());
+ }
+
+ DecorationView aDecoView(&rRenderContext);
+ if (IsControlBackground())
+ {
+ AllSettings aSettings = rRenderContext.GetSettings();
+ AllSettings aOldSettings = aSettings;
+ StyleSettings aStyleSettings = aSettings.GetStyleSettings();
+ if (nStyle & DrawButtonFlags::Highlight)
+ {
+ // with the custom background, native highlight do nothing, so code below mimic
+ // native highlight by changing luminance
+ Color controlBackgroundColorHighlighted = GetControlBackground();
+ sal_uInt8 colorLuminance = controlBackgroundColorHighlighted.GetLuminance();
+ if (colorLuminance < 205)
+ controlBackgroundColorHighlighted.IncreaseLuminance(50);
+ else
+ controlBackgroundColorHighlighted.DecreaseLuminance(50);
+ aStyleSettings.Set3DColors(controlBackgroundColorHighlighted);
+ }
+ else
+ aStyleSettings.Set3DColors(GetControlBackground());
+ aSettings.SetStyleSettings(aStyleSettings);
+
+ // Call OutputDevice::SetSettings() explicitly, as rRenderContext may
+ // be a vcl::Window in fact, and vcl::Window::SetSettings() will call
+ // Invalidate(), which is a problem, since we're in Paint().
+ rRenderContext.OutputDevice::SetSettings(aSettings);
+ rRect = aDecoView.DrawButton(rRect, nStyle);
+ rRenderContext.OutputDevice::SetSettings(aOldSettings);
+ }
+ else
+ rRect = aDecoView.DrawButton(rRect, nStyle);
+}
+
+bool PushButton::ImplHitTestPushButton( vcl::Window const * pDev,
+ const Point& rPos )
+{
+ tools::Rectangle aTestRect( Point(), pDev->GetOutputSizePixel() );
+
+ return aTestRect.Contains( rPos );
+}
+
+DrawTextFlags PushButton::ImplGetTextStyle( SystemTextColorFlags nSystemTextColorFlags ) const
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+
+ DrawTextFlags nTextStyle = DrawTextFlags::Mnemonic | DrawTextFlags::MultiLine | DrawTextFlags::EndEllipsis;
+
+ if ( ( rStyleSettings.GetOptions() & StyleSettingsOptions::Mono ) ||
+ ( nSystemTextColorFlags & SystemTextColorFlags::Mono ) )
+ nTextStyle |= DrawTextFlags::Mono;
+
+ if ( GetStyle() & WB_WORDBREAK )
+ nTextStyle |= DrawTextFlags::WordBreak;
+ if ( GetStyle() & WB_NOLABEL )
+ nTextStyle &= ~DrawTextFlags::Mnemonic;
+
+ if ( GetStyle() & WB_LEFT )
+ nTextStyle |= DrawTextFlags::Left;
+ else if ( GetStyle() & WB_RIGHT )
+ nTextStyle |= DrawTextFlags::Right;
+ else
+ nTextStyle |= DrawTextFlags::Center;
+
+ if ( GetStyle() & WB_TOP )
+ nTextStyle |= DrawTextFlags::Top;
+ else if ( GetStyle() & WB_BOTTOM )
+ nTextStyle |= DrawTextFlags::Bottom;
+ else
+ nTextStyle |= DrawTextFlags::VCenter;
+
+ if ( !IsEnabled() )
+ nTextStyle |= DrawTextFlags::Disable;
+
+ return nTextStyle;
+}
+
+void PushButton::ImplDrawPushButtonContent(OutputDevice *pDev, SystemTextColorFlags nSystemTextColorFlags,
+ const tools::Rectangle &rRect, bool bMenuBtnSep,
+ DrawButtonFlags nButtonFlags)
+{
+ const StyleSettings &rStyleSettings = GetSettings().GetStyleSettings();
+ tools::Rectangle aInRect = rRect;
+ Color aColor;
+ DrawTextFlags nTextStyle = ImplGetTextStyle(nSystemTextColorFlags);
+ DrawSymbolFlags nStyle;
+
+ if (aInRect.Right() < aInRect.Left() || aInRect.Bottom() < aInRect.Top())
+ return;
+
+ pDev->Push(vcl::PushFlags::CLIPREGION);
+ pDev->IntersectClipRegion(aInRect);
+
+ if (nSystemTextColorFlags & SystemTextColorFlags::Mono)
+ aColor = COL_BLACK;
+
+ else if (IsControlForeground())
+ aColor = GetControlForeground();
+
+ // Button types with possibly different text coloring are flat buttons and regular buttons. Regular buttons may be action
+ // buttons and may have an additional default status. Moreover all buttons may have an additional pressed and rollover
+ // (highlight) status. Pressed buttons are always in rollover status.
+
+ else if (GetStyle() & WB_FLATBUTTON)
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetFlatButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetFlatButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetFlatButtonTextColor();
+ else
+ if (isAction() && (nButtonFlags & DrawButtonFlags::Default))
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetDefaultActionButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetDefaultActionButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetDefaultActionButtonTextColor();
+ else if (isAction())
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetActionButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetActionButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetActionButtonTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Default)
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetDefaultButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetDefaultButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetDefaultButtonTextColor();
+ else
+ if (nButtonFlags & DrawButtonFlags::Pressed)
+ aColor = rStyleSettings.GetButtonPressedRolloverTextColor();
+ else if (nButtonFlags & DrawButtonFlags::Highlight)
+ aColor = rStyleSettings.GetButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetButtonTextColor();
+
+#if defined(MACOSX) || defined(IOS)
+ // tdf#152486 These are the buttons in infobars where the infobar has a custom
+ // background color and on these platforms the buttons blend with
+ // their background.
+ vcl::Window* pParent = GetParent();
+ if (pParent->get_id() == "ExtraButton")
+ {
+ while (pParent && !pParent->IsControlBackground())
+ pParent = pParent->GetParent();
+ if (pParent)
+ {
+ if (aColor.IsBright() && !pParent->GetControlBackground().IsDark())
+ aColor = COL_BLACK;
+ }
+ }
+#endif
+
+ pDev->SetTextColor(aColor);
+
+ if ( IsEnabled() )
+ nStyle = DrawSymbolFlags::NONE;
+ else
+ nStyle = DrawSymbolFlags::Disable;
+
+ Size aSize = rRect.GetSize();
+ Point aPos = rRect.TopLeft();
+
+ sal_Int32 nImageSep = 1 + (pDev->GetTextHeight()-10)/2;
+ if( nImageSep < 1 )
+ nImageSep = 1;
+ if ( mnDDStyle == PushButtonDropdownStyle::MenuButton ||
+ mnDDStyle == PushButtonDropdownStyle::SplitMenuButton )
+ {
+ tools::Long nSeparatorX = 0;
+ tools::Rectangle aSymbolRect = aInRect;
+
+ // calculate symbol size
+ tools::Long nSymbolSize = pDev->GetTextHeight() / 2 + 1;
+ if (nSymbolSize > aSize.Width() / 2)
+ nSymbolSize = aSize.Width() / 2;
+
+ nSeparatorX = aInRect.Right() - 2*nSymbolSize;
+
+ // tdf#141761 Minimum width should be (1) Pixel, see comment
+ // with same task number above for more info
+ const tools::Long nWidthAdjust(2*nSymbolSize);
+ aSize.setWidth(std::max(static_cast<tools::Long>(1), aSize.getWidth() - nWidthAdjust));
+
+ // center symbol rectangle in the separated area
+ aSymbolRect.AdjustRight( -(nSymbolSize/2) );
+ aSymbolRect.SetLeft( aSymbolRect.Right() - nSymbolSize );
+
+ ImplDrawAlignedImage( pDev, aPos, aSize, nImageSep,
+ nTextStyle, nullptr, true );
+
+ tools::Long nDistance = (aSymbolRect.GetHeight() > 10) ? 2 : 1;
+ DecorationView aDecoView( pDev );
+ if( bMenuBtnSep && nSeparatorX > 0 )
+ {
+ Point aStartPt( nSeparatorX, aSymbolRect.Top()+nDistance );
+ Point aEndPt( nSeparatorX, aSymbolRect.Bottom()-nDistance );
+ aDecoView.DrawSeparator( aStartPt, aEndPt );
+ }
+ ImplSetSeparatorX( nSeparatorX );
+
+ aDecoView.DrawSymbol( aSymbolRect, SymbolType::SPIN_DOWN, aColor, nStyle );
+
+ }
+ else
+ {
+ tools::Rectangle aSymbolRect;
+ ImplDrawAlignedImage( pDev, aPos, aSize, nImageSep,
+ nTextStyle, IsSymbol() ? &aSymbolRect : nullptr, true );
+
+ if ( IsSymbol() )
+ {
+ DecorationView aDecoView( pDev );
+ aDecoView.DrawSymbol( aSymbolRect, meSymbol, aColor, nStyle );
+ }
+ }
+
+ pDev->Pop(); // restore clipregion
+}
+
+void PushButton::ImplDrawPushButton(vcl::RenderContext& rRenderContext)
+{
+ HideFocus();
+
+ DrawButtonFlags nButtonStyle = GetButtonState();
+ Size aOutSz(GetOutputSizePixel());
+ tools::Rectangle aRect(Point(), aOutSz);
+ tools::Rectangle aInRect = aRect;
+ bool bNativeOK = false;
+
+ // adjust style if button should be rendered 'pressed'
+ if (mbPressed || mbIsActive)
+ nButtonStyle |= DrawButtonFlags::Pressed;
+
+ if (GetStyle() & WB_FLATBUTTON)
+ nButtonStyle |= DrawButtonFlags::Flat;
+
+ // TODO: move this to Window class or make it a member !!!
+ ControlType aCtrlType = ControlType::Generic;
+ switch(GetParent()->GetType())
+ {
+ case WindowType::LISTBOX:
+ case WindowType::MULTILISTBOX:
+ case WindowType::TREELISTBOX:
+ aCtrlType = ControlType::Listbox;
+ 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:
+ aCtrlType = ControlType::Combobox;
+ break;
+ default:
+ break;
+ }
+
+ bool bDropDown = (IsSymbol() && (GetSymbol() == SymbolType::SPIN_DOWN) && GetText().isEmpty());
+
+ if( bDropDown && (aCtrlType == ControlType::Combobox || aCtrlType == ControlType::Listbox))
+ {
+ if (GetParent()->IsNativeControlSupported(aCtrlType, ControlPart::Entire))
+ {
+ // skip painting if the button was already drawn by the theme
+ if (aCtrlType == ControlType::Combobox)
+ {
+ Edit* pEdit = static_cast<Edit*>(GetParent());
+ if (pEdit->ImplUseNativeBorder(rRenderContext, pEdit->GetStyle()))
+ bNativeOK = true;
+ }
+ else if (GetParent()->IsNativeControlSupported(aCtrlType, ControlPart::HasBackgroundTexture))
+ {
+ bNativeOK = true;
+ }
+
+ if (!bNativeOK && GetParent()->IsNativeControlSupported(aCtrlType, ControlPart::ButtonDown))
+ {
+ // let the theme draw it, note we then need support
+ // for ControlType::Listbox/ControlPart::ButtonDown and ControlType::Combobox/ControlPart::ButtonDown
+
+ ImplControlValue aControlValue;
+ ControlState nState = ControlState::NONE;
+
+ if (mbPressed || mbIsActive)
+ nState |= ControlState::PRESSED;
+ if (GetButtonState() & DrawButtonFlags::Pressed)
+ nState |= ControlState::PRESSED;
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+ if (GetButtonState() & DrawButtonFlags::Default)
+ nState |= ControlState::DEFAULT;
+ if (Window::IsEnabled())
+ nState |= ControlState::ENABLED;
+
+ if (IsMouseOver() && aInRect.Contains(GetPointerPosPixel()))
+ nState |= ControlState::ROLLOVER;
+
+ if ( IsMouseOver() && aInRect.Contains(GetPointerPosPixel()) && mbIsActive)
+ {
+ nState |= ControlState::ROLLOVER;
+ nButtonStyle &= ~DrawButtonFlags::Pressed;
+ }
+
+ bNativeOK = rRenderContext.DrawNativeControl(aCtrlType, ControlPart::ButtonDown, aInRect, nState,
+ aControlValue, OUString());
+ }
+ }
+ }
+
+ if (bNativeOK)
+ return;
+
+ bool bRollOver = (IsMouseOver() && aInRect.Contains(GetPointerPosPixel()));
+ if (bRollOver)
+ nButtonStyle |= DrawButtonFlags::Highlight;
+ bool bDrawMenuSep = mnDDStyle == PushButtonDropdownStyle::SplitMenuButton;
+ if (GetStyle() & WB_FLATBUTTON)
+ {
+ if (!bRollOver && !HasFocus())
+ bDrawMenuSep = false;
+ }
+ // tdf#123175 if there is a custom control bg set, draw the button without outsourcing to the NWF
+ bNativeOK = !IsControlBackground() && rRenderContext.IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Entire);
+ if (bNativeOK)
+ {
+ PushButtonValue aControlValue;
+ aControlValue.mbIsAction = isAction();
+
+ tools::Rectangle aCtrlRegion(aInRect);
+ ControlState nState = ControlState::NONE;
+
+ if (mbPressed || IsChecked() || mbIsActive)
+ {
+ nState |= ControlState::PRESSED;
+ nButtonStyle |= DrawButtonFlags::Pressed;
+ }
+ if (GetButtonState() & DrawButtonFlags::Pressed)
+ nState |= ControlState::PRESSED;
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+ if (GetButtonState() & DrawButtonFlags::Default)
+ nState |= ControlState::DEFAULT;
+ if (Window::IsEnabled())
+ nState |= ControlState::ENABLED;
+
+ if (bRollOver || mbIsActive)
+ {
+ nButtonStyle |= DrawButtonFlags::Highlight;
+ nState |= ControlState::ROLLOVER;
+ }
+
+ if (mbIsActive && bRollOver)
+ {
+ nState &= ~ControlState::PRESSED;
+ nButtonStyle &= ~DrawButtonFlags::Pressed;
+ }
+
+ if (GetStyle() & WB_FLATBUTTON)
+ aControlValue.m_bFlatButton = true;
+
+ // draw frame into invisible window to have aInRect modified correctly
+ // but do not shift the inner rect for pressed buttons (ie remove DrawButtonFlags::Pressed)
+ // this assumes the theme has enough visual cues to signalize the button was pressed
+ //Window aWin( this );
+ //ImplDrawPushButtonFrame( &aWin, aInRect, nButtonStyle & ~DrawButtonFlags::Pressed );
+
+ // looks better this way as symbols were displaced slightly using the above approach
+ aInRect.AdjustTop(4 );
+ aInRect.AdjustBottom( -4 );
+ aInRect.AdjustLeft(4 );
+ aInRect.AdjustRight( -4 );
+
+ // prepare single line hint (needed on mac to decide between normal push button and
+ // rectangular bevel button look)
+ Size aFontSize(Application::GetSettings().GetStyleSettings().GetPushButtonFont().GetFontSize());
+ aFontSize = rRenderContext.LogicToPixel(aFontSize, MapMode(MapUnit::MapPoint));
+ Size aInRectSize(rRenderContext.LogicToPixel(Size(aInRect.GetWidth(), aInRect.GetHeight())));
+ aControlValue.mbSingleLine = (aInRectSize.Height() < 2 * aFontSize.Height());
+
+ if (!aControlValue.m_bFlatButton || (nState & ControlState::ROLLOVER) || (nState & ControlState::PRESSED)
+ || (HasFocus() && mpWindowImpl->mbUseNativeFocus
+ && !IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus)))
+ {
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Pushbutton, ControlPart::Entire, aCtrlRegion, nState,
+ aControlValue, OUString() /*PushButton::GetText()*/);
+ }
+ else
+ {
+ bNativeOK = true;
+ }
+
+ // draw content using the same aInRect as non-native VCL would do
+ ImplDrawPushButtonContent(&rRenderContext, SystemTextColorFlags::NONE,
+ aInRect, bDrawMenuSep, nButtonStyle);
+
+ if (HasFocus())
+ ShowFocus(ImplGetFocusRect());
+ }
+
+ if (bNativeOK)
+ return;
+
+ // draw PushButtonFrame, aInRect has content size afterwards
+ if (GetStyle() & WB_FLATBUTTON)
+ {
+ tools::Rectangle aTempRect(aInRect);
+ ImplDrawPushButtonFrame(rRenderContext, aTempRect, nButtonStyle);
+ aInRect.AdjustLeft(2 );
+ aInRect.AdjustTop(2 );
+ aInRect.AdjustRight( -2 );
+ aInRect.AdjustBottom( -2 );
+ }
+ else
+ {
+ ImplDrawPushButtonFrame(rRenderContext, aInRect, nButtonStyle);
+ }
+
+ // draw content
+ ImplDrawPushButtonContent(&rRenderContext, SystemTextColorFlags::NONE, aInRect, bDrawMenuSep, nButtonStyle);
+
+ if (HasFocus())
+ {
+ ShowFocus(ImplGetFocusRect());
+ }
+}
+
+void PushButton::ImplSetDefButton( bool bSet )
+{
+ Size aSize( GetSizePixel() );
+ Point aPos( GetPosPixel() );
+ int dLeft(0), dRight(0), dTop(0), dBottom(0);
+ bool bSetPos = false;
+
+ if ( IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Entire) )
+ {
+ tools::Rectangle aBound, aCont;
+ tools::Rectangle aCtrlRegion( 0, 0, 80, 20 ); // use a constant size to avoid accumulating
+ // will not work if the theme has dynamic adornment sizes
+ ImplControlValue aControlValue;
+
+ // get native size of a 'default' button
+ // and adjust the VCL button if more space for adornment is required
+ if( GetNativeControlRegion( ControlType::Pushbutton, ControlPart::Entire, aCtrlRegion,
+ ControlState::DEFAULT|ControlState::ENABLED,
+ aControlValue,
+ aBound, aCont ) )
+ {
+ dLeft = aCont.Left() - aBound.Left();
+ dTop = aCont.Top() - aBound.Top();
+ dRight = aBound.Right() - aCont.Right();
+ dBottom = aBound.Bottom() - aCont.Bottom();
+ bSetPos = dLeft || dTop || dRight || dBottom;
+ }
+ }
+
+ if ( bSet )
+ {
+ if( !(GetButtonState() & DrawButtonFlags::Default) && bSetPos )
+ {
+ // adjust pos/size when toggling from non-default to default
+ aPos.Move(-dLeft, -dTop);
+ aSize.AdjustWidth(dLeft + dRight );
+ aSize.AdjustHeight(dTop + dBottom );
+ }
+ GetButtonState() |= DrawButtonFlags::Default;
+ }
+ else
+ {
+ if( (GetButtonState() & DrawButtonFlags::Default) && bSetPos )
+ {
+ // adjust pos/size when toggling from default to non-default
+ aPos.Move(dLeft, dTop);
+ aSize.AdjustWidth( -(dLeft + dRight) );
+ aSize.AdjustHeight( -(dTop + dBottom) );
+ }
+ GetButtonState() &= ~DrawButtonFlags::Default;
+ }
+ if( bSetPos )
+ setPosSizePixel( aPos.X(), aPos.Y(), aSize.Width(), aSize.Height() );
+
+ Invalidate();
+}
+
+bool PushButton::ImplIsDefButton() const
+{
+ return bool(GetButtonState() & DrawButtonFlags::Default);
+}
+
+PushButton::PushButton( WindowType nType ) :
+ Button( nType )
+{
+ ImplInitPushButtonData();
+}
+
+PushButton::PushButton( vcl::Window* pParent, WinBits nStyle ) :
+ Button( WindowType::PUSHBUTTON )
+{
+ ImplInitPushButtonData();
+ ImplInit( pParent, nStyle );
+}
+
+void PushButton::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( !(rMEvt.IsLeft() &&
+ ImplHitTestPushButton( this, rMEvt.GetPosPixel() )) )
+ return;
+
+ StartTrackingFlags nTrackFlags = StartTrackingFlags::NONE;
+
+ if ( ( GetStyle() & WB_REPEAT ) &&
+ ! ( GetStyle() & WB_TOGGLE ) )
+ nTrackFlags |= StartTrackingFlags::ButtonRepeat;
+
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ StartTracking( nTrackFlags );
+
+ if ( nTrackFlags & StartTrackingFlags::ButtonRepeat )
+ Click();
+}
+
+void PushButton::Tracking( const TrackingEvent& rTEvt )
+{
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ if ( !(GetStyle() & WB_NOPOINTERFOCUS) && !rTEvt.IsTrackingCanceled() )
+ GrabFocus();
+
+ if ( GetStyle() & WB_TOGGLE )
+ {
+ // Don't toggle, when aborted
+ if ( !rTEvt.IsTrackingCanceled() )
+ {
+ if ( IsChecked() )
+ {
+ Check( false );
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ }
+ else
+ Check();
+ }
+ }
+ else
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+
+ Invalidate();
+
+ // do not call Click handler if aborted
+ if ( !rTEvt.IsTrackingCanceled() )
+ {
+ if ( ! ( GetStyle() & WB_REPEAT ) || ( GetStyle() & WB_TOGGLE ) )
+ Click();
+ }
+ }
+ }
+ else
+ {
+ if ( ImplHitTestPushButton( this, rTEvt.GetMouseEvent().GetPosPixel() ) )
+ {
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ if ( rTEvt.IsTrackingRepeat() && (GetStyle() & WB_REPEAT) &&
+ ! ( GetStyle() & WB_TOGGLE ) )
+ Click();
+ }
+ else
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ else
+ {
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ }
+}
+
+void PushButton::KeyInput( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ if ( !aKeyCode.GetModifier() &&
+ ((aKeyCode.GetCode() == KEY_RETURN) || (aKeyCode.GetCode() == KEY_SPACE)) )
+ {
+ if ( !(GetButtonState() & DrawButtonFlags::Pressed) )
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+
+ if ( ( GetStyle() & WB_REPEAT ) &&
+ ! ( GetStyle() & WB_TOGGLE ) )
+ Click();
+ }
+ else if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_ESCAPE) )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ else
+ Button::KeyInput( rKEvt );
+}
+
+void PushButton::KeyUp( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ if ( (GetButtonState() & DrawButtonFlags::Pressed) &&
+ ((aKeyCode.GetCode() == KEY_RETURN) || (aKeyCode.GetCode() == KEY_SPACE)) )
+ {
+ if ( GetStyle() & WB_TOGGLE )
+ {
+ if ( IsChecked() )
+ {
+ Check( false );
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ }
+ else
+ Check();
+
+ Toggle();
+ }
+ else
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+
+ Invalidate();
+
+ if ( !( GetStyle() & WB_REPEAT ) || ( GetStyle() & WB_TOGGLE ) )
+ Click();
+ }
+ else
+ Button::KeyUp( rKEvt );
+}
+
+void PushButton::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const_cast<PushButton*>(this)->Invalidate();
+}
+
+void PushButton::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ const Image& rCustomButtonImage = GetCustomButtonImage();
+ if (!!rCustomButtonImage)
+ {
+ rRenderContext.DrawImage(Point(0, 0), rCustomButtonImage);
+ return;
+ }
+ ImplDrawPushButton(rRenderContext);
+}
+
+void PushButton::Draw( OutputDevice* pDev, const Point& rPos,
+ SystemTextColorFlags nFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ tools::Rectangle aRect( aPos, aSize );
+ vcl::Font aFont = GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+
+ std::optional<StyleSettings> oOrigDevStyleSettings;
+
+ if ( nFlags & SystemTextColorFlags::Mono )
+ {
+ pDev->SetTextColor( COL_BLACK );
+ }
+ else
+ {
+ pDev->SetTextColor( GetTextColor() );
+ // DecoView uses the FaceColor...
+ AllSettings aSettings = pDev->GetSettings();
+ StyleSettings aStyleSettings = aSettings.GetStyleSettings();
+ oOrigDevStyleSettings = aStyleSettings;
+ if ( IsControlBackground() )
+ aStyleSettings.SetFaceColor( GetControlBackground() );
+ else
+ aStyleSettings.SetFaceColor( GetSettings().GetStyleSettings().GetFaceColor() );
+ aSettings.SetStyleSettings( aStyleSettings );
+ pDev->OutputDevice::SetSettings( aSettings );
+ }
+ pDev->SetTextFillColor();
+
+ DecorationView aDecoView( pDev );
+ DrawButtonFlags nButtonStyle = DrawButtonFlags::NONE;
+ if ( nFlags & SystemTextColorFlags::Mono )
+ nButtonStyle |= DrawButtonFlags::Mono;
+ if ( IsChecked() )
+ nButtonStyle |= DrawButtonFlags::Checked;
+ aRect = aDecoView.DrawButton( aRect, nButtonStyle );
+
+ ImplDrawPushButtonContent( pDev, nFlags, aRect, true, nButtonStyle );
+
+ // restore original settings (which are not affected by Push/Pop) after
+ // finished drawing
+ if (oOrigDevStyleSettings)
+ {
+ AllSettings aSettings = pDev->GetSettings();
+ aSettings.SetStyleSettings(*oOrigDevStyleSettings);
+ pDev->OutputDevice::SetSettings( aSettings );
+ }
+
+ pDev->Pop();
+}
+
+void PushButton::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void PushButton::GetFocus()
+{
+ ShowFocus( ImplGetFocusRect() );
+ SetInputContext( InputContext( GetFont() ) );
+ Button::GetFocus();
+}
+
+void PushButton::LoseFocus()
+{
+ EndSelection();
+ HideFocus();
+ Button::LoseFocus();
+}
+
+void PushButton::StateChanged( StateChangedType nType )
+{
+ Button::StateChanged( nType );
+
+ if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::Data) ||
+ (nType == StateChangedType::State) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetWindow( GetWindowType::Prev ), GetStyle() ) );
+
+ bool bIsDefButton = ( GetStyle() & WB_DEFBUTTON ) != 0;
+ bool bWasDefButton = ( GetPrevStyle() & WB_DEFBUTTON ) != 0;
+ if ( bIsDefButton != bWasDefButton )
+ ImplSetDefButton( bIsDefButton );
+
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ if ( (GetPrevStyle() & PUSHBUTTON_VIEW_STYLE) !=
+ (GetStyle() & PUSHBUTTON_VIEW_STYLE) )
+ Invalidate();
+ }
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+void PushButton::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Button::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+bool PushButton::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == NotifyEventType::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && (pMouseEvt->IsEnterWindow() || pMouseEvt->IsLeaveWindow()) )
+ {
+ // trigger redraw as mouse over state has changed
+
+ // TODO: move this to Window class or make it a member !!!
+ ControlType aCtrlType = ControlType::Generic;
+ switch( GetParent()->GetType() )
+ {
+ case WindowType::LISTBOX:
+ case WindowType::MULTILISTBOX:
+ case WindowType::TREELISTBOX:
+ aCtrlType = ControlType::Listbox;
+ 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:
+ aCtrlType = ControlType::Combobox;
+ break;
+ default:
+ break;
+ }
+
+ bool bDropDown = ( IsSymbol() && (GetSymbol()==SymbolType::SPIN_DOWN) && GetText().isEmpty() );
+
+ if( bDropDown && GetParent()->IsNativeControlSupported( aCtrlType, ControlPart::Entire) &&
+ !GetParent()->IsNativeControlSupported( aCtrlType, ControlPart::ButtonDown) )
+ {
+ vcl::Window *pBorder = GetParent()->GetWindow( GetWindowType::Border );
+ if(aCtrlType == ControlType::Combobox)
+ {
+ // only paint the button part to avoid flickering of the combobox text
+ tools::Rectangle aClipRect( Point(), GetOutputSizePixel() );
+ aClipRect.SetPos(pBorder->ScreenToOutputPixel(OutputToScreenPixel(aClipRect.TopLeft())));
+ pBorder->Invalidate( aClipRect );
+ }
+ else
+ {
+ pBorder->Invalidate( InvalidateFlags::NoErase );
+ }
+ }
+ else if( (GetStyle() & WB_FLATBUTTON) ||
+ IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Entire) )
+ {
+ Invalidate();
+ }
+ }
+ }
+
+ return Button::PreNotify(rNEvt);
+}
+
+void PushButton::Toggle()
+{
+ ImplCallEventListenersAndHandler( VclEventId::PushbuttonToggle, nullptr );
+}
+
+void PushButton::SetSymbol( SymbolType eSymbol )
+{
+ if ( meSymbol != eSymbol )
+ {
+ meSymbol = eSymbol;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void PushButton::SetSymbolAlign( SymbolAlign eAlign )
+{
+ ImplSetSymbolAlign( eAlign );
+}
+
+void PushButton::SetDropDown( PushButtonDropdownStyle nStyle )
+{
+ if ( mnDDStyle != nStyle )
+ {
+ mnDDStyle = nStyle;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void PushButton::SetState( TriState eState )
+{
+ if ( meState == eState )
+ return;
+
+ meState = eState;
+ if ( meState == TRISTATE_FALSE )
+ GetButtonState() &= ~DrawButtonFlags(DrawButtonFlags::Checked | DrawButtonFlags::DontKnow);
+ else if ( meState == TRISTATE_TRUE )
+ {
+ GetButtonState() &= ~DrawButtonFlags::DontKnow;
+ GetButtonState() |= DrawButtonFlags::Checked;
+ }
+ else // TRISTATE_INDET
+ {
+ GetButtonState() &= ~DrawButtonFlags::Checked;
+ GetButtonState() |= DrawButtonFlags::DontKnow;
+ }
+
+ CompatStateChanged( StateChangedType::State );
+ Toggle();
+}
+
+void PushButton::statusChanged(const css::frame::FeatureStateEvent& rEvent)
+{
+ Button::statusChanged(rEvent);
+ if (rEvent.State.has<bool>())
+ SetPressed(rEvent.State.get<bool>());
+}
+
+void PushButton::SetPressed( bool bPressed )
+{
+ if ( mbPressed != bPressed )
+ {
+ mbPressed = bPressed;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void PushButton::EndSelection()
+{
+ EndTracking( TrackingEventFlags::Cancel );
+ if ( !isDisposed() &&
+ GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ if ( !mbPressed )
+ Invalidate();
+ }
+}
+
+Size PushButton::CalcMinimumSize() const
+{
+ Size aSize;
+
+ if ( IsSymbol() )
+ {
+ if ( IsSmallSymbol ())
+ aSize = Size( 16, 12 );
+ else
+ aSize = Size( 26, 24 );
+ }
+ else if ( Button::HasImage() )
+ aSize = GetModeImage().GetSizePixel();
+ if( mnDDStyle == PushButtonDropdownStyle::MenuButton ||
+ mnDDStyle == PushButtonDropdownStyle::SplitMenuButton )
+ {
+ tools::Long nSymbolSize = GetTextHeight() / 2 + 1;
+ aSize.AdjustWidth(2*nSymbolSize );
+ }
+ if (!PushButton::GetText().isEmpty())
+ {
+ Size textSize = GetTextRect( tools::Rectangle( Point(), Size( 0x7fffffff, 0x7fffffff ) ),
+ PushButton::GetText(), ImplGetTextStyle( SystemTextColorFlags::NONE ) ).GetSize();
+
+ tools::Long nTextHeight = textSize.Height() * 1.15;
+
+ ImageAlign eImageAlign = GetImageAlign();
+ // tdf#142337 only considering the simple top/bottom/left/right possibilities
+ if (eImageAlign == ImageAlign::Top || eImageAlign == ImageAlign::Bottom)
+ {
+ aSize.AdjustHeight(nTextHeight);
+ aSize.setWidth(std::max(aSize.Width(), textSize.Width()));
+ }
+ else
+ {
+ aSize.AdjustWidth(textSize.Width());
+ aSize.setHeight(std::max(aSize.Height(), nTextHeight));
+ }
+ }
+
+ // cf. ImplDrawPushButton ...
+ if( (GetStyle() & WB_SMALLSTYLE) == 0 )
+ {
+ aSize.AdjustWidth(24 );
+ aSize.AdjustHeight(12 );
+ }
+
+ return CalcWindowSize( aSize );
+}
+
+Size PushButton::GetOptimalSize() const
+{
+ return CalcMinimumSize();
+}
+
+bool PushButton::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "has-default")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~WB_DEFBUTTON;
+ if (toBool(rValue))
+ nBits |= WB_DEFBUTTON;
+ SetStyle(nBits);
+ }
+ else
+ return Button::set_property(rKey, rValue);
+ return true;
+}
+
+void PushButton::ShowFocus(const tools::Rectangle& rRect)
+{
+ if (IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus))
+ {
+ PushButtonValue aControlValue;
+ aControlValue.mbIsAction = isAction();
+ tools::Rectangle aInRect(Point(), GetOutputSizePixel());
+ GetOutDev()->DrawNativeControl(ControlType::Pushbutton, ControlPart::Focus, aInRect,
+ ControlState::FOCUSED, aControlValue, OUString());
+ }
+ Button::ShowFocus(rRect);
+}
+
+void OKButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ set_id("ok");
+ PushButton::ImplInit( pParent, nStyle );
+
+ SetText( GetStandardText( StandardButtonType::OK ) );
+}
+
+OKButton::OKButton( vcl::Window* pParent, WinBits nStyle ) :
+ PushButton( WindowType::OKBUTTON )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void OKButton::Click()
+{
+ // close parent if no link set
+ if ( !GetClickHdl() )
+ {
+ vcl::Window* pParent = getNonLayoutParent(this);
+ if ( pParent->IsSystemWindow() )
+ {
+ if ( pParent->IsDialog() )
+ {
+ VclPtr<Dialog> xParent( static_cast<Dialog*>(pParent) );
+ if ( xParent->IsInExecute() )
+ xParent->EndDialog( RET_OK );
+ // prevent recursive calls
+ else if ( !xParent->IsInClose() )
+ {
+ if ( pParent->GetStyle() & WB_CLOSEABLE )
+ xParent->Close();
+ }
+ }
+ else
+ {
+ if ( pParent->GetStyle() & WB_CLOSEABLE )
+ static_cast<SystemWindow*>(pParent)->Close();
+ }
+ }
+ }
+ else
+ {
+ PushButton::Click();
+ }
+}
+
+void CancelButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ set_id("cancel");
+ PushButton::ImplInit( pParent, nStyle );
+
+ SetText( GetStandardText( StandardButtonType::Cancel ) );
+}
+
+CancelButton::CancelButton( vcl::Window* pParent, WinBits nStyle ) :
+ PushButton( WindowType::CANCELBUTTON )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void CancelButton::Click()
+{
+ // close parent if link not set
+ if ( !GetClickHdl() )
+ {
+ vcl::Window* pParent = getNonLayoutParent(this);
+ if ( pParent->IsSystemWindow() )
+ {
+ if ( pParent->IsDialog() )
+ {
+ if ( static_cast<Dialog*>(pParent)->IsInExecute() )
+ static_cast<Dialog*>(pParent)->EndDialog();
+ // prevent recursive calls
+ else if ( !static_cast<Dialog*>(pParent)->IsInClose() )
+ {
+ if ( pParent->GetStyle() & WB_CLOSEABLE )
+ static_cast<Dialog*>(pParent)->Close();
+ }
+ }
+ else
+ {
+ if ( pParent->GetStyle() & WB_CLOSEABLE )
+ static_cast<SystemWindow*>(pParent)->Close();
+ }
+ }
+ }
+ else
+ {
+ PushButton::Click();
+ }
+}
+
+CloseButton::CloseButton( vcl::Window* pParent )
+ : CancelButton(pParent, 0)
+{
+ SetText( GetStandardText( StandardButtonType::Close ) );
+}
+
+void HelpButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ set_id("help");
+ PushButton::ImplInit( pParent, nStyle | WB_NOPOINTERFOCUS );
+
+ SetText( GetStandardText( StandardButtonType::Help ) );
+}
+
+HelpButton::HelpButton( vcl::Window* pParent, WinBits nStyle ) :
+ PushButton( WindowType::HELPBUTTON )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void HelpButton::Click()
+{
+ // trigger help if no link set
+ if ( !GetClickHdl() )
+ {
+ vcl::Window* pFocusWin = Application::GetFocusWindow();
+ if ( !pFocusWin || comphelper::LibreOfficeKit::isActive() )
+ pFocusWin = this;
+
+ HelpEvent aEvt( pFocusWin->GetPointerPosPixel(), HelpEventMode::CONTEXT );
+ pFocusWin->RequestHelp( aEvt );
+ }
+ PushButton::Click();
+}
+
+void HelpButton::StateChanged( StateChangedType nStateChange )
+{
+ // Hide when we have no help URL.
+ if (comphelper::LibreOfficeKit::isActive() &&
+ officecfg::Office::Common::Help::HelpRootURL::get().isEmpty())
+ Hide();
+ else
+ PushButton::StateChanged(nStateChange);
+}
+
+void RadioButton::ImplInitRadioButtonData()
+{
+ mbChecked = false;
+ mbRadioCheck = true;
+ mbStateChanged = false;
+}
+
+void RadioButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle(getPreviousSibling(pParent), nStyle);
+ Button::ImplInit( pParent, nStyle, nullptr );
+
+ ImplInitSettings( true );
+}
+
+WinBits RadioButton::ImplInitStyle( const vcl::Window* pPrevWindow, WinBits nStyle ) const
+{
+ if ( !(nStyle & WB_NOGROUP) &&
+ (!pPrevWindow || (pPrevWindow->GetType() != WindowType::RADIOBUTTON)) )
+ nStyle |= WB_GROUP;
+ if ( !(nStyle & WB_NOTABSTOP) )
+ {
+ if ( IsChecked() )
+ nStyle |= WB_TABSTOP;
+ else
+ nStyle &= ~WB_TABSTOP;
+ }
+
+ return nStyle;
+}
+
+const vcl::Font& RadioButton::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetRadioCheckFont();
+}
+
+const Color& RadioButton::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetRadioCheckTextColor();
+}
+
+void RadioButton::ImplInitSettings( bool bBackground )
+{
+ Button::ImplInitSettings();
+
+ if ( !bBackground )
+ return;
+
+ vcl::Window* pParent = GetParent();
+ if ( !IsControlBackground() &&
+ (pParent->IsChildTransparentModeEnabled() || IsNativeControlSupported( ControlType::Radiobutton, ControlPart::Entire ) ) )
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode( ParentClipMode::NoClip );
+ SetPaintTransparent( true );
+ SetBackground();
+ if( IsNativeControlSupported( ControlType::Radiobutton, ControlPart::Entire ) )
+ mpWindowImpl->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
+ }
+ else
+ {
+ EnableChildTransparentMode( false );
+ SetParentClipMode();
+ SetPaintTransparent( false );
+
+ if ( IsControlBackground() )
+ SetBackground( GetControlBackground() );
+ else
+ SetBackground( pParent->GetBackground() );
+ }
+}
+
+void RadioButton::ImplDrawRadioButtonState(vcl::RenderContext& rRenderContext)
+{
+ bool bNativeOK = false;
+
+ // no native drawing for image radio buttons
+ if (!maImage && rRenderContext.IsNativeControlSupported(ControlType::Radiobutton, ControlPart::Entire))
+ {
+ ImplControlValue aControlValue( mbChecked ? ButtonValue::On : ButtonValue::Off );
+ tools::Rectangle aCtrlRect(maStateRect.TopLeft(), maStateRect.GetSize());
+ ControlState nState = ControlState::NONE;
+
+ if (GetButtonState() & DrawButtonFlags::Pressed)
+ nState |= ControlState::PRESSED;
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+ if (GetButtonState() & DrawButtonFlags::Default)
+ nState |= ControlState::DEFAULT;
+ if (IsEnabled())
+ nState |= ControlState::ENABLED;
+
+ if (IsMouseOver() && maMouseRect.Contains(GetPointerPosPixel()))
+ nState |= ControlState::ROLLOVER;
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Radiobutton, ControlPart::Entire, aCtrlRect,
+ nState, aControlValue, OUString());
+ }
+
+ if (bNativeOK)
+ return;
+
+ if (!maImage)
+ {
+ DrawButtonFlags nStyle = GetButtonState();
+ if (!IsEnabled())
+ nStyle |= DrawButtonFlags::Disabled;
+ if (mbChecked)
+ nStyle |= DrawButtonFlags::Checked;
+ Image aImage = GetRadioImage(rRenderContext.GetSettings(), nStyle);
+ if (IsZoom())
+ rRenderContext.DrawImage(maStateRect.TopLeft(), maStateRect.GetSize(), aImage);
+ else
+ rRenderContext.DrawImage(maStateRect.TopLeft(), aImage);
+ }
+ else
+ {
+ HideFocus();
+
+ DecorationView aDecoView(&rRenderContext);
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ tools::Rectangle aImageRect = maStateRect;
+ Size aImageSize = maImage.GetSizePixel();
+ bool bEnabled = IsEnabled();
+
+ aImageSize.setWidth( CalcZoom(aImageSize.Width()) );
+ aImageSize.setHeight( CalcZoom(aImageSize.Height()) );
+
+ aImageRect.AdjustLeft( 1 );
+ aImageRect.AdjustTop( 1 );
+ aImageRect.AdjustRight( -1 );
+ aImageRect.AdjustBottom( -1 );
+
+ // display border and selection status
+ aImageRect = aDecoView.DrawFrame(aImageRect, DrawFrameStyle::DoubleIn);
+ if ((GetButtonState() & DrawButtonFlags::Pressed) || !bEnabled)
+ rRenderContext.SetFillColor( rStyleSettings.GetFaceColor());
+ else
+ rRenderContext.SetFillColor(rStyleSettings.GetFieldColor());
+ rRenderContext.SetLineColor();
+ rRenderContext.DrawRect(aImageRect);
+
+ // display image
+ DrawImageFlags nImageStyle = DrawImageFlags::NONE;
+ if (!bEnabled)
+ nImageStyle |= DrawImageFlags::Disable;
+
+ Image* pImage = &maImage;
+
+ Point aImagePos(aImageRect.TopLeft());
+ aImagePos.AdjustX((aImageRect.GetWidth() - aImageSize.Width()) / 2 );
+ aImagePos.AdjustY((aImageRect.GetHeight() - aImageSize.Height()) / 2 );
+ if (IsZoom())
+ rRenderContext.DrawImage(aImagePos, aImageSize, *pImage, nImageStyle);
+ else
+ rRenderContext.DrawImage(aImagePos, *pImage, nImageStyle);
+
+ aImageRect.AdjustLeft( 1 );
+ aImageRect.AdjustTop( 1 );
+ aImageRect.AdjustRight( -1 );
+ aImageRect.AdjustBottom( -1 );
+
+ ImplSetFocusRect(aImageRect);
+
+ if (mbChecked)
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetHighlightColor());
+ rRenderContext.SetFillColor();
+ if ((aImageSize.Width() >= 20) || (aImageSize.Height() >= 20))
+ {
+ aImageRect.AdjustLeft( 1 );
+ aImageRect.AdjustTop( 1 );
+ aImageRect.AdjustRight( -1 );
+ aImageRect.AdjustBottom( -1 );
+ }
+ rRenderContext.DrawRect(aImageRect);
+ aImageRect.AdjustLeft( 1 );
+ aImageRect.AdjustTop( 1 );
+ aImageRect.AdjustRight( -1 );
+ aImageRect.AdjustBottom( -1 );
+ rRenderContext.DrawRect(aImageRect);
+ }
+
+ if (HasFocus())
+ ShowFocus(ImplGetFocusRect());
+ }
+}
+
+// for drawing RadioButton or CheckButton that has Text and/or Image
+void Button::ImplDrawRadioCheck(OutputDevice* pDev, WinBits nWinStyle, SystemTextColorFlags nSystemTextColorFlags,
+ const Point& rPos, const Size& rSize,
+ const Size& rImageSize, tools::Rectangle& rStateRect,
+ tools::Rectangle& rMouseRect)
+{
+ DrawTextFlags nTextStyle = Button::ImplGetTextStyle( nWinStyle, nSystemTextColorFlags );
+
+ const tools::Long nImageSep = GetDrawPixel( pDev, ImplGetImageToTextDistance() );
+ Size aSize( rSize );
+ Point aPos( rPos );
+ aPos.AdjustX(rImageSize.Width() + nImageSep );
+
+ // tdf#141761 Old (convenience?) adjustment of width may lead to empty
+ // or negative(!) Size, that needs to be avoided. The coordinate context
+ // is pixel-oriented (all Paints of Controls are, historically), so
+ // the minimum width should be '1' Pixel.
+ // Hint: nImageSep is based on Zoom (using Window::CalcZoom) and
+ // MapModes (using Window::GetDrawPixel) - so potentially a wide range
+ // of unpredictable values is possible
+ const tools::Long nWidthAdjust(rImageSize.Width() + nImageSep);
+ aSize.setWidth(std::max(static_cast<tools::Long>(1), aSize.getWidth() - nWidthAdjust));
+
+ // if the text rect height is smaller than the height of the image
+ // then for single lines the default should be centered text
+ if( (nWinStyle & (WB_TOP|WB_VCENTER|WB_BOTTOM)) == 0 &&
+ (rImageSize.Height() > rSize.Height() || ! (nWinStyle & WB_WORDBREAK) ) )
+ {
+ nTextStyle &= ~DrawTextFlags(DrawTextFlags::Top|DrawTextFlags::Bottom);
+ nTextStyle |= DrawTextFlags::VCenter;
+ aSize.setHeight( rImageSize.Height() );
+ }
+
+ ImplDrawAlignedImage( pDev, aPos, aSize, 1, nTextStyle );
+
+ rMouseRect = tools::Rectangle( aPos, aSize );
+ rMouseRect.SetLeft( rPos.X() );
+
+ rStateRect.SetLeft( rPos.X() );
+ rStateRect.SetTop( rMouseRect.Top() );
+
+ if ( aSize.Height() > rImageSize.Height() )
+ rStateRect.AdjustTop(( aSize.Height() - rImageSize.Height() ) / 2 );
+ else
+ {
+ rStateRect.AdjustTop( -(( rImageSize.Height() - aSize.Height() ) / 2) );
+ if( rStateRect.Top() < 0 )
+ rStateRect.SetTop( 0 );
+ }
+
+ rStateRect.SetRight( rStateRect.Left()+rImageSize.Width()-1 );
+ rStateRect.SetBottom( rStateRect.Top()+rImageSize.Height()-1 );
+
+ if ( rStateRect.Bottom() > rMouseRect.Bottom() )
+ rMouseRect.SetBottom( rStateRect.Bottom() );
+}
+
+void RadioButton::ImplDraw( OutputDevice* pDev, SystemTextColorFlags nSystemTextColorFlags,
+ const Point& rPos, const Size& rSize,
+ const Size& rImageSize, tools::Rectangle& rStateRect,
+ tools::Rectangle& rMouseRect )
+{
+ WinBits nWinStyle = GetStyle();
+ OUString aText( GetText() );
+
+ pDev->Push( vcl::PushFlags::CLIPREGION );
+ pDev->IntersectClipRegion( tools::Rectangle( rPos, rSize ) );
+
+ // no image radio button
+ if ( !maImage )
+ {
+ if (!aText.isEmpty() || HasImage())
+ {
+ Button::ImplDrawRadioCheck(pDev, nWinStyle, nSystemTextColorFlags,
+ rPos, rSize, rImageSize,
+ rStateRect, rMouseRect);
+ }
+ else
+ {
+ rStateRect.SetLeft( rPos.X() );
+ if ( nWinStyle & WB_VCENTER )
+ rStateRect.SetTop( rPos.Y()+((rSize.Height()-rImageSize.Height())/2) );
+ else if ( nWinStyle & WB_BOTTOM )
+ rStateRect.SetTop( rPos.Y()+rSize.Height()-rImageSize.Height() ); //-1;
+ else
+ rStateRect.SetTop( rPos.Y() );
+ rStateRect.SetRight( rStateRect.Left()+rImageSize.Width()-1 );
+ rStateRect.SetBottom( rStateRect.Top()+rImageSize.Height()-1 );
+ rMouseRect = rStateRect;
+
+ ImplSetFocusRect( rStateRect );
+ }
+ }
+ else
+ {
+ bool bTopImage = (nWinStyle & WB_TOP) != 0;
+ Size aImageSize = maImage.GetSizePixel();
+ tools::Rectangle aImageRect( rPos, rSize );
+ tools::Long nTextHeight = pDev->GetTextHeight();
+ tools::Long nTextWidth = pDev->GetCtrlTextWidth( aText );
+
+ // calculate position and sizes
+ if (!aText.isEmpty())
+ {
+ Size aTmpSize( (aImageSize.Width()+8), (aImageSize.Height()+8) );
+ if ( bTopImage )
+ {
+ aImageRect.SetLeft( (rSize.Width()-aTmpSize.Width())/2 );
+ aImageRect.SetTop( (rSize.Height()-(aTmpSize.Height()+nTextHeight+6))/2 );
+ }
+ else
+ aImageRect.SetTop( (rSize.Height()-aTmpSize.Height())/2 );
+
+ aImageRect.SetRight( aImageRect.Left()+aTmpSize.Width() );
+ aImageRect.SetBottom( aImageRect.Top()+aTmpSize.Height() );
+
+ // display text
+ Point aTxtPos = rPos;
+ if ( bTopImage )
+ {
+ aTxtPos.AdjustX((rSize.Width()-nTextWidth)/2 );
+ aTxtPos.AdjustY(aImageRect.Bottom()+6 );
+ }
+ else
+ {
+ aTxtPos.AdjustX(aImageRect.Right()+8 );
+ aTxtPos.AdjustY((rSize.Height()-nTextHeight)/2 );
+ }
+ pDev->DrawCtrlText( aTxtPos, aText, 0, aText.getLength() );
+ }
+
+ rMouseRect = aImageRect;
+ rStateRect = aImageRect;
+ }
+
+ pDev->Pop();
+}
+
+void RadioButton::ImplDrawRadioButton(vcl::RenderContext& rRenderContext)
+{
+ HideFocus();
+
+ Size aImageSize;
+ if (!maImage)
+ aImageSize = ImplGetRadioImageSize();
+ else
+ aImageSize = maImage.GetSizePixel();
+
+ aImageSize.setWidth( CalcZoom(aImageSize.Width()) );
+ aImageSize.setHeight( CalcZoom(aImageSize.Height()) );
+
+ // Draw control text
+ ImplDraw(&rRenderContext, SystemTextColorFlags::NONE, Point(), GetOutputSizePixel(),
+ aImageSize, maStateRect, maMouseRect);
+
+ if (!maImage && HasFocus())
+ ShowFocus(ImplGetFocusRect());
+
+ ImplDrawRadioButtonState(rRenderContext);
+}
+
+void RadioButton::group(RadioButton &rOther)
+{
+ if (&rOther == this)
+ return;
+
+ if (!m_xGroup)
+ {
+ m_xGroup = std::make_shared<std::vector<VclPtr<RadioButton> >>();
+ m_xGroup->push_back(this);
+ }
+
+ auto aFind = std::find(m_xGroup->begin(), m_xGroup->end(), VclPtr<RadioButton>(&rOther));
+ if (aFind == m_xGroup->end())
+ {
+ m_xGroup->push_back(&rOther);
+
+ if (rOther.m_xGroup)
+ {
+ std::vector< VclPtr<RadioButton> > aOthers(rOther.GetRadioButtonGroup(false));
+ //make all members of the group share the same button group
+ for (auto const& elem : aOthers)
+ {
+ aFind = std::find(m_xGroup->begin(), m_xGroup->end(), elem);
+ if (aFind == m_xGroup->end())
+ m_xGroup->push_back(elem);
+ }
+ }
+
+ //make all members of the group share the same button group
+ for (VclPtr<RadioButton> const & pButton : *m_xGroup)
+ {
+ pButton->m_xGroup = m_xGroup;
+ }
+ }
+
+ //if this one is checked, uncheck all the others
+ if (mbChecked)
+ ImplUncheckAllOther();
+}
+
+std::vector< VclPtr<RadioButton> > RadioButton::GetRadioButtonGroup(bool bIncludeThis) const
+{
+ if (m_xGroup)
+ {
+ if (bIncludeThis)
+ return *m_xGroup;
+ std::vector< VclPtr<RadioButton> > aGroup;
+ for (VclPtr<RadioButton> const & pRadioButton : *m_xGroup)
+ {
+ if (pRadioButton == this)
+ continue;
+ aGroup.push_back(pRadioButton);
+ }
+ return aGroup;
+ }
+
+ std::vector<VclPtr<RadioButton>> aGroup;
+ if (mbUsesExplicitGroup)
+ return aGroup;
+
+ //old-school
+
+ // go back to first in group;
+ vcl::Window* pFirst = const_cast<RadioButton*>(this);
+ while( ( pFirst->GetStyle() & WB_GROUP ) == 0 )
+ {
+ vcl::Window* pWindow = pFirst->GetWindow( GetWindowType::Prev );
+ if( pWindow )
+ pFirst = pWindow;
+ else
+ break;
+ }
+ // insert radiobuttons up to next group
+ do
+ {
+ if( pFirst->GetType() == WindowType::RADIOBUTTON )
+ {
+ if( pFirst != this || bIncludeThis )
+ aGroup.emplace_back(static_cast<RadioButton*>(pFirst) );
+ }
+ pFirst = pFirst->GetWindow( GetWindowType::Next );
+ } while( pFirst && ( ( pFirst->GetStyle() & WB_GROUP ) == 0 ) );
+
+ return aGroup;
+}
+
+void RadioButton::ImplUncheckAllOther()
+{
+ mpWindowImpl->mnStyle |= WB_TABSTOP;
+
+ std::vector<VclPtr<RadioButton> > aGroup(GetRadioButtonGroup(false));
+ // iterate over radio button group and checked buttons
+ for (VclPtr<RadioButton>& pWindow : aGroup)
+ {
+ if ( pWindow->IsChecked() )
+ {
+ pWindow->SetState( false );
+ if ( pWindow->isDisposed() )
+ return;
+ }
+
+ // not inside if clause to always remove wrongly set WB_TABSTOPS
+ pWindow->mpWindowImpl->mnStyle &= ~WB_TABSTOP;
+ }
+}
+
+void RadioButton::ImplCallClick( bool bGrabFocus, GetFocusFlags nFocusFlags )
+{
+ mbStateChanged = !mbChecked;
+ mbChecked = true;
+ mpWindowImpl->mnStyle |= WB_TABSTOP;
+ Invalidate();
+ VclPtr<vcl::Window> xWindow = this;
+ if ( mbRadioCheck )
+ ImplUncheckAllOther();
+ if ( xWindow->isDisposed() )
+ return;
+ if ( bGrabFocus )
+ ImplGrabFocus( nFocusFlags );
+ if ( xWindow->isDisposed() )
+ return;
+ if ( mbStateChanged )
+ Toggle();
+ if ( xWindow->isDisposed() )
+ return;
+ Click();
+ if ( xWindow->isDisposed() )
+ return;
+ mbStateChanged = false;
+}
+
+RadioButton::RadioButton(vcl::Window* pParent, bool bUsesExplicitGroup, WinBits nStyle)
+ : Button(WindowType::RADIOBUTTON)
+ , mbUsesExplicitGroup(bUsesExplicitGroup)
+{
+ ImplInitRadioButtonData();
+ ImplInit( pParent, nStyle );
+}
+
+RadioButton::~RadioButton()
+{
+ disposeOnce();
+}
+
+void RadioButton::dispose()
+{
+ if (m_xGroup)
+ {
+ std::erase(*m_xGroup, VclPtr<RadioButton>(this));
+ m_xGroup.reset();
+ }
+ Button::dispose();
+}
+
+void RadioButton::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( rMEvt.IsLeft() && maMouseRect.Contains( rMEvt.GetPosPixel() ) )
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ StartTracking();
+ return;
+ }
+
+ Button::MouseButtonDown( rMEvt );
+}
+
+void RadioButton::Tracking( const TrackingEvent& rTEvt )
+{
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ if ( !(GetStyle() & WB_NOPOINTERFOCUS) && !rTEvt.IsTrackingCanceled() )
+ GrabFocus();
+
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+
+ // do not call click handler if aborted
+ if ( !rTEvt.IsTrackingCanceled() )
+ ImplCallClick();
+ else
+ {
+ Invalidate();
+ }
+ }
+ }
+ else
+ {
+ if ( maMouseRect.Contains( rTEvt.GetMouseEvent().GetPosPixel() ) )
+ {
+ if ( !(GetButtonState() & DrawButtonFlags::Pressed) )
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ else
+ {
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ }
+}
+
+void RadioButton::KeyInput( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ if ( !aKeyCode.GetModifier() && (aKeyCode.GetCode() == KEY_SPACE) )
+ {
+ if ( !(GetButtonState() & DrawButtonFlags::Pressed) )
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ else if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_ESCAPE) )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ else
+ Button::KeyInput( rKEvt );
+}
+
+void RadioButton::KeyUp( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_SPACE) )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ ImplCallClick();
+ }
+ else
+ Button::KeyUp( rKEvt );
+}
+
+void RadioButton::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const_cast<RadioButton*>(this)->Invalidate();
+}
+
+void RadioButton::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ ImplDrawRadioButton(rRenderContext);
+}
+
+void RadioButton::Draw( OutputDevice* pDev, const Point& rPos,
+ SystemTextColorFlags nFlags )
+{
+ if ( !maImage )
+ {
+ MapMode aResMapMode( MapUnit::Map100thMM );
+ Size aSize = GetSizePixel();
+ Size aImageSize = pDev->LogicToPixel( Size( 300, 300 ), aResMapMode );
+ Size aBrd1Size = pDev->LogicToPixel( Size( 20, 20 ), aResMapMode );
+ Size aBrd2Size = pDev->LogicToPixel( Size( 60, 60 ), aResMapMode );
+ vcl::Font aFont = GetDrawPixelFont( pDev );
+ tools::Rectangle aStateRect;
+ tools::Rectangle aMouseRect;
+
+ aImageSize.setWidth( CalcZoom( aImageSize.Width() ) );
+ aImageSize.setHeight( CalcZoom( aImageSize.Height() ) );
+ aBrd1Size.setWidth( CalcZoom( aBrd1Size.Width() ) );
+ aBrd1Size.setHeight( CalcZoom( aBrd1Size.Height() ) );
+ aBrd2Size.setWidth( CalcZoom( aBrd2Size.Width() ) );
+ aBrd2Size.setHeight( CalcZoom( aBrd2Size.Height() ) );
+
+ if ( !aBrd1Size.Width() )
+ aBrd1Size.setWidth( 1 );
+ if ( !aBrd1Size.Height() )
+ aBrd1Size.setHeight( 1 );
+ if ( !aBrd2Size.Width() )
+ aBrd2Size.setWidth( 1 );
+ if ( !aBrd2Size.Height() )
+ aBrd2Size.setHeight( 1 );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ if ( nFlags & SystemTextColorFlags::Mono )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ pDev->SetTextColor( GetTextColor() );
+ pDev->SetTextFillColor();
+
+ ImplDraw( pDev, nFlags, rPos, aSize,
+ aImageSize, aStateRect, aMouseRect );
+
+ Point aCenterPos = aStateRect.Center();
+ tools::Long nRadX = aImageSize.Width()/2;
+ tools::Long nRadY = aImageSize.Height()/2;
+
+ pDev->SetLineColor();
+ pDev->SetFillColor( COL_BLACK );
+ pDev->DrawPolygon( tools::Polygon( aCenterPos, nRadX, nRadY ) );
+ nRadX -= aBrd1Size.Width();
+ nRadY -= aBrd1Size.Height();
+ pDev->SetFillColor( COL_WHITE );
+ pDev->DrawPolygon( tools::Polygon( aCenterPos, nRadX, nRadY ) );
+ if ( mbChecked )
+ {
+ nRadX -= aBrd1Size.Width();
+ nRadY -= aBrd1Size.Height();
+ if ( !nRadX )
+ nRadX = 1;
+ if ( !nRadY )
+ nRadY = 1;
+ pDev->SetFillColor( COL_BLACK );
+ pDev->DrawPolygon( tools::Polygon( aCenterPos, nRadX, nRadY ) );
+ }
+
+ pDev->Pop();
+ }
+ else
+ {
+ OSL_FAIL( "RadioButton::Draw() - not implemented for RadioButton with Image" );
+ }
+}
+
+void RadioButton::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void RadioButton::GetFocus()
+{
+ ShowFocus( ImplGetFocusRect() );
+ SetInputContext( InputContext( GetFont() ) );
+ Button::GetFocus();
+}
+
+void RadioButton::LoseFocus()
+{
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+
+ HideFocus();
+ Button::LoseFocus();
+}
+
+void RadioButton::StateChanged( StateChangedType nType )
+{
+ Button::StateChanged( nType );
+
+ if ( nType == StateChangedType::State )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate( maStateRect );
+ }
+ else if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::Data) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetWindow( GetWindowType::Prev ), GetStyle() ) );
+
+ if ( (GetPrevStyle() & RADIOBUTTON_VIEW_STYLE) !=
+ (GetStyle() & RADIOBUTTON_VIEW_STYLE) )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+void RadioButton::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Button::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+bool RadioButton::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == NotifyEventType::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
+ {
+ // trigger redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::Radiobutton, ControlPart::Entire) )
+ {
+ if (maMouseRect.Contains(GetPointerPosPixel()) != maMouseRect.Contains(GetLastPointerPosPixel()) ||
+ pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
+ {
+ Invalidate( maStateRect );
+ }
+ }
+ }
+ }
+
+ return Button::PreNotify(rNEvt);
+}
+
+void RadioButton::Toggle()
+{
+ ImplCallEventListenersAndHandler( VclEventId::RadiobuttonToggle, [this] () { maToggleHdl.Call(*this); } );
+}
+
+void RadioButton::SetModeRadioImage( const Image& rImage )
+{
+ if ( rImage != maImage )
+ {
+ maImage = rImage;
+ CompatStateChanged( StateChangedType::Data );
+ queue_resize();
+ }
+}
+
+
+void RadioButton::SetState( bool bCheck )
+{
+ // carry the TabStop flag along correctly
+ if ( bCheck )
+ mpWindowImpl->mnStyle |= WB_TABSTOP;
+ else
+ mpWindowImpl->mnStyle &= ~WB_TABSTOP;
+
+ if ( mbChecked != bCheck )
+ {
+ mbChecked = bCheck;
+ CompatStateChanged( StateChangedType::State );
+ Toggle();
+ }
+}
+
+bool RadioButton::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "active")
+ SetState(toBool(rValue));
+ else if (rKey == "image-position")
+ {
+ WinBits nBits = GetStyle();
+ if (rValue == "left")
+ {
+ nBits &= ~(WB_CENTER | WB_RIGHT);
+ nBits |= WB_LEFT;
+ }
+ else if (rValue == "right")
+ {
+ nBits &= ~(WB_CENTER | WB_LEFT);
+ nBits |= WB_RIGHT;
+ }
+ else if (rValue == "top")
+ {
+ nBits &= ~(WB_VCENTER | WB_BOTTOM);
+ nBits |= WB_TOP;
+ }
+ else if (rValue == "bottom")
+ {
+ nBits &= ~(WB_VCENTER | WB_TOP);
+ nBits |= WB_BOTTOM;
+ }
+ //It's rather mad to have to set these bits when there is the other
+ //image align. Looks like e.g. the radiobuttons etc weren't converted
+ //over to image align fully.
+ SetStyle(nBits);
+ //Deliberate to set the sane ImageAlign property
+ return Button::set_property(rKey, rValue);
+ }
+ else
+ return Button::set_property(rKey, rValue);
+ return true;
+}
+
+void RadioButton::Check( bool bCheck )
+{
+ // TabStop-Flag richtig mitfuehren
+ if ( bCheck )
+ mpWindowImpl->mnStyle |= WB_TABSTOP;
+ else
+ mpWindowImpl->mnStyle &= ~WB_TABSTOP;
+
+ if ( mbChecked == bCheck )
+ return;
+
+ mbChecked = bCheck;
+ VclPtr<vcl::Window> xWindow = this;
+ CompatStateChanged( StateChangedType::State );
+ if ( xWindow->isDisposed() )
+ return;
+ if ( bCheck && mbRadioCheck )
+ ImplUncheckAllOther();
+ if ( xWindow->isDisposed() )
+ return;
+ Toggle();
+}
+
+tools::Long Button::ImplGetImageToTextDistance() const
+{
+ // 4 pixels, but take zoom into account, so the text doesn't "jump" relative to surrounding elements,
+ // which might have been aligned with the text of the check box
+ return CalcZoom( 4 );
+}
+
+Size RadioButton::ImplGetRadioImageSize() const
+{
+ Size aSize;
+ bool bDefaultSize = true;
+ if( IsNativeControlSupported( ControlType::Radiobutton, ControlPart::Entire ) )
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), GetSizePixel() );
+ tools::Rectangle aBoundingRgn, aContentRgn;
+
+ // get native size of a radio button
+ if( GetNativeControlRegion( ControlType::Radiobutton, ControlPart::Entire, aCtrlRegion,
+ ControlState::DEFAULT|ControlState::ENABLED,
+ aControlValue,
+ aBoundingRgn, aContentRgn ) )
+ {
+ aSize = aContentRgn.GetSize();
+ bDefaultSize = false;
+ }
+ }
+ if( bDefaultSize )
+ aSize = GetRadioImage( GetSettings(), DrawButtonFlags::NONE ).GetSizePixel();
+ return aSize;
+}
+
+static void LoadThemedImageList(const StyleSettings &rStyleSettings,
+ std::vector<Image>& rList, const std::vector<OUString> &rResources)
+{
+ Color aColorAry1[6];
+ Color aColorAry2[6];
+ aColorAry1[0] = Color( 0xC0, 0xC0, 0xC0 );
+ aColorAry1[1] = Color( 0xFF, 0xFF, 0x00 );
+ aColorAry1[2] = Color( 0xFF, 0xFF, 0xFF );
+ aColorAry1[3] = Color( 0x80, 0x80, 0x80 );
+ aColorAry1[4] = Color( 0x00, 0x00, 0x00 );
+ aColorAry1[5] = Color( 0x00, 0xFF, 0x00 );
+ aColorAry2[0] = rStyleSettings.GetFaceColor();
+ aColorAry2[1] = rStyleSettings.GetWindowColor();
+ aColorAry2[2] = rStyleSettings.GetLightColor();
+ aColorAry2[3] = rStyleSettings.GetShadowColor();
+ aColorAry2[4] = rStyleSettings.GetDarkShadowColor();
+ aColorAry2[5] = rStyleSettings.GetWindowTextColor();
+
+ static_assert( sizeof(aColorAry1) == sizeof(aColorAry2), "aColorAry1 must match aColorAry2" );
+
+ for (const auto &a : rResources)
+ {
+ BitmapEx aBmpEx(a);
+ aBmpEx.Replace(aColorAry1, aColorAry2, SAL_N_ELEMENTS(aColorAry1));
+ rList.emplace_back(aBmpEx);
+ }
+}
+
+Image RadioButton::GetRadioImage( const AllSettings& rSettings, DrawButtonFlags nFlags )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ const StyleSettings& rStyleSettings = rSettings.GetStyleSettings();
+ sal_uInt16 nStyle = 0;
+
+ if ( rStyleSettings.GetOptions() & StyleSettingsOptions::Mono )
+ nStyle = STYLE_RADIOBUTTON_MONO;
+
+ if ( pSVData->maCtrlData.maRadioImgList.empty() ||
+ (pSVData->maCtrlData.mnRadioStyle != nStyle) ||
+ (pSVData->maCtrlData.mnLastRadioFColor != rStyleSettings.GetFaceColor()) ||
+ (pSVData->maCtrlData.mnLastRadioWColor != rStyleSettings.GetWindowColor()) ||
+ (pSVData->maCtrlData.mnLastRadioLColor != rStyleSettings.GetLightColor()) )
+ {
+ pSVData->maCtrlData.maRadioImgList.clear();
+
+ pSVData->maCtrlData.mnLastRadioFColor = rStyleSettings.GetFaceColor();
+ pSVData->maCtrlData.mnLastRadioWColor = rStyleSettings.GetWindowColor();
+ pSVData->maCtrlData.mnLastRadioLColor = rStyleSettings.GetLightColor();
+
+ std::vector<OUString> aResources;
+ if (nStyle)
+ {
+ aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO1);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO2);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO3);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO4);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO5);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIOMONO6);
+ }
+ else
+ {
+ aResources.emplace_back(SV_RESID_BITMAP_RADIO1);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIO2);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIO3);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIO4);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIO5);
+ aResources.emplace_back(SV_RESID_BITMAP_RADIO6);
+ }
+ LoadThemedImageList( rStyleSettings, pSVData->maCtrlData.maRadioImgList, aResources);
+ pSVData->maCtrlData.mnRadioStyle = nStyle;
+ }
+
+ sal_uInt16 nIndex;
+ if ( nFlags & DrawButtonFlags::Disabled )
+ {
+ if ( nFlags & DrawButtonFlags::Checked )
+ nIndex = 5;
+ else
+ nIndex = 4;
+ }
+ else if ( nFlags & DrawButtonFlags::Pressed )
+ {
+ if ( nFlags & DrawButtonFlags::Checked )
+ nIndex = 3;
+ else
+ nIndex = 2;
+ }
+ else
+ {
+ if ( nFlags & DrawButtonFlags::Checked )
+ nIndex = 1;
+ else
+ nIndex = 0;
+ }
+ return pSVData->maCtrlData.maRadioImgList[nIndex];
+}
+
+void RadioButton::ImplAdjustNWFSizes()
+{
+ GetOutDev()->Push( vcl::PushFlags::MAPMODE );
+ SetMapMode(MapMode(MapUnit::MapPixel));
+
+ ImplControlValue aControlValue;
+ Size aCurSize( GetSizePixel() );
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), aCurSize );
+ tools::Rectangle aBoundingRgn, aContentRgn;
+
+ // get native size of a radiobutton
+ if( GetNativeControlRegion( ControlType::Radiobutton, ControlPart::Entire, aCtrlRegion,
+ ControlState::DEFAULT|ControlState::ENABLED, aControlValue,
+ aBoundingRgn, aContentRgn ) )
+ {
+ Size aSize = aContentRgn.GetSize();
+
+ if( aSize.Height() > aCurSize.Height() )
+ {
+ aCurSize.setHeight( aSize.Height() );
+ SetSizePixel( aCurSize );
+ }
+ }
+
+ GetOutDev()->Pop();
+}
+
+Size RadioButton::CalcMinimumSize(tools::Long nMaxWidth) const
+{
+ Size aSize;
+ if ( !maImage )
+ aSize = ImplGetRadioImageSize();
+ else
+ {
+ aSize = maImage.GetSizePixel();
+ aSize.AdjustWidth(8);
+ aSize.AdjustHeight(8);
+ }
+
+ if (Button::HasImage())
+ {
+ Size aImgSize = GetModeImage().GetSizePixel();
+ aSize = Size(std::max(aImgSize.Width(), aSize.Width()),
+ std::max(aImgSize.Height(), aSize.Height()));
+ }
+
+ OUString aText = GetText();
+ if (!aText.isEmpty())
+ {
+ bool bTopImage = (GetStyle() & WB_TOP) != 0;
+
+ Size aTextSize = GetTextRect( tools::Rectangle( Point(), Size( nMaxWidth > 0 ? nMaxWidth : 0x7fffffff, 0x7fffffff ) ),
+ aText, FixedText::ImplGetTextStyle( GetStyle() ) ).GetSize();
+
+ aSize.AdjustWidth(2 ); // for focus rect
+
+ if (!bTopImage)
+ {
+ aSize.AdjustWidth(ImplGetImageToTextDistance() );
+ aSize.AdjustWidth(aTextSize.Width() );
+ if ( aSize.Height() < aTextSize.Height() )
+ aSize.setHeight( aTextSize.Height() );
+ }
+ else
+ {
+ aSize.AdjustHeight(6 );
+ aSize.AdjustHeight(GetTextHeight() );
+ if ( aSize.Width() < aTextSize.Width() )
+ aSize.setWidth( aTextSize.Width() );
+ }
+ }
+
+ return CalcWindowSize( aSize );
+}
+
+Size RadioButton::GetOptimalSize() const
+{
+ return CalcMinimumSize();
+}
+
+void RadioButton::ShowFocus(const tools::Rectangle& rRect)
+{
+ if (IsNativeControlSupported(ControlType::Radiobutton, ControlPart::Focus))
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aInRect(Point(0, 0), GetSizePixel());
+
+ aInRect.SetLeft( rRect.Left() ); // exclude the radio element itself from the focusrect
+
+ GetOutDev()->DrawNativeControl(ControlType::Radiobutton, ControlPart::Focus, aInRect,
+ ControlState::FOCUSED, aControlValue, OUString());
+ }
+ Button::ShowFocus(rRect);
+}
+
+void RadioButton::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Button::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("checked", IsChecked());
+
+ OUString sGroupId;
+ std::vector<VclPtr<RadioButton>> aGroup = GetRadioButtonGroup();
+ for(const auto& pButton : aGroup)
+ sGroupId += pButton->get_id();
+
+ if (!sGroupId.isEmpty())
+ rJsonWriter.put("group", sGroupId);
+
+ if (!!maImage)
+ {
+ SvMemoryStream aOStm(6535, 6535);
+ if(GraphicConverter::Export(aOStm, maImage.GetBitmapEx(), ConvertDataFormat::PNG) == ERRCODE_NONE)
+ {
+ css::uno::Sequence<sal_Int8> aSeq( static_cast<sal_Int8 const *>(aOStm.GetData()), aOStm.Tell());
+ OStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ rJsonWriter.put("image", aBuffer);
+ }
+ }
+}
+
+FactoryFunction RadioButton::GetUITestFactory() const
+{
+ return RadioButtonUIObject::create;
+}
+
+void CheckBox::ImplInitCheckBoxData()
+{
+ meState = TRISTATE_FALSE;
+ mbTriState = false;
+}
+
+void CheckBox::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle(getPreviousSibling(pParent), nStyle);
+ Button::ImplInit( pParent, nStyle, nullptr );
+
+ ImplInitSettings( true );
+}
+
+WinBits CheckBox::ImplInitStyle( const vcl::Window* pPrevWindow, WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+ if ( !(nStyle & WB_NOGROUP) &&
+ (!pPrevWindow || (pPrevWindow->GetType() != WindowType::CHECKBOX)) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+const vcl::Font& CheckBox::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetRadioCheckFont();
+}
+
+const Color& CheckBox::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetRadioCheckTextColor();
+}
+
+void CheckBox::ImplInitSettings( bool bBackground )
+{
+ Button::ImplInitSettings();
+
+ if ( !bBackground )
+ return;
+
+ vcl::Window* pParent = GetParent();
+ if ( !IsControlBackground() &&
+ (pParent->IsChildTransparentModeEnabled() || IsNativeControlSupported( ControlType::Checkbox, ControlPart::Entire ) ) )
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode( ParentClipMode::NoClip );
+ SetPaintTransparent( true );
+ SetBackground();
+ if( IsNativeControlSupported( ControlType::Checkbox, ControlPart::Entire ) )
+ ImplGetWindowImpl()->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
+ }
+ else
+ {
+ EnableChildTransparentMode( false );
+ SetParentClipMode();
+ SetPaintTransparent( false );
+
+ if ( IsControlBackground() )
+ SetBackground( GetControlBackground() );
+ else
+ SetBackground( pParent->GetBackground() );
+ }
+}
+
+void CheckBox::ImplDrawCheckBoxState(vcl::RenderContext& rRenderContext)
+{
+ bool bNativeOK = rRenderContext.IsNativeControlSupported(ControlType::Checkbox, ControlPart::Entire);
+ if (bNativeOK)
+ {
+ ImplControlValue aControlValue(meState == TRISTATE_TRUE ? ButtonValue::On : ButtonValue::Off);
+ tools::Rectangle aCtrlRegion(maStateRect);
+ ControlState nState = ControlState::NONE;
+
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+ if (GetButtonState() & DrawButtonFlags::Default)
+ nState |= ControlState::DEFAULT;
+ if (GetButtonState() & DrawButtonFlags::Pressed)
+ nState |= ControlState::PRESSED;
+ if (IsEnabled())
+ nState |= ControlState::ENABLED;
+
+ if (meState == TRISTATE_TRUE)
+ aControlValue.setTristateVal(ButtonValue::On);
+ else if (meState == TRISTATE_INDET)
+ aControlValue.setTristateVal(ButtonValue::Mixed);
+
+ if (IsMouseOver() && maMouseRect.Contains(GetPointerPosPixel()))
+ nState |= ControlState::ROLLOVER;
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Checkbox, ControlPart::Entire, aCtrlRegion,
+ nState, aControlValue, OUString());
+ }
+
+ if (bNativeOK)
+ return;
+
+ DrawButtonFlags nStyle = GetButtonState();
+ if (!IsEnabled())
+ nStyle |= DrawButtonFlags::Disabled;
+ if (meState == TRISTATE_INDET)
+ nStyle |= DrawButtonFlags::DontKnow;
+ else if (meState == TRISTATE_TRUE)
+ nStyle |= DrawButtonFlags::Checked;
+ Image aImage = GetCheckImage(GetSettings(), nStyle);
+ if (IsZoom())
+ rRenderContext.DrawImage(maStateRect.TopLeft(), maStateRect.GetSize(), aImage);
+ else
+ rRenderContext.DrawImage(maStateRect.TopLeft(), aImage);
+}
+
+void CheckBox::ImplDraw( OutputDevice* pDev, SystemTextColorFlags nSystemTextColorFlags,
+ const Point& rPos, const Size& rSize,
+ const Size& rImageSize, tools::Rectangle& rStateRect,
+ tools::Rectangle& rMouseRect )
+{
+ WinBits nWinStyle = GetStyle();
+ OUString aText( GetText() );
+
+ pDev->Push( vcl::PushFlags::CLIPREGION | vcl::PushFlags::LINECOLOR );
+ pDev->IntersectClipRegion( tools::Rectangle( rPos, rSize ) );
+
+ if (!aText.isEmpty() || HasImage())
+ {
+ Button::ImplDrawRadioCheck(pDev, nWinStyle, nSystemTextColorFlags,
+ rPos, rSize, rImageSize,
+ rStateRect, rMouseRect);
+ }
+ else
+ {
+ rStateRect.SetLeft( rPos.X() );
+ if ( nWinStyle & WB_VCENTER )
+ rStateRect.SetTop( rPos.Y()+((rSize.Height()-rImageSize.Height())/2) );
+ else if ( nWinStyle & WB_BOTTOM )
+ rStateRect.SetTop( rPos.Y()+rSize.Height()-rImageSize.Height() );
+ else
+ rStateRect.SetTop( rPos.Y() );
+ rStateRect.SetRight( rStateRect.Left()+rImageSize.Width()-1 );
+ rStateRect.SetBottom( rStateRect.Top()+rImageSize.Height()-1 );
+ // provide space for focusrect
+ // note: this assumes that the control's size was adjusted
+ // accordingly in Get/LoseFocus, so the onscreen position won't change
+ if( HasFocus() )
+ rStateRect.Move( 1, 1 );
+ rMouseRect = rStateRect;
+
+ ImplSetFocusRect( rStateRect );
+ }
+
+ pDev->Pop();
+}
+
+void CheckBox::ImplDrawCheckBox(vcl::RenderContext& rRenderContext)
+{
+ Size aImageSize = ImplGetCheckImageSize();
+ aImageSize.setWidth( CalcZoom( aImageSize.Width() ) );
+ aImageSize.setHeight( CalcZoom( aImageSize.Height() ) );
+
+ HideFocus();
+
+ ImplDraw(&rRenderContext, SystemTextColorFlags::NONE, Point(), GetOutputSizePixel(),
+ aImageSize, maStateRect, maMouseRect);
+
+ ImplDrawCheckBoxState(rRenderContext);
+ if (HasFocus())
+ ShowFocus(ImplGetFocusRect());
+}
+
+void CheckBox::ImplCheck()
+{
+ TriState eNewState;
+ if ( meState == TRISTATE_FALSE )
+ eNewState = TRISTATE_TRUE;
+ else if ( !mbTriState )
+ eNewState = TRISTATE_FALSE;
+ else if ( meState == TRISTATE_TRUE )
+ eNewState = TRISTATE_INDET;
+ else
+ eNewState = TRISTATE_FALSE;
+ meState = eNewState;
+
+ VclPtr<vcl::Window> xWindow = this;
+ Invalidate();
+ Toggle();
+ if ( xWindow->isDisposed() )
+ return;
+ Click();
+}
+
+CheckBox::CheckBox( vcl::Window* pParent, WinBits nStyle ) :
+ Button( WindowType::CHECKBOX )
+{
+ ImplInitCheckBoxData();
+ ImplInit( pParent, nStyle );
+}
+
+void CheckBox::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( rMEvt.IsLeft() && maMouseRect.Contains( rMEvt.GetPosPixel() ) )
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ StartTracking();
+ return;
+ }
+
+ Button::MouseButtonDown( rMEvt );
+}
+
+void CheckBox::Tracking( const TrackingEvent& rTEvt )
+{
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ if ( !(GetStyle() & WB_NOPOINTERFOCUS) && !rTEvt.IsTrackingCanceled() )
+ GrabFocus();
+
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+
+ // do not call click handler if aborted
+ if ( !rTEvt.IsTrackingCanceled() )
+ ImplCheck();
+ else
+ {
+ Invalidate();
+ }
+ }
+ }
+ else
+ {
+ if ( maMouseRect.Contains( rTEvt.GetMouseEvent().GetPosPixel() ) )
+ {
+ if ( !(GetButtonState() & DrawButtonFlags::Pressed) )
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ else
+ {
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ }
+}
+
+void CheckBox::KeyInput( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ if ( !aKeyCode.GetModifier() && (aKeyCode.GetCode() == KEY_SPACE) )
+ {
+ if ( !(GetButtonState() & DrawButtonFlags::Pressed) )
+ {
+ GetButtonState() |= DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ }
+ else if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_ESCAPE) )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+ else
+ Button::KeyInput( rKEvt );
+}
+
+void CheckBox::KeyUp( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ if ( (GetButtonState() & DrawButtonFlags::Pressed) && (aKeyCode.GetCode() == KEY_SPACE) )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ ImplCheck();
+ }
+ else
+ Button::KeyUp( rKEvt );
+}
+
+void CheckBox::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const_cast<CheckBox*>(this)->Invalidate();
+}
+
+void CheckBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImplDrawCheckBox(rRenderContext);
+}
+
+void CheckBox::Draw( OutputDevice* pDev, const Point& rPos,
+ SystemTextColorFlags nFlags )
+{
+ MapMode aResMapMode( MapUnit::Map100thMM );
+ Size aSize = GetSizePixel();
+ Size aImageSize = pDev->LogicToPixel( Size( 300, 300 ), aResMapMode );
+ Size aBrd1Size = pDev->LogicToPixel( Size( 20, 20 ), aResMapMode );
+ Size aBrd2Size = pDev->LogicToPixel( Size( 30, 30 ), aResMapMode );
+ tools::Long nCheckWidth = pDev->LogicToPixel( Size( 20, 20 ), aResMapMode ).Width();
+ vcl::Font aFont = GetDrawPixelFont( pDev );
+ tools::Rectangle aStateRect;
+ tools::Rectangle aMouseRect;
+
+ aImageSize.setWidth( CalcZoom( aImageSize.Width() ) );
+ aImageSize.setHeight( CalcZoom( aImageSize.Height() ) );
+ aBrd1Size.setWidth( CalcZoom( aBrd1Size.Width() ) );
+ aBrd1Size.setHeight( CalcZoom( aBrd1Size.Height() ) );
+ aBrd2Size.setWidth( CalcZoom( aBrd2Size.Width() ) );
+ aBrd2Size.setHeight( CalcZoom( aBrd2Size.Height() ) );
+
+ if ( !aBrd1Size.Width() )
+ aBrd1Size.setWidth( 1 );
+ if ( !aBrd1Size.Height() )
+ aBrd1Size.setHeight( 1 );
+ if ( !aBrd2Size.Width() )
+ aBrd2Size.setWidth( 1 );
+ if ( !aBrd2Size.Height() )
+ aBrd2Size.setHeight( 1 );
+ if ( !nCheckWidth )
+ nCheckWidth = 1;
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ if ( nFlags & SystemTextColorFlags::Mono )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ pDev->SetTextColor( GetTextColor() );
+ pDev->SetTextFillColor();
+
+ ImplDraw( pDev, nFlags, rPos, aSize,
+ aImageSize, aStateRect, aMouseRect );
+
+ pDev->SetLineColor();
+ pDev->SetFillColor( COL_BLACK );
+ pDev->DrawRect( aStateRect );
+ aStateRect.AdjustLeft(aBrd1Size.Width() );
+ aStateRect.AdjustTop(aBrd1Size.Height() );
+ aStateRect.AdjustRight( -(aBrd1Size.Width()) );
+ aStateRect.AdjustBottom( -(aBrd1Size.Height()) );
+ if ( meState == TRISTATE_INDET )
+ pDev->SetFillColor( COL_LIGHTGRAY );
+ else
+ pDev->SetFillColor( COL_WHITE );
+ pDev->DrawRect( aStateRect );
+
+ if ( meState == TRISTATE_TRUE )
+ {
+ aStateRect.AdjustLeft(aBrd2Size.Width() );
+ aStateRect.AdjustTop(aBrd2Size.Height() );
+ aStateRect.AdjustRight( -(aBrd2Size.Width()) );
+ aStateRect.AdjustBottom( -(aBrd2Size.Height()) );
+ Point aPos11( aStateRect.TopLeft() );
+ Point aPos12( aStateRect.BottomRight() );
+ Point aPos21( aStateRect.TopRight() );
+ Point aPos22( aStateRect.BottomLeft() );
+ Point aTempPos11( aPos11 );
+ Point aTempPos12( aPos12 );
+ Point aTempPos21( aPos21 );
+ Point aTempPos22( aPos22 );
+ pDev->SetLineColor( COL_BLACK );
+ tools::Long nDX = 0;
+ for ( tools::Long i = 0; i < nCheckWidth; i++ )
+ {
+ if ( !(i % 2) )
+ {
+ aTempPos11.setX( aPos11.X()+nDX );
+ aTempPos12.setX( aPos12.X()+nDX );
+ aTempPos21.setX( aPos21.X()+nDX );
+ aTempPos22.setX( aPos22.X()+nDX );
+ }
+ else
+ {
+ nDX++;
+ aTempPos11.setX( aPos11.X()-nDX );
+ aTempPos12.setX( aPos12.X()-nDX );
+ aTempPos21.setX( aPos21.X()-nDX );
+ aTempPos22.setX( aPos22.X()-nDX );
+ }
+ pDev->DrawLine( aTempPos11, aTempPos12 );
+ pDev->DrawLine( aTempPos21, aTempPos22 );
+ }
+ }
+
+ pDev->Pop();
+}
+
+void CheckBox::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void CheckBox::GetFocus()
+{
+ if (GetText().isEmpty())
+ {
+ // increase button size to have space for focus rect
+ // checkboxes without text will draw focusrect around the check
+ // See CheckBox::ImplDraw()
+ Point aPos( GetPosPixel() );
+ Size aSize( GetSizePixel() );
+ aPos.Move(-1,-1);
+ aSize.AdjustHeight(2 );
+ aSize.AdjustWidth(2 );
+ setPosSizePixel( aPos.X(), aPos.Y(), aSize.Width(), aSize.Height() );
+ Invalidate();
+ // Trigger drawing to initialize the mouse rectangle, otherwise the mouse button down
+ // handler would ignore the mouse event.
+ PaintImmediately();
+ }
+ else
+ ShowFocus( ImplGetFocusRect() );
+
+ SetInputContext( InputContext( GetFont() ) );
+ Button::GetFocus();
+}
+
+void CheckBox::LoseFocus()
+{
+ if ( GetButtonState() & DrawButtonFlags::Pressed )
+ {
+ GetButtonState() &= ~DrawButtonFlags::Pressed;
+ Invalidate();
+ }
+
+ HideFocus();
+ Button::LoseFocus();
+
+ if (GetText().isEmpty())
+ {
+ // decrease button size again (see GetFocus())
+ // checkboxes without text will draw focusrect around the check
+ Point aPos( GetPosPixel() );
+ Size aSize( GetSizePixel() );
+ aPos.Move(1,1);
+ aSize.AdjustHeight( -2 );
+ aSize.AdjustWidth( -2 );
+ setPosSizePixel( aPos.X(), aPos.Y(), aSize.Width(), aSize.Height() );
+ Invalidate();
+ }
+}
+
+void CheckBox::StateChanged( StateChangedType nType )
+{
+ Button::StateChanged( nType );
+
+ if ( nType == StateChangedType::State )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate( maStateRect );
+ }
+ else if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::Data) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetWindow( GetWindowType::Prev ), GetStyle() ) );
+
+ if ( (GetPrevStyle() & CHECKBOX_VIEW_STYLE) !=
+ (GetStyle() & CHECKBOX_VIEW_STYLE) )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+void CheckBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Button::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+bool CheckBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == NotifyEventType::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
+ {
+ // trigger redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::Checkbox, ControlPart::Entire) )
+ {
+ if (maMouseRect.Contains(GetPointerPosPixel()) != maMouseRect.Contains(GetLastPointerPosPixel()) ||
+ pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
+ {
+ Invalidate( maStateRect );
+ }
+ }
+ }
+ }
+
+ return Button::PreNotify(rNEvt);
+}
+
+void CheckBox::Toggle()
+{
+ ImplCallEventListenersAndHandler( VclEventId::CheckboxToggle, [this] () { maToggleHdl.Call(*this); } );
+}
+
+void CheckBox::SetState( TriState eState )
+{
+ if ( !mbTriState && (eState == TRISTATE_INDET) )
+ eState = TRISTATE_FALSE;
+
+ if ( meState != eState )
+ {
+ meState = eState;
+ StateChanged( StateChangedType::State );
+ Toggle();
+ }
+}
+
+bool CheckBox::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "active")
+ SetState(toBool(rValue) ? TRISTATE_TRUE : TRISTATE_FALSE);
+ else
+ return Button::set_property(rKey, rValue);
+ return true;
+}
+
+void CheckBox::EnableTriState( bool bTriState )
+{
+ if ( mbTriState != bTriState )
+ {
+ mbTriState = bTriState;
+
+ if ( !bTriState && (meState == TRISTATE_INDET) )
+ SetState( TRISTATE_FALSE );
+ }
+}
+
+Size CheckBox::ImplGetCheckImageSize() const
+{
+ Size aSize;
+ bool bDefaultSize = true;
+ if( IsNativeControlSupported( ControlType::Checkbox, ControlPart::Entire ) )
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), GetSizePixel() );
+ tools::Rectangle aBoundingRgn, aContentRgn;
+
+ // get native size of a check box
+ if( GetNativeControlRegion( ControlType::Checkbox, ControlPart::Entire, aCtrlRegion,
+ ControlState::DEFAULT|ControlState::ENABLED,
+ aControlValue,
+ aBoundingRgn, aContentRgn ) )
+ {
+ aSize = aContentRgn.GetSize();
+ bDefaultSize = false;
+ }
+ }
+ if( bDefaultSize )
+ aSize = GetCheckImage( GetSettings(), DrawButtonFlags::NONE ).GetSizePixel();
+ return aSize;
+}
+
+Image CheckBox::GetCheckImage( const AllSettings& rSettings, DrawButtonFlags nFlags )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ const StyleSettings& rStyleSettings = rSettings.GetStyleSettings();
+ sal_uInt16 nStyle = 0;
+
+ if ( rStyleSettings.GetOptions() & StyleSettingsOptions::Mono )
+ nStyle = STYLE_CHECKBOX_MONO;
+
+ if ( pSVData->maCtrlData.maCheckImgList.empty() ||
+ (pSVData->maCtrlData.mnCheckStyle != nStyle) ||
+ (pSVData->maCtrlData.mnLastCheckFColor != rStyleSettings.GetFaceColor()) ||
+ (pSVData->maCtrlData.mnLastCheckWColor != rStyleSettings.GetWindowColor()) ||
+ (pSVData->maCtrlData.mnLastCheckLColor != rStyleSettings.GetLightColor()) )
+ {
+ pSVData->maCtrlData.maCheckImgList.clear();
+
+ pSVData->maCtrlData.mnLastCheckFColor = rStyleSettings.GetFaceColor();
+ pSVData->maCtrlData.mnLastCheckWColor = rStyleSettings.GetWindowColor();
+ pSVData->maCtrlData.mnLastCheckLColor = rStyleSettings.GetLightColor();
+
+ std::vector<OUString> aResources;
+ if (nStyle)
+ {
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO1);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO2);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO3);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO4);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO5);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO6);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO7);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO8);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECKMONO9);
+ }
+ else
+ {
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK1);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK2);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK3);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK4);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK5);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK6);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK7);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK8);
+ aResources.emplace_back(SV_RESID_BITMAP_CHECK9);
+ }
+ LoadThemedImageList(rStyleSettings, pSVData->maCtrlData.maCheckImgList, aResources);
+ pSVData->maCtrlData.mnCheckStyle = nStyle;
+ }
+
+ sal_uInt16 nIndex;
+ if ( nFlags & DrawButtonFlags::Disabled )
+ {
+ if ( nFlags & DrawButtonFlags::DontKnow )
+ nIndex = 8;
+ else if ( nFlags & DrawButtonFlags::Checked )
+ nIndex = 5;
+ else
+ nIndex = 4;
+ }
+ else if ( nFlags & DrawButtonFlags::Pressed )
+ {
+ if ( nFlags & DrawButtonFlags::DontKnow )
+ nIndex = 7;
+ else if ( nFlags & DrawButtonFlags::Checked )
+ nIndex = 3;
+ else
+ nIndex = 2;
+ }
+ else
+ {
+ if ( nFlags & DrawButtonFlags::DontKnow )
+ nIndex = 6;
+ else if ( nFlags & DrawButtonFlags::Checked )
+ nIndex = 1;
+ else
+ nIndex = 0;
+ }
+ return pSVData->maCtrlData.maCheckImgList[nIndex];
+}
+
+void CheckBox::ImplAdjustNWFSizes()
+{
+ GetOutDev()->Push( vcl::PushFlags::MAPMODE );
+ SetMapMode(MapMode(MapUnit::MapPixel));
+
+ ImplControlValue aControlValue;
+ Size aCurSize( GetSizePixel() );
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), aCurSize );
+ tools::Rectangle aBoundingRgn, aContentRgn;
+
+ // get native size of a radiobutton
+ if( GetNativeControlRegion( ControlType::Checkbox, ControlPart::Entire, aCtrlRegion,
+ ControlState::DEFAULT|ControlState::ENABLED, aControlValue,
+ aBoundingRgn, aContentRgn ) )
+ {
+ Size aSize = aContentRgn.GetSize();
+
+ if( aSize.Height() > aCurSize.Height() )
+ {
+ aCurSize.setHeight( aSize.Height() );
+ SetSizePixel( aCurSize );
+ }
+ }
+
+ GetOutDev()->Pop();
+}
+
+Size CheckBox::CalcMinimumSize( tools::Long nMaxWidth ) const
+{
+ Size aSize = ImplGetCheckImageSize();
+ nMaxWidth -= aSize.Width();
+
+ OUString aText = GetText();
+ if (!aText.isEmpty())
+ {
+ // subtract what will be added later
+ nMaxWidth-=2;
+ nMaxWidth -= ImplGetImageToTextDistance();
+
+ Size aTextSize = GetTextRect( tools::Rectangle( Point(), Size( nMaxWidth > 0 ? nMaxWidth : 0x7fffffff, 0x7fffffff ) ),
+ aText, FixedText::ImplGetTextStyle( GetStyle() ) ).GetSize();
+ aSize.AdjustWidth(2 ); // for focus rect
+ aSize.AdjustWidth(ImplGetImageToTextDistance() );
+ aSize.AdjustWidth(aTextSize.Width() );
+ if ( aSize.Height() < aTextSize.Height() )
+ aSize.setHeight( aTextSize.Height() );
+ }
+ else
+ {
+ // is this still correct ? since the checkbox now
+ // shows a focus rect it should be 2 pixels wider and longer
+/* since otherwise the controls in the Writer hang too far up
+ aSize.Width() += 2;
+ aSize.Height() += 2;
+*/
+ }
+
+ return CalcWindowSize( aSize );
+}
+
+Size CheckBox::GetOptimalSize() const
+{
+ int nWidthRequest(get_width_request());
+ return CalcMinimumSize(nWidthRequest != -1 ? nWidthRequest : 0);
+}
+
+void CheckBox::ShowFocus(const tools::Rectangle& rRect)
+{
+ if (IsNativeControlSupported(ControlType::Checkbox, ControlPart::Focus))
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aInRect(Point(0, 0), GetSizePixel());
+
+ aInRect.SetLeft( rRect.Left() ); // exclude the checkbox itself from the focusrect
+
+ GetOutDev()->DrawNativeControl(ControlType::Checkbox, ControlPart::Focus, aInRect,
+ ControlState::FOCUSED, aControlValue, OUString());
+ }
+ Button::ShowFocus(rRect);
+}
+
+void CheckBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Button::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("checked", IsChecked());
+}
+
+FactoryFunction CheckBox::GetUITestFactory() const
+{
+ return CheckBoxUIObject::create;
+}
+
+ImageButton::ImageButton( vcl::Window* pParent, WinBits nStyle ) :
+ PushButton( pParent, nStyle )
+{
+ ImplInitStyle();
+}
+
+void ImageButton::ImplInitStyle()
+{
+ WinBits nStyle = GetStyle();
+
+ if ( ! ( nStyle & ( WB_RIGHT | WB_LEFT ) ) )
+ nStyle |= WB_CENTER;
+
+ if ( ! ( nStyle & ( WB_TOP | WB_BOTTOM ) ) )
+ nStyle |= WB_VCENTER;
+
+ SetStyle( nStyle );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/calendar.cxx b/vcl/source/control/calendar.cxx
new file mode 100644
index 0000000000..cce0bce63a
--- /dev/null
+++ b/vcl/source/control/calendar.cxx
@@ -0,0 +1,1747 @@
+/* -*- 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/builder.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/help.hxx>
+#include <vcl/kernarray.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/calendar.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/dockwin.hxx>
+#include <unotools/calendarwrapper.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <com/sun/star/i18n/Weekdays.hpp>
+#include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
+#include <com/sun/star/i18n/CalendarFieldIndex.hpp>
+#include <sal/log.hxx>
+#include <tools/json_writer.hxx>
+
+#include <calendar.hxx>
+#include <svdata.hxx>
+#include <strings.hrc>
+#include <memory>
+
+#define DAY_OFFX 4
+#define DAY_OFFY 2
+#define MONTH_BORDERX 4
+#define MONTH_OFFY 3
+#define WEEKDAY_OFFY 3
+#define TITLE_OFFY 3
+#define TITLE_BORDERY 2
+#define SPIN_OFFX 4
+#define SPIN_OFFY TITLE_BORDERY
+
+#define CALENDAR_HITTEST_DAY (sal_uInt16(0x0001))
+#define CALENDAR_HITTEST_MONTHTITLE (sal_uInt16(0x0004))
+#define CALENDAR_HITTEST_PREV (sal_uInt16(0x0008))
+#define CALENDAR_HITTEST_NEXT (sal_uInt16(0x0010))
+
+#define MENU_YEAR_COUNT 3
+
+using namespace ::com::sun::star;
+
+static void ImplCalendarSelectDate( IntDateSet* pTable, const Date& rDate, bool bSelect )
+{
+ if ( bSelect )
+ pTable->insert( rDate.GetDate() );
+ else
+ pTable->erase( rDate.GetDate() );
+}
+
+
+
+void Calendar::ImplInit( WinBits nWinStyle )
+{
+ mpSelectTable.reset(new IntDateSet);
+ mnDayCount = 0;
+ mnWinStyle = nWinStyle;
+ mnFirstYear = 0;
+ mnLastYear = 0;
+ mbCalc = true;
+ mbFormat = true;
+ mbDrag = false;
+ mbMenuDown = false;
+ mbSpinDown = false;
+ mbPrevIn = false;
+ mbNextIn = false;
+
+ OUString aGregorian( "gregorian");
+ maCalendarWrapper.loadCalendar( aGregorian,
+ Application::GetAppLocaleDataWrapper().getLanguageTag().getLocale());
+ if (maCalendarWrapper.getUniqueID() != aGregorian)
+ {
+ SAL_WARN( "vcl.control", "Calendar::ImplInit: No ``gregorian'' calendar available for locale ``"
+ << Application::GetAppLocaleDataWrapper().getLanguageTag().getBcp47()
+ << "'' and other calendars aren't supported. Using en-US fallback." );
+
+ /* If we ever wanted to support other calendars than Gregorian a lot of
+ * rewrite would be necessary to internally replace use of class Date
+ * with proper class CalendarWrapper methods, get rid of fixed 12
+ * months, fixed 7 days, ... */
+ maCalendarWrapper.loadCalendar( aGregorian, lang::Locale( "en", "US", ""));
+ }
+
+ SetFirstDate( maCurDate );
+ ImplCalendarSelectDate( mpSelectTable.get(), maCurDate, true );
+
+ // Sonstige Strings erzeugen
+ maDayText = VclResId(STR_SVT_CALENDAR_DAY);
+ maWeekText = VclResId(STR_SVT_CALENDAR_WEEK);
+
+ // Tagestexte anlegen
+ for (sal_Int32 i = 0; i < 31; ++i)
+ maDayTexts[i] = OUString::number(i+1);
+
+ ImplInitSettings();
+}
+
+void Calendar::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ maSelColor = rStyleSettings.GetHighlightTextColor();
+ SetPointFont(rRenderContext, rStyleSettings.GetToolFont());
+ rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
+ rRenderContext.SetBackground(Wallpaper(rStyleSettings.GetFieldColor()));
+}
+
+void Calendar::ImplInitSettings()
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ maSelColor = rStyleSettings.GetHighlightTextColor();
+ SetPointFont(*GetOutDev(), rStyleSettings.GetToolFont());
+ SetTextColor(rStyleSettings.GetFieldTextColor());
+ SetBackground(Wallpaper(rStyleSettings.GetFieldColor()));
+}
+
+Calendar::Calendar( vcl::Window* pParent, WinBits nWinStyle ) :
+ Control( pParent, nWinStyle & (WB_TABSTOP | WB_GROUP | WB_BORDER | WB_3DLOOK) ),
+ maCalendarWrapper( Application::GetAppLocaleDataWrapper().getComponentContext() ),
+ maOldFormatFirstDate( 0, 0, 1900 ),
+ maOldFormatLastDate( 0, 0, 1900 ),
+ maFirstDate( 0, 0, 1900 ),
+ maOldFirstDate( 0, 0, 1900 ),
+ maCurDate( Date::SYSTEM ),
+ maOldCurDate( 0, 0, 1900 )
+{
+ ImplInit( nWinStyle );
+}
+
+Calendar::~Calendar()
+{
+ disposeOnce();
+}
+
+void Calendar::dispose()
+{
+ mpSelectTable.reset();
+ mpOldSelectTable.reset();
+ Control::dispose();
+}
+
+DayOfWeek Calendar::ImplGetWeekStart() const
+{
+ // Map i18n::Weekdays to Date DayOfWeek
+ DayOfWeek eDay;
+ sal_Int16 nDay = maCalendarWrapper.getFirstDayOfWeek();
+ switch (nDay)
+ {
+ case i18n::Weekdays::SUNDAY :
+ eDay = SUNDAY;
+ break;
+ case i18n::Weekdays::MONDAY :
+ eDay = MONDAY;
+ break;
+ case i18n::Weekdays::TUESDAY :
+ eDay = TUESDAY;
+ break;
+ case i18n::Weekdays::WEDNESDAY :
+ eDay = WEDNESDAY;
+ break;
+ case i18n::Weekdays::THURSDAY :
+ eDay = THURSDAY;
+ break;
+ case i18n::Weekdays::FRIDAY :
+ eDay = FRIDAY;
+ break;
+ case i18n::Weekdays::SATURDAY :
+ eDay = SATURDAY;
+ break;
+ default:
+ SAL_WARN( "vcl.control", "Calendar::ImplGetWeekStart: broken i18n Gregorian calendar (getFirstDayOfWeek())");
+ eDay = SUNDAY;
+ }
+ return eDay;
+}
+
+void Calendar::ImplFormat()
+{
+ if ( !mbFormat )
+ return;
+
+ if ( mbCalc )
+ {
+ Size aOutSize = GetOutputSizePixel();
+
+ if ( (aOutSize.Width() <= 1) || (aOutSize.Height() <= 1) )
+ return;
+
+ tools::Long n99TextWidth = GetTextWidth( "99" );
+ tools::Long nTextHeight = GetTextHeight();
+
+ // calculate width and x-position
+ mnDayWidth = n99TextWidth+DAY_OFFX;
+ mnMonthWidth = mnDayWidth*7;
+ mnMonthWidth += MONTH_BORDERX*2;
+ mnMonthPerLine = aOutSize.Width() / mnMonthWidth;
+ if ( !mnMonthPerLine )
+ mnMonthPerLine = 1;
+ tools::Long nOver = (aOutSize.Width()-(mnMonthPerLine*mnMonthWidth)) / mnMonthPerLine;
+ mnMonthWidth += nOver;
+ mnDaysOffX = MONTH_BORDERX;
+ mnDaysOffX += nOver/2;
+
+ // calculate height and y-position
+ mnDayHeight = nTextHeight + DAY_OFFY;
+ mnWeekDayOffY = nTextHeight + TITLE_OFFY + (TITLE_BORDERY*2);
+ mnDaysOffY = mnWeekDayOffY + nTextHeight + WEEKDAY_OFFY;
+ mnMonthHeight = (mnDayHeight*6) + mnDaysOffY;
+ mnMonthHeight += MONTH_OFFY;
+ mnLines = aOutSize.Height() / mnMonthHeight;
+ if ( !mnLines )
+ mnLines = 1;
+ mnMonthHeight += (aOutSize.Height()-(mnLines*mnMonthHeight)) / mnLines;
+
+ // calculate spinfields
+ tools::Long nSpinSize = nTextHeight+TITLE_BORDERY-SPIN_OFFY;
+ maPrevRect.SetLeft( SPIN_OFFX );
+ maPrevRect.SetTop( SPIN_OFFY );
+ maPrevRect.SetRight( maPrevRect.Left()+nSpinSize );
+ maPrevRect.SetBottom( maPrevRect.Top()+nSpinSize );
+ maNextRect.SetLeft( aOutSize.Width()-SPIN_OFFX-nSpinSize-1 );
+ maNextRect.SetTop( SPIN_OFFY );
+ maNextRect.SetRight( maNextRect.Left()+nSpinSize );
+ maNextRect.SetBottom( maNextRect.Top()+nSpinSize );
+
+ // Calculate DayOfWeekText (gets displayed in a narrow font)
+ maDayOfWeekText.clear();
+ tools::Long nStartOffX = 0;
+ sal_Int16 nDay = maCalendarWrapper.getFirstDayOfWeek();
+ for ( sal_Int16 nDayOfWeek = 0; nDayOfWeek < 7; nDayOfWeek++ )
+ {
+ // Use narrow name.
+ OUString aDayOfWeek( maCalendarWrapper.getDisplayName(
+ i18n::CalendarDisplayIndex::DAY, nDay, 2));
+ tools::Long nOffX = (mnDayWidth-GetTextWidth( aDayOfWeek ))/2;
+ if ( !nDayOfWeek )
+ nStartOffX = nOffX;
+ else
+ nOffX -= nStartOffX;
+ nOffX += nDayOfWeek * mnDayWidth;
+ mnDayOfWeekAry[nDayOfWeek] = nOffX;
+ maDayOfWeekText += aDayOfWeek;
+ nDay++;
+ nDay %= 7;
+ }
+
+ // header position for the last day of week
+ mnDayOfWeekAry[7] = mnMonthWidth;
+
+ mbCalc = false;
+ }
+
+ // calculate number of days
+
+ DayOfWeek eStartDay = ImplGetWeekStart();
+
+ sal_uInt16 nWeekDay;
+ Date aTempDate = GetFirstMonth();
+ maFirstDate = aTempDate;
+ nWeekDay = static_cast<sal_uInt16>(aTempDate.GetDayOfWeek());
+ nWeekDay = (nWeekDay+(7-static_cast<sal_uInt16>(eStartDay))) % 7;
+ maFirstDate.AddDays( -nWeekDay );
+ mnDayCount = nWeekDay;
+ sal_uInt16 nDaysInMonth;
+ sal_uInt16 nMonthCount = static_cast<sal_uInt16>(mnMonthPerLine*mnLines);
+ for ( sal_uInt16 i = 0; i < nMonthCount; i++ )
+ {
+ nDaysInMonth = aTempDate.GetDaysInMonth();
+ mnDayCount += nDaysInMonth;
+ aTempDate.AddDays( nDaysInMonth );
+ }
+ Date aTempDate2 = aTempDate;
+ --aTempDate2;
+ nDaysInMonth = aTempDate2.GetDaysInMonth();
+ aTempDate2.AddDays( -(nDaysInMonth-1) );
+ nWeekDay = static_cast<sal_uInt16>(aTempDate2.GetDayOfWeek());
+ nWeekDay = (nWeekDay+(7-static_cast<sal_uInt16>(eStartDay))) % 7;
+ mnDayCount += 42-nDaysInMonth-nWeekDay;
+
+ // determine colours
+ maOtherColor = COL_LIGHTGRAY;
+ if ( maOtherColor.IsRGBEqual( GetBackground().GetColor() ) )
+ maOtherColor = COL_GRAY;
+
+ Date aLastDate = GetLastDate();
+ if ( (maOldFormatLastDate != aLastDate) ||
+ (maOldFormatFirstDate != maFirstDate) )
+ {
+ maOldFormatFirstDate = maFirstDate;
+ maOldFormatLastDate = aLastDate;
+ }
+
+ // get DateInfo
+ sal_Int16 nNewFirstYear = maFirstDate.GetYear();
+ sal_Int16 nNewLastYear = GetLastDate().GetYear();
+ if ( mnFirstYear )
+ {
+ if ( nNewFirstYear < mnFirstYear )
+ {
+ mnFirstYear = nNewFirstYear;
+ }
+ if ( nNewLastYear > mnLastYear )
+ {
+ mnLastYear = nNewLastYear;
+ }
+ }
+ else
+ {
+ mnFirstYear = nNewFirstYear;
+ mnLastYear = nNewLastYear;
+ }
+
+ mbFormat = false;
+}
+
+sal_uInt16 Calendar::ImplDoHitTest( const Point& rPos, Date& rDate ) const
+{
+ if ( mbFormat )
+ return 0;
+
+ if ( maPrevRect.Contains( rPos ) )
+ return CALENDAR_HITTEST_PREV;
+ else if ( maNextRect.Contains( rPos ) )
+ return CALENDAR_HITTEST_NEXT;
+
+ tools::Long nY;
+ tools::Long nOffX;
+ sal_Int32 nDay;
+ DayOfWeek eStartDay = ImplGetWeekStart();
+
+ rDate = GetFirstMonth();
+ nY = 0;
+ for ( tools::Long i = 0; i < mnLines; i++ )
+ {
+ if ( rPos.Y() < nY )
+ return 0;
+
+ tools::Long nX = 0;
+ tools::Long nYMonth = nY+mnMonthHeight;
+ for ( tools::Long j = 0; j < mnMonthPerLine; j++ )
+ {
+ if ( (rPos.X() < nX) && (rPos.Y() < nYMonth) )
+ return 0;
+
+ sal_uInt16 nDaysInMonth = rDate.GetDaysInMonth();
+
+ // matching month was found
+ if ( (rPos.X() > nX) && (rPos.Y() < nYMonth) &&
+ (rPos.X() < nX+mnMonthWidth) )
+ {
+ if ( rPos.Y() < (nY+(TITLE_BORDERY*2)+mnDayHeight))
+ return CALENDAR_HITTEST_MONTHTITLE;
+ else
+ {
+ tools::Long nDayX = nX+mnDaysOffX;
+ tools::Long nDayY = nY+mnDaysOffY;
+ if ( rPos.Y() < nDayY )
+ return 0;
+ sal_Int32 nDayIndex = static_cast<sal_Int32>(rDate.GetDayOfWeek());
+ nDayIndex = (nDayIndex+(7-static_cast<sal_Int32>(eStartDay))) % 7;
+ if ( (i == 0) && (j == 0) )
+ {
+ Date aTempDate = rDate;
+ aTempDate.AddDays( -nDayIndex );
+ for ( nDay = 0; nDay < nDayIndex; nDay++ )
+ {
+ nOffX = nDayX + (nDay*mnDayWidth);
+ if ( (rPos.Y() >= nDayY) && (rPos.Y() < nDayY+mnDayHeight) &&
+ (rPos.X() >= nOffX) && (rPos.X() < nOffX+mnDayWidth) )
+ {
+ rDate = aTempDate;
+ rDate.AddDays( nDay );
+ return CALENDAR_HITTEST_DAY;
+ }
+ }
+ }
+ for ( nDay = 1; nDay <= nDaysInMonth; nDay++ )
+ {
+ if ( rPos.Y() < nDayY )
+ {
+ rDate.AddDays( nDayIndex );
+ return 0;
+ }
+ nOffX = nDayX + (nDayIndex*mnDayWidth);
+ if ( (rPos.Y() >= nDayY) && (rPos.Y() < nDayY+mnDayHeight) &&
+ (rPos.X() >= nOffX) && (rPos.X() < nOffX+mnDayWidth) )
+ {
+ rDate.AddDays( nDay-1 );
+ return CALENDAR_HITTEST_DAY;
+ }
+ if ( nDayIndex == 6 )
+ {
+ nDayIndex = 0;
+ nDayY += mnDayHeight;
+ }
+ else
+ nDayIndex++;
+ }
+ if ( (i == mnLines-1) && (j == mnMonthPerLine-1) )
+ {
+ sal_uInt16 nWeekDay = static_cast<sal_uInt16>(rDate.GetDayOfWeek());
+ nWeekDay = (nWeekDay+(7-static_cast<sal_uInt16>(eStartDay))) % 7;
+ sal_Int32 nDayCount = 42-nDaysInMonth-nWeekDay;
+ Date aTempDate = rDate;
+ aTempDate.AddDays( nDaysInMonth );
+ for ( nDay = 1; nDay <= nDayCount; nDay++ )
+ {
+ if ( rPos.Y() < nDayY )
+ {
+ rDate.AddDays( nDayIndex );
+ return 0;
+ }
+ nOffX = nDayX + (nDayIndex*mnDayWidth);
+ if ( (rPos.Y() >= nDayY) && (rPos.Y() < nDayY+mnDayHeight) &&
+ (rPos.X() >= nOffX) && (rPos.X() < nOffX+mnDayWidth) )
+ {
+ rDate = aTempDate;
+ rDate.AddDays( nDay-1 );
+ return CALENDAR_HITTEST_DAY;
+ }
+ if ( nDayIndex == 6 )
+ {
+ nDayIndex = 0;
+ nDayY += mnDayHeight;
+ }
+ else
+ nDayIndex++;
+ }
+ }
+ }
+ }
+
+ rDate.AddDays( nDaysInMonth );
+ nX += mnMonthWidth;
+ }
+
+ nY += mnMonthHeight;
+ }
+
+ return 0;
+}
+
+namespace
+{
+
+void ImplDrawSpinArrow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, bool bPrev)
+{
+ tools::Long i;
+ tools::Long n;
+ tools::Long nLines;
+ tools::Long nHeight = rRect.GetHeight();
+ tools::Long nWidth = rRect.GetWidth();
+ if (nWidth < nHeight)
+ n = nWidth;
+ else
+ n = nHeight;
+ if (!(n & 0x01))
+ n--;
+ nLines = n/2;
+
+ tools::Rectangle aRect(Point( rRect.Left() + (nWidth / 2) - (nLines / 2),
+ rRect.Top() + (nHeight / 2) ),
+ Size(1, 1));
+ if (!bPrev)
+ {
+ aRect.AdjustLeft(nLines );
+ aRect.AdjustRight(nLines );
+ }
+
+ rRenderContext.DrawRect(aRect);
+ for (i = 0; i < nLines; i++)
+ {
+ if (bPrev)
+ {
+ aRect.AdjustLeft( 1 );
+ aRect.AdjustRight( 1 );
+ }
+ else
+ {
+ aRect.AdjustLeft( -1 );
+ aRect.AdjustRight( -1 );
+ }
+ aRect.AdjustTop( -1 );
+ aRect.AdjustBottom( 1 );
+ rRenderContext.DrawRect(aRect);
+ }
+}
+
+} //end anonymous namespace
+
+void Calendar::ImplDrawSpin(vcl::RenderContext& rRenderContext )
+{
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetButtonTextColor());
+ tools::Rectangle aOutRect = maPrevRect;
+ aOutRect.AdjustLeft(3 );
+ aOutRect.AdjustTop(3 );
+ aOutRect.AdjustRight( -3 );
+ aOutRect.AdjustBottom( -3 );
+ ImplDrawSpinArrow(rRenderContext, aOutRect, true);
+ aOutRect = maNextRect;
+ aOutRect.AdjustLeft(3 );
+ aOutRect.AdjustTop(3 );
+ aOutRect.AdjustRight( -3 );
+ aOutRect.AdjustBottom( -3 );
+ ImplDrawSpinArrow(rRenderContext, aOutRect, false);
+}
+
+void Calendar::ImplDrawDate(vcl::RenderContext& rRenderContext,
+ tools::Long nX, tools::Long nY,
+ sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear,
+ bool bOther, sal_Int32 nToday )
+{
+ Color const * pTextColor = nullptr;
+ const OUString& rDay = maDayTexts[(nDay - 1) % std::size(maDayTexts)];
+ tools::Rectangle aDateRect(nX, nY, nX + mnDayWidth - 1, nY + mnDayHeight - 1);
+
+ bool bSel = false;
+ bool bFocus = false;
+ // actual day
+ if ((nDay == maCurDate.GetDay()) &&
+ (nMonth == maCurDate.GetMonth()) &&
+ (nYear == maCurDate.GetYear()))
+ {
+ bFocus = true;
+ }
+ if (mpSelectTable)
+ {
+ if (mpSelectTable->find(Date(nDay, nMonth, nYear).GetDate()) != mpSelectTable->end())
+ bSel = true;
+ }
+
+ // get textcolour
+ if (bSel)
+ pTextColor = &maSelColor;
+ else if (bOther)
+ pTextColor = &maOtherColor;
+
+ if (bFocus)
+ HideFocus();
+
+ // display background
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ if (bSel)
+ {
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(rStyleSettings.GetHighlightColor());
+ rRenderContext.DrawRect(aDateRect);
+ }
+
+ // display text
+ tools::Long nTextX = nX + (mnDayWidth - GetTextWidth(rDay)) - (DAY_OFFX / 2);
+ tools::Long nTextY = nY + (mnDayHeight - GetTextHeight()) / 2;
+ if (pTextColor)
+ {
+ Color aOldColor = rRenderContext.GetTextColor();
+ rRenderContext.SetTextColor(*pTextColor);
+ rRenderContext.DrawText(Point(nTextX, nTextY), rDay);
+ rRenderContext.SetTextColor(aOldColor);
+ }
+ else
+ rRenderContext.DrawText(Point(nTextX, nTextY), rDay);
+
+ // today
+ Date aTodayDate(maCurDate);
+ if (nToday)
+ aTodayDate.SetDate(nToday);
+ else
+ aTodayDate = Date(Date::SYSTEM);
+ if ((nDay == aTodayDate.GetDay()) &&
+ (nMonth == aTodayDate.GetMonth()) &&
+ (nYear == aTodayDate.GetYear()))
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetWindowTextColor());
+ rRenderContext.SetFillColor();
+ rRenderContext.DrawRect(aDateRect);
+ }
+
+ // if needed do FocusRect
+ if (bFocus && HasFocus())
+ ShowFocus(aDateRect);
+}
+
+void Calendar::ImplDraw(vcl::RenderContext& rRenderContext)
+{
+ ImplFormat();
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ Size aOutSize(GetOutputSizePixel());
+ tools::Long i;
+ tools::Long j;
+ tools::Long nY;
+ tools::Long nDeltaX;
+ tools::Long nDeltaY;
+ tools::Long nDayX;
+ tools::Long nDayY;
+ sal_Int32 nToday = Date(Date::SYSTEM).GetDate();
+ sal_uInt16 nDay;
+ sal_uInt16 nMonth;
+ sal_Int16 nYear;
+ Date aDate = GetFirstMonth();
+ DayOfWeek eStartDay = ImplGetWeekStart();
+
+ HideFocus();
+
+ nY = 0;
+ for (i = 0; i < mnLines; i++)
+ {
+ // display title bar
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(rStyleSettings.GetFaceColor());
+ tools::Rectangle aTitleRect(0, nY, aOutSize.Width() - 1, nY + mnDayHeight - DAY_OFFY + TITLE_BORDERY * 2);
+ rRenderContext.DrawRect(aTitleRect);
+ Point aTopLeft1(aTitleRect.Left(), aTitleRect.Top());
+ Point aTopLeft2(aTitleRect.Left(), aTitleRect.Top() + 1);
+ Point aBottomRight1(aTitleRect.Right(), aTitleRect.Bottom());
+ Point aBottomRight2(aTitleRect.Right(), aTitleRect.Bottom() - 1);
+ rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
+ rRenderContext.DrawLine(aTopLeft1, Point(aBottomRight1.X(), aTopLeft1.Y()));
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor() );
+ rRenderContext.DrawLine(aTopLeft2, Point(aBottomRight2.X(), aTopLeft2.Y()));
+ rRenderContext.DrawLine(aTopLeft2, Point(aTopLeft2.X(), aBottomRight2.Y()));
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor() );
+ rRenderContext.DrawLine(Point(aTopLeft2.X(), aBottomRight2.Y()), aBottomRight2);
+ rRenderContext.DrawLine(Point(aBottomRight2.X(), aTopLeft2.Y()), aBottomRight2);
+ rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
+ rRenderContext.DrawLine(Point(aTopLeft1.X(), aBottomRight1.Y()), aBottomRight1);
+ Point aSepPos1(0, aTitleRect.Top() + TITLE_BORDERY);
+ Point aSepPos2(0, aTitleRect.Bottom() - TITLE_BORDERY);
+ for (j = 0; j < mnMonthPerLine-1; j++)
+ {
+ aSepPos1.AdjustX(mnMonthWidth-1 );
+ aSepPos2.setX( aSepPos1.X() );
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(aSepPos1, aSepPos2);
+ aSepPos1.AdjustX( 1 );
+ aSepPos2.setX( aSepPos1.X() );
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ rRenderContext.DrawLine(aSepPos1, aSepPos2);
+ }
+
+ tools::Long nX = 0;
+ for (j = 0; j < mnMonthPerLine; j++)
+ {
+ nMonth = aDate.GetMonth();
+ nYear = aDate.GetYear();
+
+ // display month in title bar
+ nDeltaX = nX;
+ nDeltaY = nY + TITLE_BORDERY;
+ OUString aMonthText = maCalendarWrapper.getDisplayName(i18n::CalendarDisplayIndex::MONTH, nMonth - 1, 1)
+ + " "
+ + OUString::number(nYear);
+ tools::Long nMonthTextWidth = rRenderContext.GetTextWidth(aMonthText);
+ tools::Long nMonthOffX1 = 0;
+ tools::Long nMonthOffX2 = 0;
+ if (i == 0)
+ {
+ if (j == 0)
+ nMonthOffX1 = maPrevRect.Right() + 1;
+ if (j == mnMonthPerLine - 1)
+ nMonthOffX2 = aOutSize.Width() - maNextRect.Left() + 1;
+ }
+ tools::Long nMaxMonthWidth = mnMonthWidth - nMonthOffX1 - nMonthOffX2 - 4;
+ if (nMonthTextWidth > nMaxMonthWidth)
+ {
+ // Abbreviated month name.
+ aMonthText = maCalendarWrapper.getDisplayName(i18n::CalendarDisplayIndex::MONTH, nMonth - 1, 0)
+ + " "
+ + OUString::number(nYear);
+ nMonthTextWidth = rRenderContext.GetTextWidth(aMonthText);
+ }
+ tools::Long nTempOff = (mnMonthWidth - nMonthTextWidth + 1) / 2;
+ if (nTempOff < nMonthOffX1)
+ nDeltaX += nMonthOffX1 + 1;
+ else
+ {
+ if (nTempOff + nMonthTextWidth > mnMonthWidth - nMonthOffX2)
+ nDeltaX += mnMonthWidth - nMonthOffX2 - nMonthTextWidth;
+ else
+ nDeltaX += nTempOff;
+ }
+ rRenderContext.SetTextColor(rStyleSettings.GetButtonTextColor());
+ rRenderContext.DrawText(Point(nDeltaX, nDeltaY), aMonthText);
+ rRenderContext.SetTextColor(rStyleSettings.GetWindowTextColor());
+
+ // display week bar
+ nDayX = nX + mnDaysOffX;
+ nDayY = nY + mnWeekDayOffY;
+ nDeltaY = nDayY + mnDayHeight;
+ rRenderContext.SetLineColor(rStyleSettings.GetWindowTextColor());
+ Point aStartPos(nDayX, nDeltaY);
+ rRenderContext.DrawLine(aStartPos, Point(nDayX + (7 * mnDayWidth), nDeltaY));
+ KernArray aTmp;
+ for (int k=0; k<7; ++k)
+ aTmp.push_back(mnDayOfWeekAry[k+1]);
+ rRenderContext.DrawTextArray(Point(nDayX + mnDayOfWeekAry[0], nDayY), maDayOfWeekText, aTmp, {}, 0, aTmp.size());
+
+ // display days
+ sal_uInt16 nDaysInMonth = aDate.GetDaysInMonth();
+ nDayX = nX + mnDaysOffX;
+ nDayY = nY + mnDaysOffY;
+ sal_uInt16 nDayIndex = static_cast<sal_uInt16>(aDate.GetDayOfWeek());
+ nDayIndex = (nDayIndex + (7 - static_cast<sal_uInt16>(eStartDay))) % 7;
+ if (i == 0 && j == 0)
+ {
+ Date aTempDate = aDate;
+ aTempDate.AddDays( -nDayIndex );
+ for (nDay = 0; nDay < nDayIndex; ++nDay)
+ {
+ nDeltaX = nDayX + (nDay * mnDayWidth);
+ ImplDrawDate(rRenderContext, nDeltaX, nDayY, nDay + aTempDate.GetDay(),
+ aTempDate.GetMonth(), aTempDate.GetYear(),
+ true, nToday);
+ }
+ }
+ for (nDay = 1; nDay <= nDaysInMonth; nDay++)
+ {
+ nDeltaX = nDayX + (nDayIndex * mnDayWidth);
+ ImplDrawDate(rRenderContext, nDeltaX, nDayY, nDay, nMonth, nYear,
+ false, nToday);
+ if (nDayIndex == 6)
+ {
+ nDayIndex = 0;
+ nDayY += mnDayHeight;
+ }
+ else
+ nDayIndex++;
+ }
+ if ((i == mnLines - 1) && (j == mnMonthPerLine - 1))
+ {
+ sal_uInt16 nWeekDay = static_cast<sal_uInt16>(aDate.GetDayOfWeek());
+ nWeekDay = (nWeekDay + (7 - static_cast<sal_uInt16>(eStartDay))) % 7;
+ sal_uInt16 nDayCount = 42 - nDaysInMonth - nWeekDay;
+ Date aTempDate = aDate;
+ aTempDate.AddDays( nDaysInMonth );
+ for (nDay = 1; nDay <= nDayCount; ++nDay)
+ {
+ nDeltaX = nDayX + (nDayIndex * mnDayWidth);
+ ImplDrawDate(rRenderContext, nDeltaX, nDayY, nDay,
+ aTempDate.GetMonth(), aTempDate.GetYear(),
+ true, nToday);
+ if (nDayIndex == 6)
+ {
+ nDayIndex = 0;
+ nDayY += mnDayHeight;
+ }
+ else
+ nDayIndex++;
+ }
+ }
+
+ aDate.AddDays( nDaysInMonth );
+ nX += mnMonthWidth;
+ }
+
+ nY += mnMonthHeight;
+ }
+
+ // draw spin buttons
+ ImplDrawSpin(rRenderContext);
+}
+
+void Calendar::ImplUpdateDate( const Date& rDate )
+{
+ if (IsReallyVisible() && IsUpdateMode())
+ {
+ tools::Rectangle aDateRect(GetDateRect(rDate));
+ if (!aDateRect.IsEmpty())
+ {
+ Invalidate(aDateRect);
+ }
+ }
+}
+
+void Calendar::ImplUpdateSelection( IntDateSet* pOld )
+{
+ IntDateSet* pNew = mpSelectTable.get();
+
+ for (auto const& nKey : *pOld)
+ {
+ if ( pNew->find(nKey) == pNew->end() )
+ {
+ Date aTempDate(nKey);
+ ImplUpdateDate(aTempDate);
+ }
+ }
+
+ for (auto const& nKey : *pNew)
+ {
+ if ( pOld->find(nKey) == pOld->end() )
+ {
+ Date aTempDate(nKey);
+ ImplUpdateDate(aTempDate);
+ }
+ }
+}
+
+void Calendar::ImplMouseSelect( const Date& rDate, sal_uInt16 nHitTest )
+{
+ IntDateSet aOldSel( *mpSelectTable );
+ Date aOldDate = maCurDate;
+ Date aTempDate = rDate;
+
+ if ( !(nHitTest & CALENDAR_HITTEST_DAY) )
+ --aTempDate;
+
+ if ( !(nHitTest & CALENDAR_HITTEST_DAY) )
+ aTempDate = maOldCurDate;
+ if ( aTempDate != maCurDate )
+ {
+ maCurDate = aTempDate;
+ ImplCalendarSelectDate( mpSelectTable.get(), aOldDate, false );
+ ImplCalendarSelectDate( mpSelectTable.get(), maCurDate, true );
+ }
+
+ bool bNewSel = aOldSel != *mpSelectTable;
+ if ( (maCurDate != aOldDate) || bNewSel )
+ {
+ HideFocus();
+ if ( bNewSel )
+ ImplUpdateSelection( &aOldSel );
+ if ( !bNewSel || aOldSel.find( aOldDate.GetDate() ) == aOldSel.end() )
+ ImplUpdateDate( aOldDate );
+ // assure focus rectangle is displayed again
+ if ( HasFocus() || !bNewSel
+ || mpSelectTable->find( maCurDate.GetDate() ) == mpSelectTable->end() )
+ ImplUpdateDate( maCurDate );
+ }
+}
+
+void Calendar::ImplUpdate( bool bCalcNew )
+{
+ if (IsReallyVisible() && IsUpdateMode())
+ {
+ if (bCalcNew && !mbCalc)
+ {
+ Invalidate();
+ }
+ else if (!mbFormat && !mbCalc)
+ {
+ Invalidate();
+ }
+ }
+
+ if (bCalcNew)
+ mbCalc = true;
+ mbFormat = true;
+}
+
+void Calendar::ImplScrollCalendar( bool bPrev )
+{
+ Date aNewFirstMonth = GetFirstMonth();
+ if ( bPrev )
+ {
+ --aNewFirstMonth;
+ aNewFirstMonth.AddDays( -(aNewFirstMonth.GetDaysInMonth()-1));
+ }
+ else
+ aNewFirstMonth.AddDays( aNewFirstMonth.GetDaysInMonth());
+ SetFirstDate( aNewFirstMonth );
+}
+
+void Calendar::ImplShowMenu( const Point& rPos, const Date& rDate )
+{
+ EndSelection();
+
+ Date aOldFirstDate = GetFirstMonth();
+ ScopedVclPtrInstance<PopupMenu> aPopupMenu;
+ sal_uInt16 nMonthOff;
+ sal_uInt16 nCurItemId;
+ sal_uInt16 nYear = rDate.GetYear()-1;
+ sal_uInt16 i;
+ sal_uInt16 j;
+ sal_uInt16 nYearIdCount = 1000;
+
+ nMonthOff = (rDate.GetYear()-aOldFirstDate.GetYear())*12;
+ if ( aOldFirstDate.GetMonth() < rDate.GetMonth() )
+ nMonthOff += rDate.GetMonth()-aOldFirstDate.GetMonth();
+ else
+ nMonthOff -= aOldFirstDate.GetMonth()-rDate.GetMonth();
+
+ // construct menu (include years with different months)
+ for ( i = 0; i < MENU_YEAR_COUNT; i++ )
+ {
+ VclPtrInstance<PopupMenu> pYearPopupMenu;
+ for ( j = 1; j <= 12; j++ )
+ pYearPopupMenu->InsertItem( nYearIdCount+j,
+ maCalendarWrapper.getDisplayName(
+ i18n::CalendarDisplayIndex::MONTH, j-1, 1));
+ aPopupMenu->InsertItem( 10+i, OUString::number( nYear+i ) );
+ aPopupMenu->SetPopupMenu( 10+i, pYearPopupMenu );
+ nYearIdCount += 1000;
+ }
+
+ mbMenuDown = true;
+ nCurItemId = aPopupMenu->Execute( this, rPos );
+ mbMenuDown = false;
+
+ if ( !nCurItemId )
+ return;
+
+ sal_uInt16 nTempMonthOff = nMonthOff % 12;
+ sal_uInt16 nTempYearOff = nMonthOff / 12;
+ sal_uInt16 nNewMonth = nCurItemId % 1000;
+ sal_uInt16 nNewYear = nYear+((nCurItemId-1000)/1000);
+ if ( nTempMonthOff < nNewMonth )
+ nNewMonth = nNewMonth - nTempMonthOff;
+ else
+ {
+ nNewYear--;
+ nNewMonth = 12-(nTempMonthOff-nNewMonth);
+ }
+ nNewYear = nNewYear - nTempYearOff;
+ SetFirstDate( Date( 1, nNewMonth, nNewYear ) );
+}
+
+void Calendar::ImplTracking( const Point& rPos, bool bRepeat )
+{
+ Date aTempDate = maCurDate;
+ sal_uInt16 nHitTest = ImplDoHitTest( rPos, aTempDate );
+
+ if ( mbSpinDown )
+ {
+ mbPrevIn = (nHitTest & CALENDAR_HITTEST_PREV) != 0;
+ mbNextIn = (nHitTest & CALENDAR_HITTEST_NEXT) != 0;
+
+ if ( bRepeat && (mbPrevIn || mbNextIn) )
+ {
+ ImplScrollCalendar( mbPrevIn );
+ }
+ }
+ else
+ ImplMouseSelect( aTempDate, nHitTest );
+}
+
+void Calendar::ImplEndTracking( bool bCancel )
+{
+ bool bSelection = false;
+ bool bSpinDown = mbSpinDown;
+
+ mbDrag = false;
+ mbSpinDown = false;
+ mbPrevIn = false;
+ mbNextIn = false;
+
+ if ( bCancel )
+ {
+ if ( maOldFirstDate != maFirstDate )
+ SetFirstDate( maOldFirstDate );
+
+ if ( !bSpinDown )
+ {
+ IntDateSet aOldSel( *mpSelectTable );
+ Date aOldDate = maCurDate;
+ maCurDate = maOldCurDate;
+ *mpSelectTable = *mpOldSelectTable;
+ HideFocus();
+ ImplUpdateSelection( &aOldSel );
+ if ( aOldSel.find( aOldDate.GetDate() ) == aOldSel.end() )
+ ImplUpdateDate( aOldDate );
+ // assure focus rectangle is displayed again
+ if ( HasFocus() || mpSelectTable->find( maCurDate.GetDate() ) == mpSelectTable->end() )
+ ImplUpdateDate( maCurDate );
+ }
+ }
+
+ if ( bSpinDown )
+ return;
+
+ if ( !bCancel )
+ {
+ // determine if we should scroll the visible area
+ if ( !mpSelectTable->empty() )
+ {
+ Date aFirstSelDate( *mpSelectTable->begin() );
+ Date aLastSelDate( *mpSelectTable->rbegin() );
+ if ( aLastSelDate < GetFirstMonth() )
+ ImplScrollCalendar( true );
+ else if ( GetLastMonth() < aFirstSelDate )
+ ImplScrollCalendar( false );
+ }
+ }
+
+ if ( !bCancel && ((maCurDate != maOldCurDate) || (*mpOldSelectTable != *mpSelectTable)) )
+ Select();
+
+ if ( !bSelection && (mnWinStyle & WB_TABSTOP) && !bCancel )
+ GrabFocus();
+
+ mpOldSelectTable.reset();
+}
+
+void Calendar::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( rMEvt.IsLeft() && !mbMenuDown )
+ {
+ Date aTempDate = maCurDate;
+ sal_uInt16 nHitTest = ImplDoHitTest( rMEvt.GetPosPixel(), aTempDate );
+ if ( nHitTest )
+ {
+ if ( nHitTest & CALENDAR_HITTEST_MONTHTITLE )
+ ImplShowMenu( rMEvt.GetPosPixel(), aTempDate );
+ else
+ {
+ maOldFirstDate = maFirstDate;
+
+ mbPrevIn = (nHitTest & CALENDAR_HITTEST_PREV) != 0;
+ mbNextIn = (nHitTest & CALENDAR_HITTEST_NEXT) != 0;
+ if ( mbPrevIn || mbNextIn )
+ {
+ mbSpinDown = true;
+ ImplScrollCalendar( mbPrevIn );
+ // it should really read BUTTONREPEAT, therefore do not
+ // change it to SCROLLREPEAT, check with TH,
+ // why it could be different (71775)
+ StartTracking( StartTrackingFlags::ButtonRepeat );
+ }
+ else
+ {
+ if ( (rMEvt.GetClicks() != 2) || !(nHitTest & CALENDAR_HITTEST_DAY) )
+ {
+ maOldCurDate = maCurDate;
+ mpOldSelectTable.reset(new IntDateSet( *mpSelectTable ));
+
+ mbDrag = true;
+ StartTracking();
+
+ ImplMouseSelect( aTempDate, nHitTest );
+ }
+ if (rMEvt.GetClicks() == 2)
+ maActivateHdl.Call(this);
+ }
+ }
+ }
+
+ return;
+ }
+
+ Control::MouseButtonDown( rMEvt );
+}
+
+void Calendar::Tracking( const TrackingEvent& rTEvt )
+{
+ Point aMousePos = rTEvt.GetMouseEvent().GetPosPixel();
+
+ if ( rTEvt.IsTrackingEnded() )
+ ImplEndTracking( rTEvt.IsTrackingCanceled() );
+ else
+ ImplTracking( aMousePos, rTEvt.IsTrackingRepeat() );
+}
+
+void Calendar::KeyInput( const KeyEvent& rKEvt )
+{
+ Date aNewDate = maCurDate;
+
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_HOME:
+ aNewDate.SetDay( 1 );
+ break;
+
+ case KEY_END:
+ aNewDate.SetDay( aNewDate.GetDaysInMonth() );
+ break;
+
+ case KEY_LEFT:
+ --aNewDate;
+ break;
+
+ case KEY_RIGHT:
+ ++aNewDate;
+ break;
+
+ case KEY_UP:
+ aNewDate.AddDays( -7 );
+ break;
+
+ case KEY_DOWN:
+ aNewDate.AddDays( 7 );
+ break;
+
+ case KEY_PAGEUP:
+ {
+ Date aTempDate = aNewDate;
+ aTempDate.AddDays( -(aNewDate.GetDay()+1) );
+ aNewDate.AddDays( -aTempDate.GetDaysInMonth() );
+ }
+ break;
+
+ case KEY_PAGEDOWN:
+ aNewDate.AddDays( aNewDate.GetDaysInMonth() );
+ break;
+
+ case KEY_RETURN:
+ break;
+
+ default:
+ Control::KeyInput( rKEvt );
+ break;
+ }
+
+ if ( aNewDate != maCurDate )
+ {
+ SetCurDate( aNewDate );
+ Select();
+ }
+
+ if (rKEvt.GetKeyCode().GetCode() == KEY_RETURN)
+ {
+ if (maActivateHdl.IsSet())
+ maActivateHdl.Call(this);
+ else
+ Control::KeyInput(rKEvt);
+ }
+}
+
+void Calendar::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ ImplDraw(rRenderContext);
+}
+
+void Calendar::GetFocus()
+{
+ ImplUpdateDate( maCurDate );
+ Control::GetFocus();
+}
+
+void Calendar::LoseFocus()
+{
+ HideFocus();
+ Control::LoseFocus();
+}
+
+void Calendar::Resize()
+{
+ ImplUpdate( true );
+ Control::Resize();
+}
+
+void Calendar::RequestHelp( const HelpEvent& rHEvt )
+{
+ if ( rHEvt.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON) )
+ {
+ Date aDate = maCurDate;
+ if ( GetDate( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ), aDate ) )
+ {
+ tools::Rectangle aDateRect = GetDateRect( aDate );
+ Point aPt = OutputToScreenPixel( aDateRect.TopLeft() );
+ aDateRect.SetLeft( aPt.X() );
+ aDateRect.SetTop( aPt.Y() );
+ aPt = OutputToScreenPixel( aDateRect.BottomRight() );
+ aDateRect.SetRight( aPt.X() );
+ aDateRect.SetBottom( aPt.Y() );
+
+ if ( rHEvt.GetMode() & HelpEventMode::QUICK )
+ {
+ maCalendarWrapper.setGregorianDateTime( aDate);
+ sal_uInt16 nWeek = static_cast<sal_uInt16>(maCalendarWrapper.getValue( i18n::CalendarFieldIndex::WEEK_OF_YEAR));
+ sal_uInt16 nMonth = aDate.GetMonth();
+ OUString aStr = maDayText
+ + ": "
+ + OUString::number(aDate.GetDayOfYear())
+ + " / "
+ + maWeekText
+ + ": "
+ + OUString::number(nWeek);
+ // if year is not the same, add it
+ if ( (nMonth == 12) && (nWeek == 1) )
+ {
+ aStr += ", " + OUString::number(aDate.GetNextYear());
+ }
+ else if ( (nMonth == 1) && (nWeek > 50) )
+ {
+ aStr += ", " + OUString::number(aDate.GetYear()-1);
+ }
+ Help::ShowQuickHelp( this, aDateRect, aStr );
+ return;
+ }
+ }
+ }
+
+ Control::RequestHelp( rHEvt );
+}
+
+void Calendar::Command( const CommandEvent& rCEvt )
+{
+ if ( rCEvt.GetCommand() == CommandEventId::ContextMenu )
+ {
+ if ( rCEvt.IsMouseEvent() )
+ {
+ Date aTempDate = maCurDate;
+ sal_uInt16 nHitTest = ImplDoHitTest( rCEvt.GetMousePosPixel(), aTempDate );
+ if ( nHitTest & CALENDAR_HITTEST_MONTHTITLE )
+ {
+ ImplShowMenu( rCEvt.GetMousePosPixel(), aTempDate );
+ return;
+ }
+ }
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ const CommandWheelData* pData = rCEvt.GetWheelData();
+ if ( pData->GetMode() == CommandWheelMode::SCROLL )
+ {
+ tools::Long nNotchDelta = pData->GetNotchDelta();
+ if ( nNotchDelta < 0 )
+ {
+ while ( nNotchDelta < 0 )
+ {
+ ImplScrollCalendar( true );
+ nNotchDelta++;
+ }
+ }
+ else
+ {
+ while ( nNotchDelta > 0 )
+ {
+ ImplScrollCalendar( false );
+ nNotchDelta--;
+ }
+ }
+
+ return;
+ }
+ }
+
+ Control::Command( rCEvt );
+}
+
+void Calendar::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ ImplFormat();
+}
+
+void Calendar::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+void Calendar::Select()
+{
+ maSelectHdl.Call( this );
+}
+
+Date Calendar::GetFirstSelectedDate() const
+{
+ if ( !mpSelectTable->empty() )
+ return Date( *mpSelectTable->begin() );
+ else
+ {
+ Date aDate( 0, 0, 0 );
+ return aDate;
+ }
+}
+
+void Calendar::SetCurDate( const Date& rNewDate )
+{
+ if ( !rNewDate.IsValidAndGregorian() )
+ return;
+
+ if ( maCurDate == rNewDate )
+ return;
+
+ bool bUpdate = IsVisible() && IsUpdateMode();
+ Date aOldDate = maCurDate;
+ maCurDate = rNewDate;
+
+ ImplCalendarSelectDate( mpSelectTable.get(), aOldDate, false );
+ ImplCalendarSelectDate( mpSelectTable.get(), maCurDate, true );
+
+ // shift actual date in the visible area
+ if ( mbFormat || (maCurDate < GetFirstMonth()) )
+ SetFirstDate( maCurDate );
+ else if ( maCurDate > GetLastMonth() )
+ {
+ Date aTempDate = GetLastMonth();
+ tools::Long nDateOff = maCurDate-aTempDate;
+ if ( nDateOff < 365 )
+ {
+ Date aFirstDate = GetFirstMonth();
+ aFirstDate.AddDays( aFirstDate.GetDaysInMonth() );
+ ++aTempDate;
+ while ( nDateOff > aTempDate.GetDaysInMonth() )
+ {
+ aFirstDate.AddDays( aFirstDate.GetDaysInMonth() );
+ sal_Int32 nDaysInMonth = aTempDate.GetDaysInMonth();
+ aTempDate.AddDays( nDaysInMonth );
+ nDateOff -= nDaysInMonth;
+ }
+ SetFirstDate( aFirstDate );
+ }
+ else
+ SetFirstDate( maCurDate );
+ }
+ else
+ {
+ if ( bUpdate )
+ {
+ HideFocus();
+ ImplUpdateDate( aOldDate );
+ ImplUpdateDate( maCurDate );
+ }
+ }
+}
+
+void Calendar::SetFirstDate( const Date& rNewFirstDate )
+{
+ if ( maFirstDate != rNewFirstDate )
+ {
+ maFirstDate = Date( 1, rNewFirstDate.GetMonth(), rNewFirstDate.GetYear() );
+ ImplUpdate();
+ }
+}
+
+Date Calendar::GetFirstMonth() const
+{
+ if ( maFirstDate.GetDay() > 1 )
+ {
+ if ( maFirstDate.GetMonth() == 12 )
+ return Date( 1, 1, maFirstDate.GetNextYear() );
+ else
+ return Date( 1, maFirstDate.GetMonth()+1, maFirstDate.GetYear() );
+ }
+ else
+ return maFirstDate;
+}
+
+Date Calendar::GetLastMonth() const
+{
+ Date aDate = GetFirstMonth();
+ sal_uInt16 nMonthCount = GetMonthCount();
+ for ( sal_uInt16 i = 0; i < nMonthCount; i++ )
+ aDate.AddDays( aDate.GetDaysInMonth() );
+ --aDate;
+ return aDate;
+}
+
+sal_uInt16 Calendar::GetMonthCount() const
+{
+ if ( mbFormat )
+ return 1;
+ else
+ return static_cast<sal_uInt16>(mnMonthPerLine*mnLines);
+}
+
+bool Calendar::GetDate( const Point& rPos, Date& rDate ) const
+{
+ Date aDate = maCurDate;
+ sal_uInt16 nHitTest = ImplDoHitTest( rPos, aDate );
+ if ( nHitTest & CALENDAR_HITTEST_DAY )
+ {
+ rDate = aDate;
+ return true;
+ }
+ else
+ return false;
+}
+
+tools::Rectangle Calendar::GetDateRect( const Date& rDate ) const
+{
+ tools::Rectangle aRect;
+
+ if ( mbFormat || (rDate < maFirstDate) || (rDate > (maFirstDate+mnDayCount)) )
+ return aRect;
+
+ tools::Long nX;
+ tools::Long nY;
+ sal_Int32 nDaysOff;
+ sal_uInt16 nDayIndex;
+ Date aDate = GetFirstMonth();
+
+ if ( rDate < aDate )
+ {
+ aRect = GetDateRect( aDate );
+ nDaysOff = aDate-rDate;
+ nX = nDaysOff*mnDayWidth;
+ aRect.AdjustLeft( -nX );
+ aRect.AdjustRight( -nX );
+ return aRect;
+ }
+ else
+ {
+ Date aLastDate = GetLastMonth();
+ if ( rDate > aLastDate )
+ {
+ sal_Int32 nWeekDay = static_cast<sal_Int32>(aLastDate.GetDayOfWeek());
+ nWeekDay = (nWeekDay+(7-ImplGetWeekStart())) % 7;
+ aLastDate.AddDays( -nWeekDay );
+ aRect = GetDateRect( aLastDate );
+ nDaysOff = rDate-aLastDate;
+ nDayIndex = 0;
+ for ( sal_Int32 i = 0; i <= nDaysOff; i++ )
+ {
+ if ( aLastDate == rDate )
+ {
+ aRect.AdjustLeft(nDayIndex*mnDayWidth );
+ aRect.SetRight( aRect.Left()+mnDayWidth );
+ return aRect;
+ }
+ if ( nDayIndex == 6 )
+ {
+ nDayIndex = 0;
+ aRect.AdjustTop(mnDayHeight );
+ aRect.AdjustBottom(mnDayHeight );
+ }
+ else
+ nDayIndex++;
+ ++aLastDate;
+ }
+ }
+ }
+
+ nY = 0;
+ for ( tools::Long i = 0; i < mnLines; i++ )
+ {
+ nX = 0;
+ for ( tools::Long j = 0; j < mnMonthPerLine; j++ )
+ {
+ sal_uInt16 nDaysInMonth = aDate.GetDaysInMonth();
+
+ // month is called
+ if ( (aDate.GetMonth() == rDate.GetMonth()) &&
+ (aDate.GetYear() == rDate.GetYear()) )
+ {
+ tools::Long nDayX = nX+mnDaysOffX;
+ tools::Long nDayY = nY+mnDaysOffY;
+ nDayIndex = static_cast<sal_uInt16>(aDate.GetDayOfWeek());
+ nDayIndex = (nDayIndex+(7-static_cast<sal_uInt16>(ImplGetWeekStart()))) % 7;
+ for ( sal_uInt16 nDay = 1; nDay <= nDaysInMonth; nDay++ )
+ {
+ if ( nDay == rDate.GetDay() )
+ {
+ aRect.SetLeft( nDayX + (nDayIndex*mnDayWidth) );
+ aRect.SetTop( nDayY );
+ aRect.SetRight( aRect.Left()+mnDayWidth );
+ aRect.SetBottom( aRect.Top()+mnDayHeight );
+ break;
+ }
+ if ( nDayIndex == 6 )
+ {
+ nDayIndex = 0;
+ nDayY += mnDayHeight;
+ }
+ else
+ nDayIndex++;
+ }
+ }
+
+ aDate.AddDays( nDaysInMonth );
+ nX += mnMonthWidth;
+ }
+
+ nY += mnMonthHeight;
+ }
+
+ return aRect;
+}
+
+void Calendar::EndSelection()
+{
+ if ( mbDrag || mbSpinDown )
+ {
+ ReleaseMouse();
+
+ mbDrag = false;
+ mbSpinDown = false;
+ mbPrevIn = false;
+ mbNextIn = false;
+ }
+}
+
+Size Calendar::CalcWindowSizePixel() const
+{
+ Size aSize;
+ tools::Long n99TextWidth = GetTextWidth( "99" );
+ tools::Long nTextHeight = GetTextHeight();
+
+ aSize.AdjustWidth((n99TextWidth+DAY_OFFX)*7);
+ aSize.AdjustWidth(MONTH_BORDERX*2 );
+
+ aSize.setHeight( nTextHeight + TITLE_OFFY + (TITLE_BORDERY*2) );
+ aSize.AdjustHeight(nTextHeight + WEEKDAY_OFFY );
+ aSize.AdjustHeight((nTextHeight+DAY_OFFY)*6);
+ aSize.AdjustHeight(MONTH_OFFY );
+
+ return aSize;
+}
+
+Size Calendar::GetOptimalSize() const
+{
+ return CalcWindowSizePixel();
+}
+
+void Calendar::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+
+ auto aDate = GetFirstSelectedDate();
+
+ rJsonWriter.put("type", "calendar");
+ rJsonWriter.put("day", aDate.GetDay());
+ rJsonWriter.put("month", aDate.GetMonth());
+ rJsonWriter.put("year", aDate.GetYear());
+}
+
+namespace
+{
+ class ImplCFieldFloat final
+ {
+ private:
+ std::unique_ptr<weld::Builder> mxBuilder;
+ std::unique_ptr<weld::Container> mxContainer;
+ std::unique_ptr<weld::Calendar> mxCalendar;
+ std::unique_ptr<weld::Button> mxTodayBtn;
+ std::unique_ptr<weld::Button> mxNoneBtn;
+
+ public:
+ ImplCFieldFloat(vcl::Window* pContainer)
+ : mxBuilder(Application::CreateInterimBuilder(pContainer, "svt/ui/calendar.ui", false))
+ , mxContainer(mxBuilder->weld_container("Calendar"))
+ , mxCalendar(mxBuilder->weld_calendar("date"))
+ , mxTodayBtn(mxBuilder->weld_button("today"))
+ , mxNoneBtn(mxBuilder->weld_button("none"))
+ {
+ }
+
+ weld::Calendar* GetCalendar() { return mxCalendar.get(); }
+ weld::Button* EnableTodayBtn(bool bEnable);
+ weld::Button* EnableNoneBtn(bool bEnable);
+
+ void GrabFocus()
+ {
+ mxCalendar->grab_focus();
+ }
+ };
+}
+
+struct ImplCFieldFloatWin : public DropdownDockingWindow
+{
+ explicit ImplCFieldFloatWin(vcl::Window* pParent);
+ virtual void dispose() override;
+ virtual ~ImplCFieldFloatWin() override;
+ virtual void GetFocus() override;
+
+ std::unique_ptr<ImplCFieldFloat> mxWidget;
+};
+
+ImplCFieldFloatWin::ImplCFieldFloatWin(vcl::Window* pParent)
+ : DropdownDockingWindow(pParent)
+{
+ setDeferredProperties();
+ mxWidget.reset(new ImplCFieldFloat(m_xBox.get()));
+}
+
+ImplCFieldFloatWin::~ImplCFieldFloatWin()
+{
+ disposeOnce();
+}
+
+void ImplCFieldFloatWin::dispose()
+{
+ mxWidget.reset();
+ DropdownDockingWindow::dispose();
+}
+
+void ImplCFieldFloatWin::GetFocus()
+{
+ DropdownDockingWindow::GetFocus();
+ if (!mxWidget)
+ return;
+ mxWidget->GrabFocus();
+}
+
+weld::Button* ImplCFieldFloat::EnableTodayBtn(bool bEnable)
+{
+ mxTodayBtn->set_visible(bEnable);
+ return bEnable ? mxTodayBtn.get() : nullptr;
+}
+
+weld::Button* ImplCFieldFloat::EnableNoneBtn(bool bEnable)
+{
+ mxNoneBtn->set_visible(bEnable);
+ return bEnable ? mxNoneBtn.get() : nullptr;
+}
+
+CalendarField::CalendarField(vcl::Window* pParent, WinBits nWinStyle)
+ : DateField(pParent, nWinStyle)
+ , mpFloatWin(nullptr)
+ , mpTodayBtn(nullptr)
+ , mpNoneBtn(nullptr)
+ , mbToday(false)
+ , mbNone(false)
+{
+}
+
+CalendarField::~CalendarField()
+{
+ disposeOnce();
+}
+
+void CalendarField::dispose()
+{
+ mpTodayBtn = nullptr;
+ mpNoneBtn = nullptr;
+ mpFloatWin.disposeAndClear();
+ DateField::dispose();
+}
+
+IMPL_LINK(CalendarField, ImplSelectHdl, weld::Calendar&, rCalendar, void)
+{
+ Date aNewDate = rCalendar.get_date();
+
+ vcl::Window::GetDockingManager()->EndPopupMode(mpFloatWin);
+ mpFloatWin->EnableDocking(false);
+ EndDropDown();
+ GrabFocus();
+ if ( IsEmptyDate() || ( aNewDate != GetDate() ) )
+ {
+ SetDate( aNewDate );
+ SetModifyFlag();
+ Modify();
+ }
+}
+
+IMPL_LINK(CalendarField, ImplClickHdl, weld::Button&, rBtn, void)
+{
+ vcl::Window::GetDockingManager()->EndPopupMode(mpFloatWin);
+ mpFloatWin->EnableDocking(false);
+ EndDropDown();
+ GrabFocus();
+
+ if (&rBtn == mpTodayBtn)
+ {
+ Date aToday( Date::SYSTEM );
+ if ( (aToday != GetDate()) || IsEmptyDate() )
+ {
+ SetDate( aToday );
+ SetModifyFlag();
+ Modify();
+ }
+ }
+ else if (&rBtn == mpNoneBtn)
+ {
+ if ( !IsEmptyDate() )
+ {
+ SetEmptyDate();
+ SetModifyFlag();
+ Modify();
+ }
+ }
+}
+
+IMPL_LINK_NOARG(CalendarField, ImplPopupModeEndHdl, FloatingWindow*, void)
+{
+ EndDropDown();
+ GrabFocus();
+}
+
+bool CalendarField::ShowDropDown( bool bShow )
+{
+ if ( bShow )
+ {
+ if ( !mpFloatWin )
+ mpFloatWin = VclPtr<ImplCFieldFloatWin>::Create( this );
+
+ Date aDate = GetDate();
+ if ( IsEmptyDate() || !aDate.IsValidAndGregorian() )
+ {
+ aDate = Date( Date::SYSTEM );
+ }
+ weld::Calendar* pCalendar = mpFloatWin->mxWidget->GetCalendar();
+ pCalendar->set_date( aDate );
+ pCalendar->connect_activated(LINK(this, CalendarField, ImplSelectHdl));
+ mpTodayBtn = mpFloatWin->mxWidget->EnableTodayBtn(mbToday);
+ mpNoneBtn = mpFloatWin->mxWidget->EnableNoneBtn(mbNone);
+ if (mpTodayBtn)
+ mpTodayBtn->connect_clicked( LINK( this, CalendarField, ImplClickHdl ) );
+ if (mpNoneBtn)
+ mpNoneBtn->connect_clicked( LINK( this, CalendarField, ImplClickHdl ) );
+ Point aPos(GetParent()->OutputToScreenPixel(GetPosPixel()));
+ tools::Rectangle aRect(aPos, GetSizePixel());
+ aRect.AdjustBottom( -1 );
+ DockingManager* pDockingManager = vcl::Window::GetDockingManager();
+ mpFloatWin->EnableDocking(true);
+ pDockingManager->SetPopupModeEndHdl(mpFloatWin, LINK(this, CalendarField, ImplPopupModeEndHdl));
+ pDockingManager->StartPopupMode(mpFloatWin, aRect, FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus);
+ }
+ else
+ {
+ vcl::Window::GetDockingManager()->EndPopupMode(mpFloatWin);
+ mpFloatWin->EnableDocking(false);
+ EndDropDown();
+ }
+ return true;
+}
+
+void CalendarField::StateChanged( StateChangedType nStateChange )
+{
+ DateField::StateChanged( nStateChange );
+
+ if ( ( nStateChange == StateChangedType::Style ) && GetSubEdit() )
+ {
+ WinBits nAllAlignmentBits = ( WB_LEFT | WB_CENTER | WB_RIGHT | WB_TOP | WB_VCENTER | WB_BOTTOM );
+ WinBits nMyAlignment = GetStyle() & nAllAlignmentBits;
+ GetSubEdit()->SetStyle( ( GetSubEdit()->GetStyle() & ~nAllAlignmentBits ) | nMyAlignment );
+ }
+}
+
+// tdf#142783 consider the Edit and its DropDown as one compound control for the purpose of
+// notification of loss of focus from the control
+bool CalendarField::FocusWindowBelongsToControl(const vcl::Window* pFocusWin) const
+{
+ return DateField::FocusWindowBelongsToControl(pFocusWin) || (mpFloatWin && mpFloatWin->ImplIsWindowOrChild(pFocusWin));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/combobox.cxx b/vcl/source/control/combobox.cxx
new file mode 100644
index 0000000000..2a979b70b0
--- /dev/null
+++ b/vcl/source/control/combobox.cxx
@@ -0,0 +1,1608 @@
+/* -*- 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/combobox.hxx>
+
+#include <set>
+
+#include <comphelper/string.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <sal/log.hxx>
+
+#include <listbox.hxx>
+#include <comphelper/lok.hxx>
+#include <tools/json_writer.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace {
+
+struct ComboBoxBounds
+{
+ Point aSubEditPos;
+ Size aSubEditSize;
+
+ Point aButtonPos;
+ Size aButtonSize;
+};
+
+}
+
+struct ComboBox::Impl
+{
+ ComboBox & m_rThis;
+ VclPtr<Edit> m_pSubEdit;
+ VclPtr<ImplListBox> m_pImplLB;
+ VclPtr<ImplBtn> m_pBtn;
+ VclPtr<ImplListBoxFloatingWindow> m_pFloatWin;
+ sal_uInt16 m_nDDHeight;
+ sal_Unicode m_cMultiSep;
+ bool m_isDDAutoSize : 1;
+ bool m_isSyntheticModify : 1;
+ bool m_isKeyBoardModify : 1;
+ bool m_isMatchCase : 1;
+ sal_Int32 m_nMaxWidthChars;
+ sal_Int32 m_nWidthInChars;
+ Link<ComboBox&,void> m_SelectHdl;
+
+ explicit Impl(ComboBox & rThis)
+ : m_rThis(rThis)
+ , m_nDDHeight(0)
+ , m_cMultiSep(0)
+ , m_isDDAutoSize(false)
+ , m_isSyntheticModify(false)
+ , m_isKeyBoardModify(false)
+ , m_isMatchCase(false)
+ , m_nMaxWidthChars(0)
+ , m_nWidthInChars(-1)
+ {
+ }
+
+ void ImplInitComboBoxData();
+ void ImplUpdateFloatSelection();
+ ComboBoxBounds calcComboBoxDropDownComponentBounds(
+ const Size &rOutSize, const Size &rBorderOutSize) const;
+
+ DECL_LINK( ImplSelectHdl, LinkParamNone*, void );
+ DECL_LINK( ImplCancelHdl, LinkParamNone*, void );
+ DECL_LINK( ImplDoubleClickHdl, ImplListBoxWindow*, void );
+ DECL_LINK( ImplClickBtnHdl, void*, void );
+ DECL_LINK( ImplPopupModeEndHdl, FloatingWindow*, void );
+ DECL_LINK( ImplSelectionChangedHdl, sal_Int32, void );
+ DECL_LINK( ImplAutocompleteHdl, Edit&, void );
+ DECL_LINK( ImplListItemSelectHdl , LinkParamNone*, void );
+};
+
+
+static void lcl_GetSelectedEntries( ::std::set< sal_Int32 >& rSelectedPos, std::u16string_view rText, sal_Unicode cTokenSep, const ImplEntryList& rEntryList )
+{
+ if (rText.empty())
+ return;
+
+ sal_Int32 nIdx{0};
+ do {
+ const sal_Int32 nPos = rEntryList.FindEntry(comphelper::string::strip(o3tl::getToken(rText, 0, cTokenSep, nIdx), ' '));
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ rSelectedPos.insert( nPos );
+ } while (nIdx>=0);
+}
+
+ComboBox::ComboBox(vcl::Window *const pParent, WinBits const nStyle)
+ : Edit( WindowType::COMBOBOX )
+ , m_pImpl(new Impl(*this))
+{
+ m_pImpl->ImplInitComboBoxData();
+ ImplInit( pParent, nStyle );
+ SetWidthInChars(-1);
+}
+
+ComboBox::~ComboBox()
+{
+ disposeOnce();
+}
+
+void ComboBox::dispose()
+{
+ m_pImpl->m_pSubEdit.disposeAndClear();
+
+ VclPtr< ImplListBox > pImplLB = m_pImpl->m_pImplLB;
+ m_pImpl->m_pImplLB.clear();
+ pImplLB.disposeAndClear();
+
+ m_pImpl->m_pFloatWin.disposeAndClear();
+ m_pImpl->m_pBtn.disposeAndClear();
+ Edit::dispose();
+}
+
+void ComboBox::Impl::ImplInitComboBoxData()
+{
+ m_pSubEdit.disposeAndClear();
+ m_pBtn = nullptr;
+ m_pImplLB = nullptr;
+ m_pFloatWin = nullptr;
+
+ m_nDDHeight = 0;
+ m_isDDAutoSize = true;
+ m_isSyntheticModify = false;
+ m_isKeyBoardModify = false;
+ m_isMatchCase = false;
+ m_cMultiSep = ';';
+ m_nMaxWidthChars = -1;
+ m_nWidthInChars = -1;
+}
+
+void ComboBox::ImplCalcEditHeight()
+{
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ GetBorder( nLeft, nTop, nRight, nBottom );
+ m_pImpl->m_nDDHeight = static_cast<sal_uInt16>(m_pImpl->m_pSubEdit->GetTextHeight() + nTop + nBottom + 4);
+ if ( !IsDropDownBox() )
+ m_pImpl->m_nDDHeight += 4;
+
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), Size( 10, 10 ) );
+ tools::Rectangle aBoundRegion, aContentRegion;
+ ImplControlValue aControlValue;
+ ControlType aType = IsDropDownBox() ? ControlType::Combobox : ControlType::Editbox;
+ if( GetNativeControlRegion( aType, ControlPart::Entire,
+ aCtrlRegion,
+ ControlState::ENABLED,
+ aControlValue,
+ aBoundRegion, aContentRegion ) )
+ {
+ const tools::Long nNCHeight = aBoundRegion.GetHeight();
+ if (m_pImpl->m_nDDHeight < nNCHeight)
+ m_pImpl->m_nDDHeight = sal::static_int_cast<sal_uInt16>(nNCHeight);
+ }
+}
+
+void ComboBox::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ bool bNoBorder = ( nStyle & WB_NOBORDER ) != 0;
+ if ( !(nStyle & WB_DROPDOWN) )
+ {
+ nStyle &= ~WB_BORDER;
+ nStyle |= WB_NOBORDER;
+ }
+ else
+ {
+ if ( !bNoBorder )
+ nStyle |= WB_BORDER;
+ }
+
+ Edit::ImplInit( pParent, nStyle );
+ SetBackground();
+
+ // DropDown ?
+ WinBits nEditStyle = nStyle & ( WB_LEFT | WB_RIGHT | WB_CENTER );
+ WinBits nListStyle = nStyle;
+ if( nStyle & WB_DROPDOWN )
+ {
+ m_pImpl->m_pFloatWin = VclPtr<ImplListBoxFloatingWindow>::Create( this );
+ if (!IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus))
+ m_pImpl->m_pFloatWin->RequestDoubleBuffering(true);
+ m_pImpl->m_pFloatWin->SetAutoWidth( true );
+ m_pImpl->m_pFloatWin->SetPopupModeEndHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplPopupModeEndHdl) );
+
+ m_pImpl->m_pBtn = VclPtr<ImplBtn>::Create( this, WB_NOLIGHTBORDER | WB_RECTSTYLE );
+ ImplInitDropDownButton( m_pImpl->m_pBtn );
+ m_pImpl->m_pBtn->SetMBDownHdl( LINK( m_pImpl.get(), ComboBox::Impl, ImplClickBtnHdl ) );
+ m_pImpl->m_pBtn->Show();
+
+ nEditStyle |= WB_NOBORDER;
+ nListStyle &= ~WB_BORDER;
+ nListStyle |= WB_NOBORDER;
+ }
+ else
+ {
+ if ( !bNoBorder )
+ {
+ nEditStyle |= WB_BORDER;
+ nListStyle &= ~WB_NOBORDER;
+ nListStyle |= WB_BORDER;
+ }
+ }
+
+ m_pImpl->m_pSubEdit.set( VclPtr<Edit>::Create( this, nEditStyle ) );
+ m_pImpl->m_pSubEdit->EnableRTL( false );
+ SetSubEdit( m_pImpl->m_pSubEdit );
+ m_pImpl->m_pSubEdit->SetPosPixel( Point() );
+ EnableAutocomplete( true );
+ m_pImpl->m_pSubEdit->Show();
+
+ vcl::Window* pLBParent = this;
+ if (m_pImpl->m_pFloatWin)
+ pLBParent = m_pImpl->m_pFloatWin;
+ m_pImpl->m_pImplLB = VclPtr<ImplListBox>::Create( pLBParent, nListStyle|WB_SIMPLEMODE|WB_AUTOHSCROLL );
+ m_pImpl->m_pImplLB->SetPosPixel( Point() );
+ m_pImpl->m_pImplLB->SetSelectHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplSelectHdl) );
+ m_pImpl->m_pImplLB->SetCancelHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplCancelHdl) );
+ m_pImpl->m_pImplLB->SetDoubleClickHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplDoubleClickHdl) );
+ m_pImpl->m_pImplLB->SetSelectionChangedHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplSelectionChangedHdl) );
+ m_pImpl->m_pImplLB->SetListItemSelectHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplListItemSelectHdl) );
+ m_pImpl->m_pImplLB->Show();
+
+ if (m_pImpl->m_pFloatWin)
+ m_pImpl->m_pFloatWin->SetImplListBox( m_pImpl->m_pImplLB );
+ else
+ GetMainWindow()->AllowGrabFocus( true );
+
+ ImplCalcEditHeight();
+
+ SetCompoundControl( true );
+}
+
+WinBits ComboBox::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+void ComboBox::EnableAutocomplete( bool bEnable, bool bMatchCase )
+{
+ m_pImpl->m_isMatchCase = bMatchCase;
+
+ if ( bEnable )
+ m_pImpl->m_pSubEdit->SetAutocompleteHdl( LINK(m_pImpl.get(), ComboBox::Impl, ImplAutocompleteHdl) );
+ else
+ m_pImpl->m_pSubEdit->SetAutocompleteHdl( Link<Edit&,void>() );
+}
+
+bool ComboBox::IsAutocompleteEnabled() const
+{
+ return m_pImpl->m_pSubEdit->GetAutocompleteHdl().IsSet();
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplClickBtnHdl, void*, void)
+{
+ m_rThis.CallEventListeners( VclEventId::DropdownPreOpen );
+ m_pSubEdit->GrabFocus();
+ if (!m_pImplLB->GetEntryList().GetMRUCount())
+ ImplUpdateFloatSelection();
+ else
+ m_pImplLB->SelectEntry( 0 , true );
+ m_pBtn->SetPressed( true );
+ m_rThis.SetSelection( Selection( 0, SELECTION_MAX ) );
+ m_pFloatWin->StartFloat( true );
+ m_rThis.CallEventListeners( VclEventId::DropdownOpen );
+
+ m_rThis.ImplClearLayoutData();
+ if (m_pImplLB)
+ m_pImplLB->GetMainWindow()->ImplClearLayoutData();
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplPopupModeEndHdl, FloatingWindow*, void)
+{
+ if (m_pFloatWin->IsPopupModeCanceled())
+ {
+ if (!m_pImplLB->GetEntryList().IsEntryPosSelected(
+ m_pFloatWin->GetPopupModeStartSaveSelection()))
+ {
+ m_pImplLB->SelectEntry(m_pFloatWin->GetPopupModeStartSaveSelection(), true);
+ bool bTravelSelect = m_pImplLB->IsTravelSelect();
+ m_pImplLB->SetTravelSelect( true );
+ m_rThis.Select();
+ m_pImplLB->SetTravelSelect( bTravelSelect );
+ }
+ }
+
+ m_rThis.ImplClearLayoutData();
+ if (m_pImplLB)
+ m_pImplLB->GetMainWindow()->ImplClearLayoutData();
+
+ m_pBtn->SetPressed( false );
+ m_rThis.CallEventListeners( VclEventId::DropdownClose );
+}
+
+IMPL_LINK(ComboBox::Impl, ImplAutocompleteHdl, Edit&, rEdit, void)
+{
+ Selection aSel = rEdit.GetSelection();
+
+ {
+ OUString aFullText = rEdit.GetText();
+ OUString aStartText = aFullText.copy( 0, static_cast<sal_Int32>(aSel.Max()) );
+ sal_Int32 nStart = m_pImplLB->GetCurrentPos();
+
+ if ( nStart == LISTBOX_ENTRY_NOTFOUND )
+ nStart = 0;
+
+ sal_Int32 nPos = LISTBOX_ENTRY_NOTFOUND;
+ if (!m_isMatchCase)
+ {
+ // Try match case insensitive from current position
+ nPos = m_pImplLB->GetEntryList().FindMatchingEntry(aStartText, nStart, true);
+ if ( nPos == LISTBOX_ENTRY_NOTFOUND )
+ // Try match case insensitive, but from start
+ nPos = m_pImplLB->GetEntryList().FindMatchingEntry(aStartText, 0, true);
+ }
+
+ if ( nPos == LISTBOX_ENTRY_NOTFOUND )
+ // Try match full from current position
+ nPos = m_pImplLB->GetEntryList().FindMatchingEntry(aStartText, nStart, false);
+ if ( nPos == LISTBOX_ENTRY_NOTFOUND )
+ // Match full, but from start
+ nPos = m_pImplLB->GetEntryList().FindMatchingEntry(aStartText, 0, false);
+
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ OUString aText = m_pImplLB->GetEntryList().GetEntryText( nPos );
+ Selection aSelection( aText.getLength(), aStartText.getLength() );
+ rEdit.SetText( aText, aSelection );
+ }
+ }
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplSelectHdl, LinkParamNone*, void)
+{
+ bool bPopup = m_rThis.IsInDropDown();
+ bool bCallSelect = false;
+ if (m_pImplLB->IsSelectionChanged() || bPopup)
+ {
+ OUString aText;
+ if (m_rThis.IsMultiSelectionEnabled())
+ {
+ aText = m_pSubEdit->GetText();
+
+ // remove all entries to which there is an entry, but which is not selected
+ sal_Int32 nIndex = 0;
+ while ( nIndex >= 0 )
+ {
+ sal_Int32 nPrevIndex = nIndex;
+ std::u16string_view aToken = o3tl::getToken(aText, 0, m_cMultiSep, nIndex );
+ sal_Int32 nTokenLen = aToken.size();
+ aToken = comphelper::string::strip(aToken, ' ');
+ sal_Int32 nP = m_pImplLB->GetEntryList().FindEntry( aToken );
+ if ((nP != LISTBOX_ENTRY_NOTFOUND) && (!m_pImplLB->GetEntryList().IsEntryPosSelected(nP)))
+ {
+ aText = aText.replaceAt( nPrevIndex, nTokenLen, u"" );
+ nIndex = nIndex - nTokenLen;
+ sal_Int32 nSepCount=0;
+ if ((nPrevIndex+nSepCount < aText.getLength()) && (aText[nPrevIndex+nSepCount] == m_cMultiSep))
+ {
+ nIndex--;
+ ++nSepCount;
+ }
+ aText = aText.replaceAt( nPrevIndex, nSepCount, u"" );
+ }
+ aText = comphelper::string::strip(aText, ' ');
+ }
+
+ // attach missing entries
+ ::std::set< sal_Int32 > aSelInText;
+ lcl_GetSelectedEntries( aSelInText, aText, m_cMultiSep, m_pImplLB->GetEntryList() );
+ sal_Int32 nSelectedEntries = m_pImplLB->GetEntryList().GetSelectedEntryCount();
+ for ( sal_Int32 n = 0; n < nSelectedEntries; n++ )
+ {
+ sal_Int32 nP = m_pImplLB->GetEntryList().GetSelectedEntryPos( n );
+ if ( !aSelInText.count( nP ) )
+ {
+ if (!aText.isEmpty() && (aText[aText.getLength()-1] != m_cMultiSep))
+ aText += OUStringChar(m_cMultiSep);
+ if ( !aText.isEmpty() )
+ aText += " "; // slightly loosen
+ aText += m_pImplLB->GetEntryList().GetEntryText( nP ) +
+ OUStringChar(m_cMultiSep);
+ }
+ }
+ aText = comphelper::string::stripEnd( aText, m_cMultiSep );
+ }
+ else
+ {
+ aText = m_pImplLB->GetEntryList().GetSelectedEntry( 0 );
+ }
+
+ m_pSubEdit->SetText( aText );
+
+ Selection aNewSelection( 0, aText.getLength() );
+ if (m_rThis.IsMultiSelectionEnabled())
+ aNewSelection.Min() = aText.getLength();
+ m_pSubEdit->SetSelection( aNewSelection );
+
+ bCallSelect = true;
+ }
+
+ // #84652# Call GrabFocus and EndPopupMode before calling Select/Modify, but after changing the text
+ bool bMenuSelect = bPopup && !m_pImplLB->IsTravelSelect() && (!m_rThis.IsMultiSelectionEnabled() || !m_pImplLB->GetSelectModifier());
+ if (bMenuSelect)
+ {
+ m_pFloatWin->EndPopupMode();
+ m_rThis.GrabFocus();
+ }
+
+ if ( bCallSelect )
+ {
+ m_isKeyBoardModify = !bMenuSelect;
+ m_pSubEdit->SetModifyFlag();
+ m_isSyntheticModify = true;
+ m_rThis.Modify();
+ m_isSyntheticModify = false;
+ m_rThis.Select();
+ m_isKeyBoardModify = false;
+ }
+}
+
+bool ComboBox::IsSyntheticModify() const
+{
+ return m_pImpl->m_isSyntheticModify;
+}
+
+bool ComboBox::IsModifyByKeyboard() const
+{
+ return m_pImpl->m_isKeyBoardModify;
+}
+
+IMPL_LINK_NOARG( ComboBox::Impl, ImplListItemSelectHdl, LinkParamNone*, void )
+{
+ m_rThis.CallEventListeners( VclEventId::DropdownSelect );
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplCancelHdl, LinkParamNone*, void)
+{
+ if (m_rThis.IsInDropDown())
+ m_pFloatWin->EndPopupMode();
+}
+
+IMPL_LINK( ComboBox::Impl, ImplSelectionChangedHdl, sal_Int32, nChanged, void )
+{
+ if (!m_pImplLB->IsTrackingSelect())
+ {
+ if (!m_pSubEdit->IsReadOnly() && m_pImplLB->GetEntryList().IsEntryPosSelected(nChanged))
+ m_pSubEdit->SetText(m_pImplLB->GetEntryList().GetEntryText(nChanged));
+ }
+}
+
+IMPL_LINK_NOARG(ComboBox::Impl, ImplDoubleClickHdl, ImplListBoxWindow*, void)
+{
+ m_rThis.DoubleClick();
+}
+
+void ComboBox::ToggleDropDown()
+{
+ if( !IsDropDownBox() )
+ return;
+
+ if (m_pImpl->m_pFloatWin->IsInPopupMode())
+ m_pImpl->m_pFloatWin->EndPopupMode();
+ else
+ {
+ m_pImpl->m_pSubEdit->GrabFocus();
+ if (!m_pImpl->m_pImplLB->GetEntryList().GetMRUCount())
+ m_pImpl->ImplUpdateFloatSelection();
+ else
+ m_pImpl->m_pImplLB->SelectEntry( 0 , true );
+ CallEventListeners( VclEventId::DropdownPreOpen );
+ m_pImpl->m_pBtn->SetPressed( true );
+ SetSelection( Selection( 0, SELECTION_MAX ) );
+ m_pImpl->m_pFloatWin->StartFloat( true );
+ CallEventListeners( VclEventId::DropdownOpen );
+ }
+}
+
+void ComboBox::Select()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ComboboxSelect, [this] () { m_pImpl->m_SelectHdl.Call(*this); } );
+}
+
+void ComboBox::DoubleClick()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ComboboxDoubleClick, [] () {} );
+}
+
+bool ComboBox::IsAutoSizeEnabled() const { return m_pImpl->m_isDDAutoSize; }
+
+void ComboBox::EnableAutoSize( bool bAuto )
+{
+ m_pImpl->m_isDDAutoSize = bAuto;
+ if (m_pImpl->m_pFloatWin)
+ {
+ if (bAuto && !m_pImpl->m_pFloatWin->GetDropDownLineCount())
+ {
+ // Adapt to GetListBoxMaximumLineCount here; was on fixed number of five before
+ AdaptDropDownLineCountToMaximum();
+ }
+ else if ( !bAuto )
+ {
+ m_pImpl->m_pFloatWin->SetDropDownLineCount( 0 );
+ }
+ }
+}
+
+void ComboBox::SetDropDownLineCount( sal_uInt16 nLines )
+{
+ if (m_pImpl->m_pFloatWin)
+ m_pImpl->m_pFloatWin->SetDropDownLineCount( nLines );
+}
+
+void ComboBox::AdaptDropDownLineCountToMaximum()
+{
+ // Adapt to maximum allowed number.
+ // Limit for LOK as we can't render outside of the dialog canvas.
+ if (comphelper::LibreOfficeKit::isActive())
+ SetDropDownLineCount(11);
+ else
+ SetDropDownLineCount(GetSettings().GetStyleSettings().GetListBoxMaximumLineCount());
+}
+
+sal_uInt16 ComboBox::GetDropDownLineCount() const
+{
+ sal_uInt16 nLines = 0;
+ if (m_pImpl->m_pFloatWin)
+ nLines = m_pImpl->m_pFloatWin->GetDropDownLineCount();
+ return nLines;
+}
+
+void ComboBox::setPosSizePixel( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight,
+ PosSizeFlags nFlags )
+{
+ if( IsDropDownBox() && ( nFlags & PosSizeFlags::Size ) )
+ {
+ Size aPrefSz = m_pImpl->m_pFloatWin->GetPrefSize();
+ if ((nFlags & PosSizeFlags::Height) && (nHeight >= 2*m_pImpl->m_nDDHeight))
+ aPrefSz.setHeight( nHeight-m_pImpl->m_nDDHeight );
+ if ( nFlags & PosSizeFlags::Width )
+ aPrefSz.setWidth( nWidth );
+ m_pImpl->m_pFloatWin->SetPrefSize( aPrefSz );
+
+ if (IsAutoSizeEnabled())
+ nHeight = m_pImpl->m_nDDHeight;
+ }
+
+ Edit::setPosSizePixel( nX, nY, nWidth, nHeight, nFlags );
+}
+
+void ComboBox::Resize()
+{
+ Control::Resize();
+
+ if (m_pImpl->m_pSubEdit)
+ {
+ Size aOutSz = GetOutputSizePixel();
+ if( IsDropDownBox() )
+ {
+ ComboBoxBounds aBounds(m_pImpl->calcComboBoxDropDownComponentBounds(aOutSz,
+ GetWindow(GetWindowType::Border)->GetOutputSizePixel()));
+ m_pImpl->m_pSubEdit->SetPosSizePixel(aBounds.aSubEditPos, aBounds.aSubEditSize);
+ m_pImpl->m_pBtn->SetPosSizePixel(aBounds.aButtonPos, aBounds.aButtonSize);
+ }
+ else
+ {
+ m_pImpl->m_pSubEdit->SetSizePixel(Size(aOutSz.Width(), m_pImpl->m_nDDHeight));
+ m_pImpl->m_pImplLB->setPosSizePixel(0, m_pImpl->m_nDDHeight,
+ aOutSz.Width(), aOutSz.Height() - m_pImpl->m_nDDHeight);
+ if ( !GetText().isEmpty() )
+ m_pImpl->ImplUpdateFloatSelection();
+ }
+ }
+
+ // adjust the size of the FloatingWindow even when invisible
+ // as KEY_PGUP/DOWN is being processed...
+ if (m_pImpl->m_pFloatWin)
+ m_pImpl->m_pFloatWin->SetSizePixel(m_pImpl->m_pFloatWin->CalcFloatSize());
+}
+
+bool ComboBox::IsDropDownBox() const { return m_pImpl->m_pFloatWin != nullptr; }
+
+void ComboBox::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ AppendLayoutData( *m_pImpl->m_pSubEdit );
+ m_pImpl->m_pSubEdit->SetLayoutDataParent( this );
+ ImplListBoxWindow* rMainWindow = GetMainWindow();
+ if (m_pImpl->m_pFloatWin)
+ {
+ // dropdown mode
+ if (m_pImpl->m_pFloatWin->IsReallyVisible())
+ {
+ AppendLayoutData( *rMainWindow );
+ rMainWindow->SetLayoutDataParent( this );
+ }
+ }
+ else
+ {
+ AppendLayoutData( *rMainWindow );
+ rMainWindow->SetLayoutDataParent( this );
+ }
+}
+
+void ComboBox::StateChanged( StateChangedType nType )
+{
+ Edit::StateChanged( nType );
+
+ if ( nType == StateChangedType::ReadOnly )
+ {
+ m_pImpl->m_pImplLB->SetReadOnly( IsReadOnly() );
+ if (m_pImpl->m_pBtn)
+ m_pImpl->m_pBtn->Enable( IsEnabled() && !IsReadOnly() );
+ }
+ else if ( nType == StateChangedType::Enable )
+ {
+ m_pImpl->m_pSubEdit->Enable( IsEnabled() );
+ m_pImpl->m_pImplLB->Enable( IsEnabled() && !IsReadOnly() );
+ if (m_pImpl->m_pBtn)
+ m_pImpl->m_pBtn->Enable( IsEnabled() && !IsReadOnly() );
+ Invalidate();
+ }
+ else if( nType == StateChangedType::UpdateMode )
+ {
+ m_pImpl->m_pImplLB->SetUpdateMode( IsUpdateMode() );
+ }
+ else if ( nType == StateChangedType::Zoom )
+ {
+ m_pImpl->m_pImplLB->SetZoom( GetZoom() );
+ m_pImpl->m_pSubEdit->SetZoom( GetZoom() );
+ ImplCalcEditHeight();
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlFont )
+ {
+ m_pImpl->m_pImplLB->SetControlFont( GetControlFont() );
+ m_pImpl->m_pSubEdit->SetControlFont( GetControlFont() );
+ ImplCalcEditHeight();
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ m_pImpl->m_pImplLB->SetControlForeground( GetControlForeground() );
+ m_pImpl->m_pSubEdit->SetControlForeground( GetControlForeground() );
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ m_pImpl->m_pImplLB->SetControlBackground( GetControlBackground() );
+ m_pImpl->m_pSubEdit->SetControlBackground( GetControlBackground() );
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ GetMainWindow()->EnableSort( ( GetStyle() & WB_SORT ) != 0 );
+ }
+ else if( nType == StateChangedType::Mirroring )
+ {
+ if (m_pImpl->m_pBtn)
+ {
+ m_pImpl->m_pBtn->EnableRTL( IsRTLEnabled() );
+ ImplInitDropDownButton( m_pImpl->m_pBtn );
+ }
+ m_pImpl->m_pSubEdit->CompatStateChanged( StateChangedType::Mirroring );
+ m_pImpl->m_pImplLB->EnableRTL( IsRTLEnabled() );
+ Resize();
+ }
+}
+
+void ComboBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( !((rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))) )
+ return;
+
+ if (m_pImpl->m_pBtn)
+ {
+ m_pImpl->m_pBtn->GetOutDev()->SetSettings( GetSettings() );
+ ImplInitDropDownButton( m_pImpl->m_pBtn );
+ }
+ Resize();
+ m_pImpl->m_pImplLB->Resize(); // not called by ComboBox::Resize() if ImplLB is unchanged
+
+ SetBackground(); // due to a hack in Window::UpdateSettings the background must be reset
+ // otherwise it will overpaint NWF drawn comboboxes
+}
+
+bool ComboBox::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if ((rNEvt.GetType() == NotifyEventType::KEYINPUT)
+ && (rNEvt.GetWindow() == m_pImpl->m_pSubEdit)
+ && !IsReadOnly())
+ {
+ KeyEvent aKeyEvt = *rNEvt.GetKeyEvent();
+ sal_uInt16 nKeyCode = aKeyEvt.GetKeyCode().GetCode();
+ switch( nKeyCode )
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ {
+ m_pImpl->ImplUpdateFloatSelection();
+ if ((nKeyCode == KEY_DOWN) && m_pImpl->m_pFloatWin
+ && !m_pImpl->m_pFloatWin->IsInPopupMode()
+ && aKeyEvt.GetKeyCode().IsMod2())
+ {
+ CallEventListeners( VclEventId::DropdownPreOpen );
+ m_pImpl->m_pBtn->SetPressed( true );
+ if (m_pImpl->m_pImplLB->GetEntryList().GetMRUCount())
+ m_pImpl->m_pImplLB->SelectEntry( 0 , true );
+ SetSelection( Selection( 0, SELECTION_MAX ) );
+ m_pImpl->m_pFloatWin->StartFloat( false );
+ CallEventListeners( VclEventId::DropdownOpen );
+ bDone = true;
+ }
+ else if ((nKeyCode == KEY_UP) && m_pImpl->m_pFloatWin
+ && m_pImpl->m_pFloatWin->IsInPopupMode()
+ && aKeyEvt.GetKeyCode().IsMod2())
+ {
+ m_pImpl->m_pFloatWin->EndPopupMode();
+ bDone = true;
+ }
+ else
+ {
+ bDone = m_pImpl->m_pImplLB->ProcessKeyInput( aKeyEvt );
+ }
+ }
+ break;
+
+ case KEY_RETURN:
+ {
+ if ((rNEvt.GetWindow() == m_pImpl->m_pSubEdit) && IsInDropDown())
+ {
+ m_pImpl->m_pImplLB->ProcessKeyInput( aKeyEvt );
+ bDone = true;
+ }
+ }
+ break;
+ }
+ }
+ else if ((rNEvt.GetType() == NotifyEventType::LOSEFOCUS) && m_pImpl->m_pFloatWin)
+ {
+ if (m_pImpl->m_pFloatWin->HasChildPathFocus())
+ m_pImpl->m_pSubEdit->GrabFocus();
+ else if (m_pImpl->m_pFloatWin->IsInPopupMode() && !HasChildPathFocus(true))
+ m_pImpl->m_pFloatWin->EndPopupMode();
+ }
+ else if( (rNEvt.GetType() == NotifyEventType::COMMAND) &&
+ (rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) &&
+ (rNEvt.GetWindow() == m_pImpl->m_pSubEdit) )
+ {
+ MouseWheelBehaviour nWheelBehavior( GetSettings().GetMouseSettings().GetWheelBehavior() );
+ if ( ( nWheelBehavior == MouseWheelBehaviour::ALWAYS )
+ || ( ( nWheelBehavior == MouseWheelBehaviour::FocusOnly )
+ && HasChildPathFocus()
+ )
+ )
+ {
+ bDone = m_pImpl->m_pImplLB->HandleWheelAsCursorTravel(*rNEvt.GetCommandEvent(), *this);
+ }
+ else
+ {
+ bDone = false; // don't eat this event, let the default handling happen (i.e. scroll the context)
+ }
+ }
+ else if ((rNEvt.GetType() == NotifyEventType::MOUSEBUTTONDOWN)
+ && (rNEvt.GetWindow() == GetMainWindow()))
+ {
+ m_pImpl->m_pSubEdit->GrabFocus();
+ }
+
+ return bDone || Edit::EventNotify( rNEvt );
+}
+
+void ComboBox::SetText( const OUString& rStr )
+{
+ CallEventListeners( VclEventId::ComboboxSetText );
+
+ Edit::SetText( rStr );
+ m_pImpl->ImplUpdateFloatSelection();
+}
+
+void ComboBox::SetText( const OUString& rStr, const Selection& rNewSelection )
+{
+ CallEventListeners( VclEventId::ComboboxSetText );
+
+ Edit::SetText( rStr, rNewSelection );
+ m_pImpl->ImplUpdateFloatSelection();
+}
+
+void ComboBox::Modify()
+{
+ if (!m_pImpl->m_isSyntheticModify)
+ m_pImpl->ImplUpdateFloatSelection();
+
+ Edit::Modify();
+}
+
+void ComboBox::Impl::ImplUpdateFloatSelection()
+{
+ if (!m_pImplLB || !m_pSubEdit)
+ return;
+
+ // move text in the ListBox into the visible region
+ m_pImplLB->SetCallSelectionChangedHdl( false );
+ if (!m_rThis.IsMultiSelectionEnabled())
+ {
+ OUString aSearchStr( m_pSubEdit->GetText() );
+ sal_Int32 nSelect = LISTBOX_ENTRY_NOTFOUND;
+ bool bSelect = true;
+
+ if (m_pImplLB->GetCurrentPos() != LISTBOX_ENTRY_NOTFOUND)
+ {
+ OUString aCurrent = m_pImplLB->GetEntryList().GetEntryText(
+ m_pImplLB->GetCurrentPos());
+ if ( aCurrent == aSearchStr )
+ nSelect = m_pImplLB->GetCurrentPos();
+ }
+
+ if ( nSelect == LISTBOX_ENTRY_NOTFOUND )
+ nSelect = m_pImplLB->GetEntryList().FindEntry( aSearchStr );
+ if ( nSelect == LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = m_pImplLB->GetEntryList().FindMatchingEntry( aSearchStr, 0, true );
+ bSelect = false;
+ }
+
+ if( nSelect != LISTBOX_ENTRY_NOTFOUND )
+ {
+ if (!m_pImplLB->IsVisible(nSelect))
+ m_pImplLB->ShowProminentEntry( nSelect );
+ m_pImplLB->SelectEntry( nSelect, bSelect );
+ }
+ else
+ {
+ nSelect = m_pImplLB->GetEntryList().GetSelectedEntryPos( 0 );
+ if( nSelect != LISTBOX_ENTRY_NOTFOUND )
+ m_pImplLB->SelectEntry( nSelect, false );
+ m_pImplLB->ResetCurrentPos();
+ }
+ }
+ else
+ {
+ ::std::set< sal_Int32 > aSelInText;
+ lcl_GetSelectedEntries(aSelInText, m_pSubEdit->GetText(), m_cMultiSep, m_pImplLB->GetEntryList());
+ for (sal_Int32 n = 0; n < m_pImplLB->GetEntryList().GetEntryCount(); n++)
+ m_pImplLB->SelectEntry( n, aSelInText.count( n ) != 0 );
+ }
+ m_pImplLB->SetCallSelectionChangedHdl( true );
+}
+
+sal_Int32 ComboBox::InsertEntry(const OUString& rStr, sal_Int32 const nPos)
+{
+ assert(nPos >= 0 && COMBOBOX_MAX_ENTRIES > m_pImpl->m_pImplLB->GetEntryList().GetEntryCount());
+
+ sal_Int32 nRealPos;
+ if (nPos == COMBOBOX_APPEND)
+ nRealPos = nPos;
+ else
+ {
+ const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ assert(nPos <= COMBOBOX_MAX_ENTRIES - nMRUCount);
+ nRealPos = nPos + nMRUCount;
+ }
+
+ nRealPos = m_pImpl->m_pImplLB->InsertEntry( nRealPos, rStr );
+ nRealPos -= m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ CallEventListeners( VclEventId::ComboboxItemAdded, reinterpret_cast<void*>(nRealPos) );
+ return nRealPos;
+}
+
+sal_Int32 ComboBox::InsertEntryWithImage(
+ const OUString& rStr, const Image& rImage, sal_Int32 const nPos)
+{
+ assert(nPos >= 0 && COMBOBOX_MAX_ENTRIES > m_pImpl->m_pImplLB->GetEntryList().GetEntryCount());
+
+ sal_Int32 nRealPos;
+ if (nPos == COMBOBOX_APPEND)
+ nRealPos = nPos;
+ else
+ {
+ const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ assert(nPos <= COMBOBOX_MAX_ENTRIES - nMRUCount);
+ nRealPos = nPos + nMRUCount;
+ }
+
+ nRealPos = m_pImpl->m_pImplLB->InsertEntry( nRealPos, rStr, rImage );
+ nRealPos -= m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ CallEventListeners( VclEventId::ComboboxItemAdded, reinterpret_cast<void*>(nRealPos) );
+ return nRealPos;
+}
+
+void ComboBox::RemoveEntryAt(sal_Int32 const nPos)
+{
+ const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ assert(nPos >= 0 && nPos <= COMBOBOX_MAX_ENTRIES - nMRUCount);
+ m_pImpl->m_pImplLB->RemoveEntry( nPos + nMRUCount );
+ CallEventListeners( VclEventId::ComboboxItemRemoved, reinterpret_cast<void*>(nPos) );
+}
+
+void ComboBox::Clear()
+{
+ if (!m_pImpl->m_pImplLB)
+ return;
+ m_pImpl->m_pImplLB->Clear();
+ CallEventListeners( VclEventId::ComboboxItemRemoved, reinterpret_cast<void*>(-1) );
+}
+
+Image ComboBox::GetEntryImage( sal_Int32 nPos ) const
+{
+ if (m_pImpl->m_pImplLB->GetEntryList().HasEntryImage(nPos))
+ return m_pImpl->m_pImplLB->GetEntryList().GetEntryImage( nPos );
+ return Image();
+}
+
+sal_Int32 ComboBox::GetEntryPos( std::u16string_view rStr ) const
+{
+ sal_Int32 nPos = m_pImpl->m_pImplLB->GetEntryList().FindEntry( rStr );
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ nPos -= m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ return nPos;
+}
+
+OUString ComboBox::GetEntry( sal_Int32 nPos ) const
+{
+ const sal_Int32 nMRUCount = m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+ if (nPos < 0 || nPos > COMBOBOX_MAX_ENTRIES - nMRUCount)
+ return OUString();
+
+ return m_pImpl->m_pImplLB->GetEntryList().GetEntryText( nPos + nMRUCount );
+}
+
+sal_Int32 ComboBox::GetEntryCount() const
+{
+ if (!m_pImpl->m_pImplLB)
+ return 0;
+ return m_pImpl->m_pImplLB->GetEntryList().GetEntryCount() - m_pImpl->m_pImplLB->GetEntryList().GetMRUCount();
+}
+
+bool ComboBox::IsTravelSelect() const
+{
+ return m_pImpl->m_pImplLB->IsTravelSelect();
+}
+
+bool ComboBox::IsInDropDown() const
+{
+ // when the dropdown is dismissed, first mbInPopupMode is set to false, and on the next event iteration then
+ // mbPopupMode is set to false
+ return m_pImpl->m_pFloatWin && m_pImpl->m_pFloatWin->IsInPopupMode() && m_pImpl->m_pFloatWin->ImplIsInPrivatePopupMode();
+}
+
+bool ComboBox::IsMultiSelectionEnabled() const
+{
+ return m_pImpl->m_pImplLB->IsMultiSelectionEnabled();
+}
+
+void ComboBox::SetSelectHdl(const Link<ComboBox&,void>& rLink) { m_pImpl->m_SelectHdl = rLink; }
+
+void ComboBox::SetEntryActivateHdl(const Link<Edit&,bool>& rLink)
+{
+ if (!m_pImpl->m_pSubEdit)
+ return;
+ m_pImpl->m_pSubEdit->SetActivateHdl(rLink);
+}
+
+Size ComboBox::GetOptimalSize() const
+{
+ return CalcMinimumSize();
+}
+
+tools::Long ComboBox::getMaxWidthScrollBarAndDownButton() const
+{
+ tools::Long nButtonDownWidth = 0;
+
+ vcl::Window *pBorder = GetWindow( GetWindowType::Border );
+ ImplControlValue aControlValue;
+ tools::Rectangle aContent, aBound;
+
+ // use the full extent of the control
+ tools::Rectangle aArea( Point(), pBorder->GetOutputSizePixel() );
+
+ if ( GetNativeControlRegion(ControlType::Combobox, ControlPart::ButtonDown,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ nButtonDownWidth = aContent.getOpenWidth();
+ }
+
+ tools::Long nScrollBarWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
+
+ return std::max(nScrollBarWidth, nButtonDownWidth);
+}
+
+Size ComboBox::CalcMinimumSize() const
+{
+ Size aSz;
+
+ if (!m_pImpl->m_pImplLB)
+ return aSz;
+
+ if (!IsDropDownBox())
+ {
+ aSz = m_pImpl->m_pImplLB->CalcSize( m_pImpl->m_pImplLB->GetEntryList().GetEntryCount() );
+ aSz.AdjustHeight(m_pImpl->m_nDDHeight );
+ }
+ else
+ {
+ aSz.setHeight( Edit::CalcMinimumSizeForText(GetText()).Height() );
+
+ if (m_pImpl->m_nWidthInChars!= -1)
+ aSz.setWidth(m_pImpl->m_nWidthInChars * approximate_digit_width());
+ else
+ aSz.setWidth(m_pImpl->m_pImplLB->GetMaxEntryWidth());
+ }
+
+ if (m_pImpl->m_nMaxWidthChars != -1)
+ {
+ tools::Long nMaxWidth = m_pImpl->m_nMaxWidthChars * approximate_char_width();
+ aSz.setWidth( std::min(aSz.Width(), nMaxWidth) );
+ }
+
+ if (IsDropDownBox())
+ aSz.AdjustWidth(getMaxWidthScrollBarAndDownButton() );
+
+ ComboBoxBounds aBounds(m_pImpl->calcComboBoxDropDownComponentBounds(
+ Size(0xFFFF, 0xFFFF), Size(0xFFFF, 0xFFFF)));
+ aSz.AdjustWidth(aBounds.aSubEditPos.X()*2 );
+
+ aSz.AdjustWidth(ImplGetExtraXOffset() * 2 );
+
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+Size ComboBox::CalcAdjustedSize( const Size& rPrefSize ) const
+{
+ Size aSz = rPrefSize;
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ static_cast<vcl::Window*>(const_cast<ComboBox *>(this))->GetBorder( nLeft, nTop, nRight, nBottom );
+ aSz.AdjustHeight( -(nTop+nBottom) );
+ if ( !IsDropDownBox() )
+ {
+ tools::Long nEntryHeight = CalcBlockSize( 1, 1 ).Height();
+ tools::Long nLines = aSz.Height() / nEntryHeight;
+ if ( nLines < 1 )
+ nLines = 1;
+ aSz.setHeight( nLines * nEntryHeight );
+ aSz.AdjustHeight(m_pImpl->m_nDDHeight );
+ }
+ else
+ {
+ aSz.setHeight( m_pImpl->m_nDDHeight );
+ }
+ aSz.AdjustHeight(nTop+nBottom );
+
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+Size ComboBox::CalcBlockSize( sal_uInt16 nColumns, sal_uInt16 nLines ) const
+{
+ // show ScrollBars where appropriate
+ Size aMinSz = CalcMinimumSize();
+ Size aSz;
+
+ // height
+ if ( nLines )
+ {
+ if ( !IsDropDownBox() )
+ aSz.setHeight( m_pImpl->m_pImplLB->CalcSize( nLines ).Height() + m_pImpl->m_nDDHeight );
+ else
+ aSz.setHeight( m_pImpl->m_nDDHeight );
+ }
+ else
+ aSz.setHeight( aMinSz.Height() );
+
+ // width
+ if ( nColumns )
+ aSz.setWidth( nColumns * approximate_char_width() );
+ else
+ aSz.setWidth( aMinSz.Width() );
+
+ if ( IsDropDownBox() )
+ aSz.AdjustWidth(getMaxWidthScrollBarAndDownButton() );
+
+ if ( !IsDropDownBox() )
+ {
+ if ( aSz.Width() < aMinSz.Width() )
+ aSz.AdjustHeight(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ if ( aSz.Height() < aMinSz.Height() )
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ }
+
+ aSz.AdjustWidth(ImplGetExtraXOffset() * 2 );
+
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+tools::Long ComboBox::GetDropDownEntryHeight() const
+{
+ return m_pImpl->m_pImplLB->GetEntryHeight();
+}
+
+void ComboBox::GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const
+{
+ tools::Long nCharWidth = GetTextWidth(OUString(u'x'));
+ if ( !IsDropDownBox() )
+ {
+ Size aOutSz = GetMainWindow()->GetOutputSizePixel();
+ rnCols = (nCharWidth > 0) ? static_cast<sal_uInt16>(aOutSz.Width()/nCharWidth) : 1;
+ rnLines = static_cast<sal_uInt16>(aOutSz.Height()/GetDropDownEntryHeight());
+ }
+ else
+ {
+ Size aOutSz = m_pImpl->m_pSubEdit->GetOutputSizePixel();
+ rnCols = (nCharWidth > 0) ? static_cast<sal_uInt16>(aOutSz.Width()/nCharWidth) : 1;
+ rnLines = 1;
+ }
+}
+
+void ComboBox::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags )
+{
+ GetMainWindow()->ApplySettings(*pDev);
+
+ Size aSize = GetSizePixel();
+ vcl::Font aFont = GetMainWindow()->GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ pDev->SetTextFillColor();
+
+ // Border/Background
+ pDev->SetLineColor();
+ pDev->SetFillColor();
+ bool bBorder = (GetStyle() & WB_BORDER);
+ bool bBackground = IsControlBackground();
+ if ( bBorder || bBackground )
+ {
+ tools::Rectangle aRect( rPos, aSize );
+ // aRect.Top() += nEditHeight;
+ if ( bBorder )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ if ( bBackground )
+ {
+ pDev->SetFillColor( GetControlBackground() );
+ pDev->DrawRect( aRect );
+ }
+ }
+
+ // contents
+ if ( !IsDropDownBox() )
+ {
+ tools::Long nOnePixel = GetDrawPixel( pDev, 1 );
+ tools::Long nTextHeight = pDev->GetTextHeight();
+ tools::Long nEditHeight = nTextHeight + 6*nOnePixel;
+ DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
+
+ // First, draw the edit part
+ Size aOrigSize(m_pImpl->m_pSubEdit->GetSizePixel());
+ m_pImpl->m_pSubEdit->SetSizePixel(Size(aSize.Width(), nEditHeight));
+ m_pImpl->m_pSubEdit->Draw( pDev, rPos, nFlags );
+ m_pImpl->m_pSubEdit->SetSizePixel(aOrigSize);
+
+ // Second, draw the listbox
+ if ( GetStyle() & WB_CENTER )
+ nTextStyle |= DrawTextFlags::Center;
+ else if ( GetStyle() & WB_RIGHT )
+ nTextStyle |= DrawTextFlags::Right;
+ else
+ nTextStyle |= DrawTextFlags::Left;
+
+ if ( nFlags & SystemTextColorFlags::Mono )
+ {
+ pDev->SetTextColor( COL_BLACK );
+ }
+ else
+ {
+ if ( !IsEnabled() )
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ pDev->SetTextColor( rStyleSettings.GetDisableColor() );
+ }
+ else
+ {
+ pDev->SetTextColor( GetTextColor() );
+ }
+ }
+
+ tools::Rectangle aClip( rPos, aSize );
+ pDev->IntersectClipRegion( aClip );
+ sal_Int32 nLines = static_cast<sal_Int32>( nTextHeight > 0 ? (aSize.Height()-nEditHeight)/nTextHeight : 1 );
+ if ( !nLines )
+ nLines = 1;
+ const sal_Int32 nTEntry = IsReallyVisible() ? m_pImpl->m_pImplLB->GetTopEntry() : 0;
+
+ tools::Rectangle aTextRect( rPos, aSize );
+
+ aTextRect.AdjustLeft(3*nOnePixel );
+ aTextRect.AdjustRight( -(3*nOnePixel) );
+ aTextRect.AdjustTop(nEditHeight + nOnePixel );
+ aTextRect.SetBottom( aTextRect.Top() + nTextHeight );
+
+ // the drawing starts here
+ for ( sal_Int32 n = 0; n < nLines; ++n )
+ {
+ pDev->DrawText( aTextRect, m_pImpl->m_pImplLB->GetEntryList().GetEntryText( n+nTEntry ), nTextStyle );
+ aTextRect.AdjustTop(nTextHeight );
+ aTextRect.AdjustBottom(nTextHeight );
+ }
+ }
+
+ pDev->Pop();
+
+ // Call Edit::Draw after restoring the MapMode...
+ if ( IsDropDownBox() )
+ {
+ Size aOrigSize(m_pImpl->m_pSubEdit->GetSizePixel());
+ m_pImpl->m_pSubEdit->SetSizePixel(GetSizePixel());
+ m_pImpl->m_pSubEdit->Draw( pDev, rPos, nFlags );
+ m_pImpl->m_pSubEdit->SetSizePixel(aOrigSize);
+ // DD-Button ?
+ }
+}
+
+void ComboBox::SetUserDrawHdl(const Link<UserDrawEvent*, void>& rLink)
+{
+ m_pImpl->m_pImplLB->SetUserDrawHdl(rLink);
+}
+
+void ComboBox::SetUserItemSize( const Size& rSz )
+{
+ GetMainWindow()->SetUserItemSize( rSz );
+}
+
+void ComboBox::EnableUserDraw( bool bUserDraw )
+{
+ GetMainWindow()->EnableUserDraw( bUserDraw );
+}
+
+bool ComboBox::IsUserDrawEnabled() const
+{
+ return GetMainWindow()->IsUserDrawEnabled();
+}
+
+void ComboBox::DrawEntry(const UserDrawEvent& rEvt)
+{
+ GetMainWindow()->DrawEntry(*rEvt.GetRenderContext(), rEvt.GetItemId(), /*bDrawImage*/false, /*bDrawText*/false);
+}
+
+void ComboBox::AddSeparator( sal_Int32 n )
+{
+ m_pImpl->m_pImplLB->AddSeparator( n );
+}
+
+void ComboBox::SetMRUEntries( std::u16string_view rEntries )
+{
+ m_pImpl->m_pImplLB->SetMRUEntries( rEntries, ';' );
+}
+
+OUString ComboBox::GetMRUEntries() const
+{
+ return m_pImpl->m_pImplLB ? m_pImpl->m_pImplLB->GetMRUEntries( ';' ) : OUString();
+}
+
+void ComboBox::SetMaxMRUCount( sal_Int32 n )
+{
+ m_pImpl->m_pImplLB->SetMaxMRUCount( n );
+}
+
+sal_Int32 ComboBox::GetMaxMRUCount() const
+{
+ return m_pImpl->m_pImplLB ? m_pImpl->m_pImplLB->GetMaxMRUCount() : 0;
+}
+
+sal_uInt16 ComboBox::GetDisplayLineCount() const
+{
+ return m_pImpl->m_pImplLB ? m_pImpl->m_pImplLB->GetDisplayLineCount() : 0;
+}
+
+void ComboBox::SetEntryData( sal_Int32 nPos, void* pNewData )
+{
+ m_pImpl->m_pImplLB->SetEntryData( nPos + m_pImpl->m_pImplLB->GetEntryList().GetMRUCount(), pNewData );
+}
+
+void* ComboBox::GetEntryData( sal_Int32 nPos ) const
+{
+ return m_pImpl->m_pImplLB->GetEntryList().GetEntryData(
+ nPos + m_pImpl->m_pImplLB->GetEntryList().GetMRUCount() );
+}
+
+sal_Int32 ComboBox::GetTopEntry() const
+{
+ sal_Int32 nPos = GetEntryCount() ? m_pImpl->m_pImplLB->GetTopEntry() : LISTBOX_ENTRY_NOTFOUND;
+ if (nPos < m_pImpl->m_pImplLB->GetEntryList().GetMRUCount())
+ nPos = 0;
+ return nPos;
+}
+
+tools::Rectangle ComboBox::GetDropDownPosSizePixel() const
+{
+ return m_pImpl->m_pFloatWin
+ ? m_pImpl->m_pFloatWin->GetWindowExtentsRelative(*this)
+ : tools::Rectangle();
+}
+
+const Wallpaper& ComboBox::GetDisplayBackground() const
+{
+ if (!m_pImpl->m_pSubEdit->IsBackground())
+ return Control::GetDisplayBackground();
+
+ const Wallpaper& rBack = m_pImpl->m_pSubEdit->GetBackground();
+ if( ! rBack.IsBitmap() &&
+ ! rBack.IsGradient() &&
+ rBack == Wallpaper(COL_TRANSPARENT)
+ )
+ return Control::GetDisplayBackground();
+ return rBack;
+}
+
+sal_Int32 ComboBox::GetSelectedEntryCount() const
+{
+ return m_pImpl->m_pImplLB->GetEntryList().GetSelectedEntryCount();
+}
+
+sal_Int32 ComboBox::GetSelectedEntryPos( sal_Int32 nIndex ) const
+{
+ sal_Int32 nPos = m_pImpl->m_pImplLB->GetEntryList().GetSelectedEntryPos( nIndex );
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ if (nPos < m_pImpl->m_pImplLB->GetEntryList().GetMRUCount())
+ nPos = m_pImpl->m_pImplLB->GetEntryList().FindEntry(m_pImpl->m_pImplLB->GetEntryList().GetEntryText(nPos));
+ nPos = sal::static_int_cast<sal_Int32>(nPos - m_pImpl->m_pImplLB->GetEntryList().GetMRUCount());
+ }
+ return nPos;
+}
+
+bool ComboBox::IsEntryPosSelected( sal_Int32 nPos ) const
+{
+ return m_pImpl->m_pImplLB->GetEntryList().IsEntryPosSelected(
+ nPos + m_pImpl->m_pImplLB->GetEntryList().GetMRUCount() );
+}
+
+void ComboBox::SelectEntryPos( sal_Int32 nPos, bool bSelect)
+{
+ if (nPos < m_pImpl->m_pImplLB->GetEntryList().GetEntryCount())
+ m_pImpl->m_pImplLB->SelectEntry(
+ nPos + m_pImpl->m_pImplLB->GetEntryList().GetMRUCount(), bSelect);
+}
+
+void ComboBox::SetNoSelection()
+{
+ m_pImpl->m_pImplLB->SetNoSelection();
+ m_pImpl->m_pSubEdit->SetText( OUString() );
+}
+
+tools::Rectangle ComboBox::GetBoundingRectangle( sal_Int32 nItem ) const
+{
+ tools::Rectangle aRect = GetMainWindow()->GetBoundingRectangle( nItem );
+ tools::Rectangle aOffset = GetMainWindow()->GetWindowExtentsRelative( *static_cast<vcl::Window*>(const_cast<ComboBox *>(this)) );
+ aRect.Move( aOffset.Left(), aOffset.Top() );
+ return aRect;
+}
+
+void ComboBox::SetBorderStyle( WindowBorderStyle nBorderStyle )
+{
+ Window::SetBorderStyle( nBorderStyle );
+ if ( !IsDropDownBox() )
+ {
+ m_pImpl->m_pSubEdit->SetBorderStyle( nBorderStyle );
+ m_pImpl->m_pImplLB->SetBorderStyle( nBorderStyle );
+ }
+}
+
+void ComboBox::SetHighlightColor( const Color& rColor )
+{
+ AllSettings aSettings(GetSettings());
+ StyleSettings aStyle(aSettings.GetStyleSettings());
+ aStyle.SetHighlightColor(rColor);
+ aSettings.SetStyleSettings(aStyle);
+ SetSettings(aSettings);
+
+ AllSettings aSettingsSubEdit(m_pImpl->m_pSubEdit->GetSettings());
+ StyleSettings aStyleSubEdit(aSettingsSubEdit.GetStyleSettings());
+ aStyleSubEdit.SetHighlightColor(rColor);
+ aSettingsSubEdit.SetStyleSettings(aStyleSubEdit);
+ m_pImpl->m_pSubEdit->SetSettings(aSettings);
+
+ m_pImpl->m_pImplLB->SetHighlightColor(rColor);
+}
+
+void ComboBox::SetHighlightTextColor( const Color& rColor )
+{
+ AllSettings aSettings(GetSettings());
+ StyleSettings aStyle(aSettings.GetStyleSettings());
+ aStyle.SetHighlightTextColor(rColor);
+ aSettings.SetStyleSettings(aStyle);
+ SetSettings(aSettings);
+
+ AllSettings aSettingsSubEdit(m_pImpl->m_pSubEdit->GetSettings());
+ StyleSettings aStyleSubEdit(aSettingsSubEdit.GetStyleSettings());
+ aStyleSubEdit.SetHighlightTextColor(rColor);
+ aSettingsSubEdit.SetStyleSettings(aStyleSubEdit);
+ m_pImpl->m_pSubEdit->SetSettings(aSettings);
+
+ m_pImpl->m_pImplLB->SetHighlightTextColor(rColor);
+}
+
+ImplListBoxWindow* ComboBox::GetMainWindow() const
+{
+ return m_pImpl->m_pImplLB->GetMainWindow();
+}
+
+tools::Long ComboBox::GetIndexForPoint( const Point& rPoint, sal_Int32& rPos ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+
+ // check whether rPoint fits at all
+ tools::Long nIndex = Control::GetIndexForPoint( rPoint );
+ if( nIndex != -1 )
+ {
+ // point must be either in main list window
+ // or in impl window (dropdown case)
+ ImplListBoxWindow* rMain = GetMainWindow();
+
+ // convert coordinates to ImplListBoxWindow pixel coordinate space
+ Point aConvPoint = LogicToPixel( rPoint );
+ AbsoluteScreenPixelPoint aConvPointAbs = OutputToAbsoluteScreenPixel( aConvPoint );
+ aConvPoint = rMain->AbsoluteScreenToOutputPixel( aConvPointAbs );
+ aConvPoint = rMain->PixelToLogic( aConvPoint );
+
+ // try to find entry
+ sal_Int32 nEntry = rMain->GetEntryPosForPoint( aConvPoint );
+ if( nEntry == LISTBOX_ENTRY_NOTFOUND )
+ nIndex = -1;
+ else
+ rPos = nEntry;
+ }
+
+ // get line relative index
+ if( nIndex != -1 )
+ nIndex = ToRelativeLineIndex( nIndex );
+
+ return nIndex;
+}
+
+ComboBoxBounds ComboBox::Impl::calcComboBoxDropDownComponentBounds(
+ const Size &rOutSz, const Size &rBorderOutSz) const
+{
+ ComboBoxBounds aBounds;
+
+ tools::Long nTop = 0;
+ tools::Long nBottom = rOutSz.Height();
+
+ vcl::Window *pBorder = m_rThis.GetWindow( GetWindowType::Border );
+ ImplControlValue aControlValue;
+ Point aPoint;
+ tools::Rectangle aContent, aBound;
+
+ // use the full extent of the control
+ tools::Rectangle aArea( aPoint, rBorderOutSz );
+
+ if (m_rThis.GetNativeControlRegion(ControlType::Combobox, ControlPart::ButtonDown,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ // convert back from border space to local coordinates
+ aPoint = pBorder->ScreenToOutputPixel(m_rThis.OutputToScreenPixel(aPoint));
+ aContent.Move(-aPoint.X(), -aPoint.Y());
+
+ aBounds.aButtonPos = Point(aContent.Left(), nTop);
+ aBounds.aButtonSize = Size(aContent.getOpenWidth(), (nBottom-nTop));
+
+ // adjust the size of the edit field
+ if (m_rThis.GetNativeControlRegion(ControlType::Combobox, ControlPart::SubEdit,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ // convert back from border space to local coordinates
+ aContent.Move(-aPoint.X(), -aPoint.Y());
+
+ // use the themes drop down size
+ aBounds.aSubEditPos = aContent.TopLeft();
+ aBounds.aSubEditSize = aContent.GetSize();
+ }
+ else
+ {
+ // use the themes drop down size for the button
+ aBounds.aSubEditSize = Size(rOutSz.Width() - aContent.getOpenWidth(), rOutSz.Height());
+ }
+ }
+ else
+ {
+ tools::Long nSBWidth = m_rThis.GetSettings().GetStyleSettings().GetScrollBarSize();
+ nSBWidth = m_rThis.CalcZoom( nSBWidth );
+ aBounds.aSubEditSize = Size(rOutSz.Width() - nSBWidth, rOutSz.Height());
+ aBounds.aButtonPos = Point(rOutSz.Width() - nSBWidth, nTop);
+ aBounds.aButtonSize = Size(nSBWidth, (nBottom-nTop));
+ }
+ return aBounds;
+}
+
+void ComboBox::SetWidthInChars(sal_Int32 nWidthInChars)
+{
+ if (nWidthInChars != m_pImpl->m_nWidthInChars)
+ {
+ m_pImpl->m_nWidthInChars = nWidthInChars;
+ queue_resize();
+ }
+}
+
+void ComboBox::setMaxWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != m_pImpl->m_nMaxWidthChars)
+ {
+ m_pImpl->m_nMaxWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+bool ComboBox::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "width-chars")
+ SetWidthInChars(rValue.toInt32());
+ else if (rKey == "max-width-chars")
+ setMaxWidthChars(rValue.toInt32());
+ else if (rKey == "can-focus")
+ {
+ // as far as I can see in Gtk, setting a ComboBox as can.focus means
+ // the focus gets stuck in it, so try here to behave like gtk does
+ // with the settings that work, i.e. can.focus of false doesn't
+ // set the hard WB_NOTABSTOP
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_TABSTOP|WB_NOTABSTOP);
+ if (toBool(rValue))
+ nBits |= WB_TABSTOP;
+ SetStyle(nBits);
+ }
+ else if (rKey == "placeholder-text")
+ SetPlaceholderText(rValue);
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+FactoryFunction ComboBox::GetUITestFactory() const
+{
+ return ComboBoxUIObject::create;
+}
+
+void ComboBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+
+ {
+ auto entriesNode = rJsonWriter.startArray("entries");
+ for (int i = 0; i < GetEntryCount(); ++i)
+ {
+ rJsonWriter.putSimpleValue(GetEntry(i));
+ }
+ }
+
+ {
+ auto selectedNode = rJsonWriter.startArray("selectedEntries");
+ for (int i = 0; i < GetSelectedEntryCount(); ++i)
+ {
+ rJsonWriter.putSimpleValue(OUString::number(GetSelectedEntryPos(i)));
+ }
+ }
+
+ rJsonWriter.put("selectedCount", GetSelectedEntryCount());
+
+ if (IsUserDrawEnabled())
+ rJsonWriter.put("customEntryRenderer", true);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/ctrl.cxx b/vcl/source/control/ctrl.cxx
new file mode 100644
index 0000000000..4f7a42badf
--- /dev/null
+++ b/vcl/source/control/ctrl.cxx
@@ -0,0 +1,513 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <comphelper/lok.hxx>
+#include <o3tl/safeint.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/event.hxx>
+#include <vcl/ctrl.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/DocWindow.hxx>
+#include <sal/log.hxx>
+
+#include <textlayout.hxx>
+#include <svdata.hxx>
+
+using namespace vcl;
+
+void Control::ImplInitControlData()
+{
+ mbHasControlFocus = false;
+ mbShowAccelerator = false;
+}
+
+Control::Control( WindowType nType ) :
+ Window( nType )
+{
+ ImplInitControlData();
+}
+
+Control::Control( vcl::Window* pParent, WinBits nStyle ) :
+ Window( WindowType::CONTROL )
+{
+ ImplInitControlData();
+ ImplInit( pParent, nStyle, nullptr );
+}
+
+Control::~Control()
+{
+ disposeOnce();
+}
+
+void Control::dispose()
+{
+ mxLayoutData.reset();
+ mpReferenceDevice.clear();
+ Window::dispose();
+}
+
+void Control::EnableRTL( bool bEnable )
+{
+ // convenience: for controls also switch layout mode
+ GetOutDev()->SetLayoutMode( bEnable ? vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft :
+ vcl::text::ComplexTextLayoutFlags::TextOriginLeft );
+ CompatStateChanged( StateChangedType::Mirroring );
+ Window::EnableRTL(bEnable);
+}
+
+void Control::Resize()
+{
+ ImplClearLayoutData();
+ Window::Resize();
+}
+
+void Control::FillLayoutData() const
+{
+}
+
+void Control::CreateLayoutData() const
+{
+ SAL_WARN_IF( mxLayoutData, "vcl", "Control::CreateLayoutData: should be called with non-existent layout data only!" );
+ mxLayoutData.emplace();
+}
+
+bool Control::HasLayoutData() const
+{
+ return bool(mxLayoutData);
+}
+
+void Control::SetText( const OUString& rStr )
+{
+ ImplClearLayoutData();
+ Window::SetText( rStr );
+}
+
+ControlLayoutData::ControlLayoutData() : m_pParent( nullptr )
+{
+}
+
+tools::Rectangle ControlLayoutData::GetCharacterBounds( tools::Long nIndex ) const
+{
+ return (nIndex >= 0 && o3tl::make_unsigned(nIndex) < m_aUnicodeBoundRects.size()) ? m_aUnicodeBoundRects[ nIndex ] : tools::Rectangle();
+}
+
+tools::Rectangle Control::GetCharacterBounds( tools::Long nIndex ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+ return mxLayoutData ? mxLayoutData->GetCharacterBounds( nIndex ) : tools::Rectangle();
+}
+
+tools::Long ControlLayoutData::GetIndexForPoint( const Point& rPoint ) const
+{
+ tools::Long nIndex = -1;
+ for( tools::Long i = m_aUnicodeBoundRects.size()-1; i >= 0; i-- )
+ {
+ Point aTopLeft = m_aUnicodeBoundRects[i].TopLeft();
+ Point aBottomRight = m_aUnicodeBoundRects[i].BottomRight();
+ if (rPoint.X() >= aTopLeft.X() && rPoint.Y() >= aTopLeft.Y() &&
+ rPoint.X() <= aBottomRight.X() && rPoint.Y() <= aBottomRight.Y())
+ {
+ nIndex = i;
+ break;
+ }
+ }
+ return nIndex;
+}
+
+tools::Long Control::GetIndexForPoint( const Point& rPoint ) const
+{
+ if( ! HasLayoutData() )
+ FillLayoutData();
+ return mxLayoutData ? mxLayoutData->GetIndexForPoint( rPoint ) : -1;
+}
+
+Pair ControlLayoutData::GetLineStartEnd( tools::Long nLine ) const
+{
+ Pair aPair( -1, -1 );
+
+ int nDisplayLines = m_aLineIndices.size();
+ if( nLine >= 0 && nLine < nDisplayLines )
+ {
+ aPair.A() = m_aLineIndices[nLine];
+ if( nLine+1 < nDisplayLines )
+ aPair.B() = m_aLineIndices[nLine+1]-1;
+ else
+ aPair.B() = m_aDisplayText.getLength()-1;
+ }
+ else if( nLine == 0 && nDisplayLines == 0 && !m_aDisplayText.isEmpty() )
+ {
+ // special case for single line controls so the implementations
+ // in that case do not have to fill in the line indices
+ aPair.A() = 0;
+ aPair.B() = m_aDisplayText.getLength()-1;
+ }
+ return aPair;
+}
+
+Pair Control::GetLineStartEnd( tools::Long nLine ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+ return mxLayoutData ? mxLayoutData->GetLineStartEnd( nLine ) : Pair( -1, -1 );
+}
+
+tools::Long ControlLayoutData::ToRelativeLineIndex( tools::Long nIndex ) const
+{
+ // is the index sensible at all ?
+ if( nIndex >= 0 && nIndex < m_aDisplayText.getLength() )
+ {
+ int nDisplayLines = m_aLineIndices.size();
+ // if only 1 line exists, then absolute and relative index are
+ // identical -> do nothing
+ if( nDisplayLines > 1 )
+ {
+ int nLine;
+ for( nLine = nDisplayLines-1; nLine >= 0; nLine-- )
+ {
+ if( m_aLineIndices[nLine] <= nIndex )
+ {
+ nIndex -= m_aLineIndices[nLine];
+ break;
+ }
+ }
+ if( nLine < 0 )
+ {
+ SAL_WARN_IF( nLine < 0, "vcl", "ToRelativeLineIndex failed" );
+ nIndex = -1;
+ }
+ }
+ }
+ else
+ nIndex = -1;
+
+ return nIndex;
+}
+
+tools::Long Control::ToRelativeLineIndex( tools::Long nIndex ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+ return mxLayoutData ? mxLayoutData->ToRelativeLineIndex( nIndex ) : -1;
+}
+
+OUString Control::GetDisplayText() const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+ return mxLayoutData ? mxLayoutData->m_aDisplayText : GetText();
+}
+
+bool Control::FocusWindowBelongsToControl(const vcl::Window* pFocusWin) const
+{
+ return ImplIsWindowOrChild(pFocusWin);
+}
+
+bool Control::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
+ {
+ if ( !mbHasControlFocus )
+ {
+ mbHasControlFocus = true;
+ CompatStateChanged( StateChangedType::ControlFocus );
+ if ( ImplCallEventListenersAndHandler( VclEventId::ControlGetFocus, {} ) )
+ // been destroyed within the handler
+ return true;
+ }
+ }
+ else
+ {
+ if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ vcl::Window* pFocusWin = Application::GetFocusWindow();
+ if ( !pFocusWin || !FocusWindowBelongsToControl(pFocusWin) )
+ {
+ mbHasControlFocus = false;
+ CompatStateChanged( StateChangedType::ControlFocus );
+ if ( ImplCallEventListenersAndHandler( VclEventId::ControlLoseFocus, [this] () { maLoseFocusHdl.Call(*this); } ) )
+ // been destroyed within the handler
+ return true;
+ }
+ }
+ }
+ return Window::EventNotify( rNEvt );
+}
+
+void Control::StateChanged( StateChangedType nStateChange )
+{
+ if( nStateChange == StateChangedType::InitShow ||
+ nStateChange == StateChangedType::Visible ||
+ nStateChange == StateChangedType::Zoom ||
+ nStateChange == StateChangedType::ControlFont
+ )
+ {
+ ImplClearLayoutData();
+ }
+ Window::StateChanged( nStateChange );
+}
+
+void Control::AppendLayoutData( const Control& rSubControl ) const
+{
+ if( !rSubControl.HasLayoutData() )
+ rSubControl.FillLayoutData();
+ if( !rSubControl.HasLayoutData() || rSubControl.mxLayoutData->m_aDisplayText.isEmpty() )
+ return;
+
+ tools::Long nCurrentIndex = mxLayoutData->m_aDisplayText.getLength();
+ mxLayoutData->m_aDisplayText += rSubControl.mxLayoutData->m_aDisplayText;
+ int nLines = rSubControl.mxLayoutData->m_aLineIndices.size();
+ int n;
+ mxLayoutData->m_aLineIndices.push_back( nCurrentIndex );
+ for( n = 1; n < nLines; n++ )
+ mxLayoutData->m_aLineIndices.push_back( rSubControl.mxLayoutData->m_aLineIndices[n] + nCurrentIndex );
+ int nRectangles = rSubControl.mxLayoutData->m_aUnicodeBoundRects.size();
+ tools::Rectangle aRel = rSubControl.GetWindowExtentsRelative(*this);
+ for( n = 0; n < nRectangles; n++ )
+ {
+ tools::Rectangle aRect = rSubControl.mxLayoutData->m_aUnicodeBoundRects[n];
+ aRect.Move( aRel.Left(), aRel.Top() );
+ mxLayoutData->m_aUnicodeBoundRects.push_back( aRect );
+ }
+}
+
+void Control::CallEventListeners( VclEventId nEvent, void* pData)
+{
+ VclPtr<Control> xThis(this);
+ UITestLogger::getInstance().logAction(xThis, nEvent);
+
+ vcl::Window::CallEventListeners(nEvent, pData);
+}
+
+bool Control::ImplCallEventListenersAndHandler( VclEventId nEvent, std::function<void()> const & callHandler )
+{
+ VclPtr<Control> xThis(this);
+
+ Control::CallEventListeners( nEvent );
+
+ if ( !xThis->isDisposed() )
+ {
+ if (callHandler)
+ {
+ callHandler();
+ }
+
+ if ( !xThis->isDisposed() )
+ return false;
+ }
+ return true;
+}
+
+void Control::SetLayoutDataParent( const Control* pParent ) const
+{
+ if( HasLayoutData() )
+ mxLayoutData->m_pParent = pParent;
+}
+
+void Control::ImplClearLayoutData() const
+{
+ mxLayoutData.reset();
+}
+
+void Control::ImplDrawFrame( OutputDevice* pDev, tools::Rectangle& rRect )
+{
+ // use a deco view to draw the frame
+ // However, since there happens a lot of magic there, we need to fake some (style) settings
+ // on the device
+ AllSettings aOriginalSettings( pDev->GetSettings() );
+
+ AllSettings aNewSettings( aOriginalSettings );
+ StyleSettings aStyle( aNewSettings.GetStyleSettings() );
+
+ // The *only known* clients of the Draw methods of the various VCL-controls are form controls:
+ // During print preview, and during printing, Draw is called. Thus, drawing always happens with a
+ // mono (colored) border
+ aStyle.SetOptions( aStyle.GetOptions() | StyleSettingsOptions::Mono );
+ aStyle.SetMonoColor( GetSettings().GetStyleSettings().GetMonoColor() );
+
+ aNewSettings.SetStyleSettings( aStyle );
+ // #i67023# do not call data changed listeners for this fake
+ // since they may understandably invalidate on settings changed
+ pDev->OutputDevice::SetSettings( aNewSettings );
+
+ DecorationView aDecoView( pDev );
+ rRect = aDecoView.DrawFrame( rRect, DrawFrameStyle::Out, DrawFrameFlags::WindowBorder );
+
+ pDev->OutputDevice::SetSettings( aOriginalSettings );
+}
+
+void Control::SetShowAccelerator(bool bVal)
+{
+ mbShowAccelerator = bVal;
+};
+
+ControlLayoutData::~ControlLayoutData()
+{
+ if( m_pParent )
+ m_pParent->ImplClearLayoutData();
+}
+
+Size Control::GetOptimalSize() const
+{
+ return Size( GetTextWidth( GetText() ) + 2 * 12,
+ GetTextHeight() + 2 * 6 );
+}
+
+void Control::SetReferenceDevice( OutputDevice* _referenceDevice )
+{
+ if ( mpReferenceDevice == _referenceDevice )
+ return;
+
+ mpReferenceDevice = _referenceDevice;
+ Invalidate();
+}
+
+OutputDevice* Control::GetReferenceDevice() const
+{
+ // tdf#118377 It can happen that mpReferenceDevice is already disposed and
+ // stays disposed (see task, even when Dialog is closed). I have no idea if
+ // this may be very bad - someone who knows more about lifetime of OutputDevice's
+ // will have to decide.
+ // To secure this, I changed all accesses to mpControlData->mpReferenceDevice to
+ // use Control::GetReferenceDevice() - only use mpControlData->mpReferenceDevice
+ // inside Control::SetReferenceDevice and Control::GetReferenceDevice().
+ // Control::GetReferenceDevice() will now reset mpReferenceDevice if it is already
+ // disposed. This way all usages will do a kind of 'test-and-get' call.
+ if(nullptr != mpReferenceDevice && mpReferenceDevice->isDisposed())
+ {
+ const_cast<Control*>(this)->SetReferenceDevice(nullptr);
+ }
+
+ return mpReferenceDevice;
+}
+
+const vcl::Font& Control::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetLabelFont();
+}
+
+const Color& Control::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetLabelTextColor();
+}
+
+void Control::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ ApplyControlFont(rRenderContext, GetCanonicalFont(rStyleSettings));
+
+ ApplyControlForeground(rRenderContext, GetCanonicalTextColor(rStyleSettings));
+ rRenderContext.SetTextFillColor();
+}
+
+void Control::ImplInitSettings()
+{
+ ApplySettings(*GetOutDev());
+}
+
+tools::Rectangle Control::DrawControlText( OutputDevice& _rTargetDevice, const tools::Rectangle& rRect, const OUString& _rStr,
+ DrawTextFlags _nStyle, std::vector< tools::Rectangle >* _pVector, OUString* _pDisplayText, const Size* i_pDeviceSize ) const
+{
+ OUString rPStr = _rStr;
+ DrawTextFlags nPStyle = _nStyle;
+
+ bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
+
+ if (autoacc && !mbShowAccelerator)
+ rPStr = removeMnemonicFromString( _rStr );
+
+ if( !GetReferenceDevice() || ( GetReferenceDevice() == &_rTargetDevice ) )
+ {
+ const tools::Rectangle aRet = _rTargetDevice.GetTextRect(rRect, rPStr, nPStyle);
+ _rTargetDevice.DrawText(aRet, rPStr, nPStyle, _pVector, _pDisplayText);
+ return aRet;
+ }
+
+ ControlTextRenderer aRenderer( *this, _rTargetDevice, *GetReferenceDevice() );
+ return aRenderer.DrawText(rRect, rPStr, nPStyle, _pVector, _pDisplayText, i_pDeviceSize);
+}
+
+tools::Rectangle Control::GetControlTextRect( OutputDevice& _rTargetDevice, const tools::Rectangle & rRect,
+ const OUString& _rStr, DrawTextFlags _nStyle, Size* o_pDeviceSize ) const
+{
+ OUString rPStr = _rStr;
+ DrawTextFlags nPStyle = _nStyle;
+
+ bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
+
+ if (autoacc && !mbShowAccelerator)
+ rPStr = removeMnemonicFromString( _rStr );
+
+ if ( !GetReferenceDevice() || ( GetReferenceDevice() == &_rTargetDevice ) )
+ {
+ tools::Rectangle aRet = _rTargetDevice.GetTextRect( rRect, rPStr, nPStyle );
+ if (o_pDeviceSize)
+ {
+ *o_pDeviceSize = aRet.GetSize();
+ }
+ return aRet;
+ }
+
+ ControlTextRenderer aRenderer( *this, _rTargetDevice, *GetReferenceDevice() );
+ return aRenderer.GetTextRect(rRect, rPStr, nPStyle, o_pDeviceSize);
+}
+
+Font
+Control::GetUnzoomedControlPointFont() const
+{
+ Font aFont(GetCanonicalFont(GetSettings().GetStyleSettings()));
+ if (IsControlFont())
+ aFont.Merge(GetControlFont());
+ return aFont;
+}
+
+void Control::LogicInvalidate(const tools::Rectangle* pRectangle)
+{
+ VclPtr<vcl::Window> pParent = GetParentWithLOKNotifier();
+ if (!pParent || !dynamic_cast<vcl::DocWindow*>(GetParent()))
+ {
+ // if control doesn't belong to a DocWindow, the overridden base class
+ // method has to be invoked
+ Window::LogicInvalidate(pRectangle);
+ return;
+ }
+
+ // avoid endless paint/invalidate loop in Impress
+ if (comphelper::LibreOfficeKit::isTiledPainting())
+ return;
+
+ tools::Rectangle aResultRectangle;
+ if (!pRectangle)
+ {
+ // we have to invalidate the whole control area not the whole document
+ aResultRectangle = PixelToLogic(tools::Rectangle(GetPosPixel(), GetSizePixel()), MapMode(MapUnit::MapTwip));
+ }
+ else
+ {
+ aResultRectangle = OutputDevice::LogicToLogic(*pRectangle, GetMapMode(), MapMode(MapUnit::MapTwip));
+ }
+
+ pParent->GetLOKNotifier()->notifyInvalidation(&aResultRectangle);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/edit.cxx b/vcl/source/control/edit.cxx
new file mode 100644
index 0000000000..0e75dc36b9
--- /dev/null
+++ b/vcl/source/control/edit.cxx
@@ -0,0 +1,2937 @@
+/* -*- 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 <utility>
+#include <vcl/builder.hxx>
+#include <vcl/event.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/specialchars.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/transfer.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#include <window.h>
+#include <svdata.hxx>
+#include <strings.hrc>
+
+#include <com/sun/star/i18n/BreakIterator.hpp>
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+
+#include <com/sun/star/i18n/InputSequenceChecker.hpp>
+#include <com/sun/star/i18n/InputSequenceCheckMode.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+
+#include <com/sun/star/uno/Any.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+
+#include <sot/exchange.hxx>
+#include <sot/formats.hxx>
+#include <sal/macros.h>
+#include <sal/log.hxx>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <vcl/unohelp2.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <tools/json_writer.hxx>
+
+#include <algorithm>
+#include <memory>
+#include <string_view>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+
+// - Redo
+// - if Tracking-Cancel recreate DefaultSelection
+
+static FncGetSpecialChars pImplFncGetSpecialChars = nullptr;
+
+#define EDIT_ALIGN_LEFT 1
+#define EDIT_ALIGN_CENTER 2
+#define EDIT_ALIGN_RIGHT 3
+
+#define EDIT_DEL_LEFT 1
+#define EDIT_DEL_RIGHT 2
+
+#define EDIT_DELMODE_SIMPLE 11
+#define EDIT_DELMODE_RESTOFWORD 12
+#define EDIT_DELMODE_RESTOFCONTENT 13
+
+struct DDInfo
+{
+ vcl::Cursor aCursor;
+ Selection aDndStartSel;
+ sal_Int32 nDropPos;
+ bool bStarterOfDD;
+ bool bDroppedInMe;
+ bool bVisCursor;
+ bool bIsStringSupported;
+
+ DDInfo()
+ {
+ aCursor.SetStyle( CURSOR_SHADOW );
+ nDropPos = 0;
+ bStarterOfDD = false;
+ bDroppedInMe = false;
+ bVisCursor = false;
+ bIsStringSupported = false;
+ }
+};
+
+struct Impl_IMEInfos
+{
+ OUString aOldTextAfterStartPos;
+ std::unique_ptr<ExtTextInputAttr[]>
+ pAttribs;
+ sal_Int32 nPos;
+ sal_Int32 nLen;
+ bool bCursor;
+ bool bWasCursorOverwrite;
+
+ Impl_IMEInfos(sal_Int32 nPos, OUString aOldTextAfterStartPos);
+
+ void CopyAttribs(const ExtTextInputAttr* pA, sal_Int32 nL);
+ void DestroyAttribs();
+};
+
+Impl_IMEInfos::Impl_IMEInfos(sal_Int32 nP, OUString _aOldTextAfterStartPos)
+ : aOldTextAfterStartPos(std::move(_aOldTextAfterStartPos)),
+ nPos(nP),
+ nLen(0),
+ bCursor(true),
+ bWasCursorOverwrite(false)
+{
+}
+
+void Impl_IMEInfos::CopyAttribs(const ExtTextInputAttr* pA, sal_Int32 nL)
+{
+ nLen = nL;
+ pAttribs.reset(new ExtTextInputAttr[ nL ]);
+ memcpy( pAttribs.get(), pA, nL*sizeof(ExtTextInputAttr) );
+}
+
+void Impl_IMEInfos::DestroyAttribs()
+{
+ pAttribs.reset();
+ nLen = 0;
+}
+
+Edit::Edit( WindowType nType )
+ : Control( nType )
+{
+ ImplInitEditData();
+}
+
+Edit::Edit( vcl::Window* pParent, WinBits nStyle )
+ : Control( WindowType::EDIT )
+{
+ ImplInitEditData();
+ ImplInit( pParent, nStyle );
+}
+
+void Edit::SetWidthInChars(sal_Int32 nWidthInChars)
+{
+ if (mnWidthInChars != nWidthInChars)
+ {
+ mnWidthInChars = nWidthInChars;
+ queue_resize();
+ }
+}
+
+void Edit::setMaxWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != mnMaxWidthChars)
+ {
+ mnMaxWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+bool Edit::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "width-chars")
+ SetWidthInChars(rValue.toInt32());
+ else if (rKey == "max-width-chars")
+ setMaxWidthChars(rValue.toInt32());
+ else if (rKey == "max-length")
+ {
+ sal_Int32 nTextLen = rValue.toInt32();
+ SetMaxTextLen(nTextLen == 0 ? EDIT_NOLIMIT : nTextLen);
+ }
+ else if (rKey == "editable")
+ {
+ SetReadOnly(!toBool(rValue));
+ }
+ else if (rKey == "overwrite-mode")
+ {
+ SetInsertMode(!toBool(rValue));
+ }
+ else if (rKey == "visibility")
+ {
+ mbPassword = false;
+ if (!toBool(rValue))
+ mbPassword = true;
+ }
+ else if (rKey == "placeholder-text")
+ SetPlaceholderText(rValue);
+ else if (rKey == "shadow-type")
+ {
+ if (GetStyle() & WB_BORDER)
+ SetBorderStyle(rValue == "none" ? WindowBorderStyle::MONO : WindowBorderStyle::NORMAL);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+Edit::~Edit()
+{
+ disposeOnce();
+}
+
+void Edit::dispose()
+{
+ mpUIBuilder.reset();
+ mpDDInfo.reset();
+
+ vcl::Cursor* pCursor = GetCursor();
+ if ( pCursor )
+ {
+ SetCursor( nullptr );
+ delete pCursor;
+ }
+
+ mpIMEInfos.reset();
+
+ if ( mxDnDListener.is() )
+ {
+ if ( GetDragGestureRecognizer().is() )
+ {
+ uno::Reference< datatransfer::dnd::XDragGestureListener> xDGL( mxDnDListener, uno::UNO_QUERY );
+ GetDragGestureRecognizer()->removeDragGestureListener( xDGL );
+ }
+ if ( GetDropTarget().is() )
+ {
+ uno::Reference< datatransfer::dnd::XDropTargetListener> xDTL( mxDnDListener, uno::UNO_QUERY );
+ GetDropTarget()->removeDropTargetListener( xDTL );
+ }
+
+ mxDnDListener->disposing( lang::EventObject() ); // #95154# #96585# Empty Source means it's the Client
+ mxDnDListener.clear();
+ }
+
+ SetType(WindowType::WINDOW);
+
+ mpSubEdit.disposeAndClear();
+ Control::dispose();
+}
+
+void Edit::ImplInitEditData()
+{
+ mpSubEdit = VclPtr<Edit>();
+ mpFilterText = nullptr;
+ mnXOffset = 0;
+ mnAlign = EDIT_ALIGN_LEFT;
+ mnMaxTextLen = EDIT_NOLIMIT;
+ mnWidthInChars = -1;
+ mnMaxWidthChars = -1;
+ mbInternModified = false;
+ mbReadOnly = false;
+ mbInsertMode = true;
+ mbClickedInSelection = false;
+ mbActivePopup = false;
+ mbIsSubEdit = false;
+ mbForceControlBackground = false;
+ mbPassword = false;
+ mpDDInfo = nullptr;
+ mpIMEInfos = nullptr;
+ mcEchoChar = 0;
+
+ // no default mirroring for Edit controls
+ // note: controls that use a subedit will revert this (SpinField, ComboBox)
+ EnableRTL( false );
+
+ mxDnDListener = new vcl::unohelper::DragAndDropWrapper( this );
+}
+
+bool Edit::ImplUseNativeBorder(vcl::RenderContext const & rRenderContext, WinBits nStyle) const
+{
+ bool bRet = rRenderContext.IsNativeControlSupported(ImplGetNativeControlType(),
+ ControlPart::HasBackgroundTexture)
+ && ((nStyle & WB_BORDER) && !(nStyle & WB_NOBORDER));
+ if (!bRet && mbIsSubEdit)
+ {
+ vcl::Window* pWindow = GetParent();
+ nStyle = pWindow->GetStyle();
+ bRet = pWindow->IsNativeControlSupported(ImplGetNativeControlType(),
+ ControlPart::HasBackgroundTexture)
+ && ((nStyle & WB_BORDER) && !(nStyle & WB_NOBORDER));
+ }
+ return bRet;
+}
+
+void Edit::ImplInit(vcl::Window* pParent, WinBits nStyle)
+{
+ nStyle = ImplInitStyle(nStyle);
+
+ if (!(nStyle & (WB_CENTER | WB_RIGHT)))
+ nStyle |= WB_LEFT;
+
+ Control::ImplInit(pParent, nStyle, nullptr);
+
+ mbReadOnly = (nStyle & WB_READONLY) != 0;
+
+ mnAlign = EDIT_ALIGN_LEFT;
+
+ // hack: right align until keyinput and cursor travelling works
+ if( IsRTLEnabled() )
+ mnAlign = EDIT_ALIGN_RIGHT;
+
+ if ( nStyle & WB_RIGHT )
+ mnAlign = EDIT_ALIGN_RIGHT;
+ else if ( nStyle & WB_CENTER )
+ mnAlign = EDIT_ALIGN_CENTER;
+
+ SetCursor( new vcl::Cursor );
+
+ SetPointer( PointerStyle::Text );
+ ApplySettings(*GetOutDev());
+
+ uno::Reference< datatransfer::dnd::XDragGestureListener> xDGL( mxDnDListener, uno::UNO_QUERY );
+ uno::Reference< datatransfer::dnd::XDragGestureRecognizer > xDGR = GetDragGestureRecognizer();
+ if ( xDGR.is() )
+ {
+ xDGR->addDragGestureListener( xDGL );
+ uno::Reference< datatransfer::dnd::XDropTargetListener> xDTL( mxDnDListener, uno::UNO_QUERY );
+ GetDropTarget()->addDropTargetListener( xDTL );
+ GetDropTarget()->setActive( true );
+ GetDropTarget()->setDefaultActions( datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE );
+ }
+}
+
+WinBits Edit::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+
+ return nStyle;
+}
+
+bool Edit::IsCharInput( const KeyEvent& rKeyEvent )
+{
+ // In the future we must use new Unicode functions for this
+ sal_Unicode cCharCode = rKeyEvent.GetCharCode();
+ return ((cCharCode >= 32) && (cCharCode != 127) &&
+ !rKeyEvent.GetKeyCode().IsMod3() &&
+ !rKeyEvent.GetKeyCode().IsMod2() &&
+ !rKeyEvent.GetKeyCode().IsMod1() );
+}
+
+void Edit::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ Control::ApplySettings(rRenderContext);
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ const vcl::Font& aFont = rStyleSettings.GetFieldFont();
+ ApplyControlFont(rRenderContext, aFont);
+
+ ImplClearLayoutData();
+
+ Color aTextColor = rStyleSettings.GetFieldTextColor();
+ ApplyControlForeground(rRenderContext, aTextColor);
+
+ if (IsControlBackground())
+ {
+ rRenderContext.SetBackground(GetControlBackground());
+ rRenderContext.SetFillColor(GetControlBackground());
+
+ if (ImplUseNativeBorder(rRenderContext, GetStyle()))
+ {
+ // indicates that no non-native drawing of background should take place
+ mpWindowImpl->mnNativeBackground = ControlPart::Entire;
+ }
+ }
+ else if (ImplUseNativeBorder(rRenderContext, GetStyle()))
+ {
+ // Transparent background
+ rRenderContext.SetBackground();
+ rRenderContext.SetFillColor();
+ }
+ else
+ {
+ rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
+ rRenderContext.SetFillColor(rStyleSettings.GetFieldColor());
+ }
+}
+
+tools::Long Edit::ImplGetExtraXOffset() const
+{
+ // MT 09/2002: nExtraOffsetX should become a member, instead of checking every time,
+ // but I need an incompatible update for this...
+ // #94095# Use extra offset only when edit has a border
+ tools::Long nExtraOffset = 0;
+ if( ( GetStyle() & WB_BORDER ) || ( mbIsSubEdit && ( GetParent()->GetStyle() & WB_BORDER ) ) )
+ nExtraOffset = 2;
+
+ return nExtraOffset;
+}
+
+tools::Long Edit::ImplGetExtraYOffset() const
+{
+ tools::Long nExtraOffset = 0;
+ ControlType eCtrlType = ImplGetNativeControlType();
+ if (eCtrlType != ControlType::EditboxNoBorder)
+ {
+ // add some space between text entry and border
+ nExtraOffset = 2;
+ }
+ return nExtraOffset;
+}
+
+OUString Edit::ImplGetText() const
+{
+ if ( mcEchoChar || mbPassword )
+ {
+ sal_Unicode cEchoChar;
+ if ( mcEchoChar )
+ cEchoChar = mcEchoChar;
+ else
+ cEchoChar = u'\x2022';
+ OUStringBuffer aText(maText.getLength());
+ comphelper::string::padToLength(aText, maText.getLength(), cEchoChar);
+ return aText.makeStringAndClear();
+ }
+ else
+ return maText.toString();
+}
+
+void Edit::ImplInvalidateOrRepaint()
+{
+ if( IsPaintTransparent() )
+ {
+ Invalidate();
+ // FIXME: this is currently only on macOS
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects )
+ PaintImmediately();
+ }
+ else
+ Invalidate();
+}
+
+tools::Long Edit::ImplGetTextYPosition() const
+{
+ if ( GetStyle() & WB_TOP )
+ return ImplGetExtraXOffset();
+ else if ( GetStyle() & WB_BOTTOM )
+ return GetOutputSizePixel().Height() - GetTextHeight() - ImplGetExtraXOffset();
+ return ( GetOutputSizePixel().Height() - GetTextHeight() ) / 2;
+}
+
+void Edit::ImplRepaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRectangle)
+{
+ if (!IsReallyVisible())
+ return;
+
+ ApplySettings(rRenderContext);
+
+ const OUString aText = ImplGetText();
+ const sal_Int32 nLen = aText.getLength();
+
+ KernArray aDX;
+ if (nLen)
+ GetOutDev()->GetCaretPositions(aText, aDX, 0, nLen);
+
+ tools::Long nTH = GetTextHeight();
+ Point aPos(mnXOffset, ImplGetTextYPosition());
+
+ vcl::Cursor* pCursor = GetCursor();
+ bool bVisCursor = pCursor && pCursor->IsVisible();
+ if (pCursor)
+ pCursor->Hide();
+
+ ImplClearBackground(rRenderContext, rRectangle, 0, GetOutputSizePixel().Width()-1);
+
+ bool bPaintPlaceholderText = aText.isEmpty() && !maPlaceholderText.isEmpty();
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ if (!IsEnabled() || bPaintPlaceholderText)
+ rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
+
+ // Set background color of the normal text
+ if (mbForceControlBackground && IsControlBackground())
+ {
+ // check if we need to set ControlBackground even in NWF case
+ rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR);
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(GetControlBackground());
+ rRenderContext.DrawRect(tools::Rectangle(aPos, Size(GetOutputSizePixel().Width() - 2 * mnXOffset, GetOutputSizePixel().Height())));
+ rRenderContext.Pop();
+
+ rRenderContext.SetTextFillColor(GetControlBackground());
+ }
+ else if (IsPaintTransparent() || ImplUseNativeBorder(rRenderContext, GetStyle()))
+ rRenderContext.SetTextFillColor();
+ else
+ rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
+
+ ImplPaintBorder(rRenderContext);
+
+ bool bDrawSelection = maSelection.Len() && (HasFocus() || (GetStyle() & WB_NOHIDESELECTION) || mbActivePopup);
+
+ aPos.setX( mnXOffset + ImplGetExtraXOffset() );
+ if (bPaintPlaceholderText)
+ {
+ rRenderContext.DrawText(aPos, maPlaceholderText);
+ }
+ else if (!bDrawSelection && !mpIMEInfos)
+ {
+ rRenderContext.DrawText(aPos, aText, 0, nLen);
+ }
+ else
+ {
+ // save graphics state
+ rRenderContext.Push();
+ // first calculate highlighted and non highlighted clip regions
+ vcl::Region aHighlightClipRegion;
+ vcl::Region aNormalClipRegion;
+ Selection aTmpSel(maSelection);
+ aTmpSel.Normalize();
+ // selection is highlighted
+ for(sal_Int32 i = 0; i < nLen; ++i)
+ {
+ tools::Rectangle aRect(aPos, Size(10, nTH));
+ aRect.SetLeft( aDX[2 * i] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.SetRight( aDX[2 * i + 1] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.Normalize();
+ bool bHighlight = false;
+ if (i >= aTmpSel.Min() && i < aTmpSel.Max())
+ bHighlight = true;
+
+ if (mpIMEInfos && mpIMEInfos->pAttribs &&
+ i >= mpIMEInfos->nPos && i < (mpIMEInfos->nPos+mpIMEInfos->nLen) &&
+ (mpIMEInfos->pAttribs[i - mpIMEInfos->nPos] & ExtTextInputAttr::Highlight))
+ {
+ bHighlight = true;
+ }
+
+ if (bHighlight)
+ aHighlightClipRegion.Union(aRect);
+ else
+ aNormalClipRegion.Union(aRect);
+ }
+ // draw normal text
+ Color aNormalTextColor = rRenderContext.GetTextColor();
+ rRenderContext.SetClipRegion(aNormalClipRegion);
+
+ if (IsPaintTransparent())
+ rRenderContext.SetTextFillColor();
+ else
+ {
+ // Set background color when part of the text is selected
+ if (ImplUseNativeBorder(rRenderContext, GetStyle()))
+ {
+ if( mbForceControlBackground && IsControlBackground() )
+ rRenderContext.SetTextFillColor(GetControlBackground());
+ else
+ rRenderContext.SetTextFillColor();
+ }
+ else
+ {
+ rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
+ }
+ }
+ rRenderContext.DrawText(aPos, aText, 0, nLen);
+
+ // draw highlighted text
+ rRenderContext.SetClipRegion(aHighlightClipRegion);
+ rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
+ rRenderContext.SetTextFillColor(rStyleSettings.GetHighlightColor());
+ rRenderContext.DrawText(aPos, aText, 0, nLen);
+
+ // if IME info exists loop over portions and output different font attributes
+ if (mpIMEInfos && mpIMEInfos->pAttribs)
+ {
+ for(int n = 0; n < 2; n++)
+ {
+ vcl::Region aRegion;
+ if (n == 0)
+ {
+ rRenderContext.SetTextColor(aNormalTextColor);
+ if (IsPaintTransparent())
+ rRenderContext.SetTextFillColor();
+ else
+ rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
+ aRegion = aNormalClipRegion;
+ }
+ else
+ {
+ rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
+ rRenderContext.SetTextFillColor(rStyleSettings.GetHighlightColor());
+ aRegion = aHighlightClipRegion;
+ }
+
+ for(int i = 0; i < mpIMEInfos->nLen; )
+ {
+ ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[i];
+ vcl::Region aClip;
+ int nIndex = i;
+ while (nIndex < mpIMEInfos->nLen && mpIMEInfos->pAttribs[nIndex] == nAttr) // #112631# check nIndex before using it
+ {
+ tools::Rectangle aRect( aPos, Size( 10, nTH ) );
+ aRect.SetLeft( aDX[2 * (nIndex + mpIMEInfos->nPos)] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.SetRight( aDX[2 * (nIndex + mpIMEInfos->nPos) + 1] + mnXOffset + ImplGetExtraXOffset() );
+ aRect.Normalize();
+ aClip.Union(aRect);
+ nIndex++;
+ }
+ i = nIndex;
+ aClip.Intersect(aRegion);
+ if (!aClip.IsEmpty() && nAttr != ExtTextInputAttr::NONE)
+ {
+ vcl::Font aFont = rRenderContext.GetFont();
+ if (nAttr & ExtTextInputAttr::Underline)
+ aFont.SetUnderline(LINESTYLE_SINGLE);
+ else if (nAttr & ExtTextInputAttr::DoubleUnderline)
+ aFont.SetUnderline(LINESTYLE_DOUBLE);
+ else if (nAttr & ExtTextInputAttr::BoldUnderline)
+ aFont.SetUnderline( LINESTYLE_BOLD);
+ else if (nAttr & ExtTextInputAttr::DottedUnderline)
+ aFont.SetUnderline( LINESTYLE_DOTTED);
+ else if (nAttr & ExtTextInputAttr::DashDotUnderline)
+ aFont.SetUnderline( LINESTYLE_DASHDOT);
+ else if (nAttr & ExtTextInputAttr::GrayWaveline)
+ {
+ aFont.SetUnderline(LINESTYLE_WAVE);
+ rRenderContext.SetTextLineColor(COL_LIGHTGRAY);
+ }
+ rRenderContext.SetFont(aFont);
+
+ if (nAttr & ExtTextInputAttr::RedText)
+ rRenderContext.SetTextColor(COL_RED);
+ else if (nAttr & ExtTextInputAttr::HalfToneText)
+ rRenderContext.SetTextColor(COL_LIGHTGRAY);
+
+ rRenderContext.SetClipRegion(aClip);
+ rRenderContext.DrawText(aPos, aText, 0, nLen);
+ }
+ }
+ }
+ }
+
+ // restore graphics state
+ rRenderContext.Pop();
+ }
+
+ if (bVisCursor && (!mpIMEInfos || mpIMEInfos->bCursor))
+ pCursor->Show();
+}
+
+void Edit::ImplDelete( const Selection& rSelection, sal_uInt8 nDirection, sal_uInt8 nMode )
+{
+ const sal_Int32 nTextLen = ImplGetText().getLength();
+
+ // deleting possible?
+ if ( !rSelection.Len() &&
+ (((rSelection.Min() == 0) && (nDirection == EDIT_DEL_LEFT)) ||
+ ((rSelection.Max() == nTextLen) && (nDirection == EDIT_DEL_RIGHT))) )
+ return;
+
+ ImplClearLayoutData();
+
+ Selection aSelection( rSelection );
+ aSelection.Normalize();
+
+ if ( !aSelection.Len() )
+ {
+ uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
+ if ( nDirection == EDIT_DEL_LEFT )
+ {
+ if ( nMode == EDIT_DELMODE_RESTOFWORD )
+ {
+ const OUString sText = maText.toString();
+ i18n::Boundary aBoundary = xBI->getWordBoundary( sText, aSelection.Min(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
+ auto startPos = aBoundary.startPos;
+ if ( startPos == aSelection.Min() )
+ {
+ aBoundary = xBI->previousWord( sText, aSelection.Min(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
+ startPos = std::max(aBoundary.startPos, sal_Int32(0));
+ }
+ aSelection.Min() = startPos;
+ }
+ else if ( nMode == EDIT_DELMODE_RESTOFCONTENT )
+ {
+ aSelection.Min() = 0;
+ }
+ else
+ {
+ sal_Int32 nCount = 1;
+ aSelection.Min() = xBI->previousCharacters( maText.toString(), aSelection.Min(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
+ }
+ }
+ else
+ {
+ if ( nMode == EDIT_DELMODE_RESTOFWORD )
+ {
+ i18n::Boundary aBoundary = xBI->nextWord( maText.toString(), aSelection.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
+ aSelection.Max() = aBoundary.startPos;
+ }
+ else if ( nMode == EDIT_DELMODE_RESTOFCONTENT )
+ {
+ aSelection.Max() = nTextLen;
+ }
+ else
+ {
+ sal_Int32 nCount = 1;
+ aSelection.Max() = xBI->nextCharacters( maText.toString(), aSelection.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
+ }
+ }
+ }
+
+ const auto nSelectionMin = aSelection.Min();
+ maText.remove( nSelectionMin, aSelection.Len() );
+ maSelection.Min() = nSelectionMin;
+ maSelection.Max() = nSelectionMin;
+ ImplAlignAndPaint();
+ mbInternModified = true;
+}
+
+OUString Edit::ImplGetValidString( const OUString& rString )
+{
+ OUString aValidString = rString.replaceAll("\n", "").replaceAll("\r", "");
+ aValidString = aValidString.replace('\t', ' ');
+ return aValidString;
+}
+
+uno::Reference <i18n::XBreakIterator> const& Edit::ImplGetBreakIterator()
+{
+ if (!mxBreakIterator)
+ mxBreakIterator = i18n::BreakIterator::create(::comphelper::getProcessComponentContext());
+ return mxBreakIterator;
+}
+
+uno::Reference <i18n::XExtendedInputSequenceChecker> const& Edit::ImplGetInputSequenceChecker()
+{
+ if (!mxISC.is())
+ mxISC = i18n::InputSequenceChecker::create(::comphelper::getProcessComponentContext());
+ return mxISC;
+}
+
+void Edit::ShowTruncationWarning(weld::Widget* pParent)
+{
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent, VclMessageType::Warning,
+ VclButtonsType::Ok, VclResId(SV_EDIT_WARNING_STR)));
+ xBox->run();
+}
+
+bool Edit::ImplTruncateToMaxLen( OUString& rStr, sal_Int32 nSelectionLen ) const
+{
+ bool bWasTruncated = false;
+ if (maText.getLength() - nSelectionLen > mnMaxTextLen - rStr.getLength())
+ {
+ sal_Int32 nErasePos = mnMaxTextLen - maText.getLength() + nSelectionLen;
+ rStr = rStr.copy( 0, nErasePos );
+ bWasTruncated = true;
+ }
+ return bWasTruncated;
+}
+
+void Edit::ImplInsertText( const OUString& rStr, const Selection* pNewSel, bool bIsUserInput )
+{
+ Selection aSelection( maSelection );
+ aSelection.Normalize();
+
+ OUString aNewText( ImplGetValidString( rStr ) );
+
+ // as below, if there's no selection, but we're in overwrite mode and not beyond
+ // the end of the existing text then that's like a selection of 1
+ auto nSelectionLen = aSelection.Len();
+ if (!nSelectionLen && !mbInsertMode && aSelection.Max() < maText.getLength())
+ nSelectionLen = 1;
+ ImplTruncateToMaxLen( aNewText, nSelectionLen );
+
+ ImplClearLayoutData();
+
+ if ( aSelection.Len() )
+ maText.remove( aSelection.Min(), aSelection.Len() );
+ else if (!mbInsertMode && aSelection.Max() < maText.getLength())
+ maText.remove( aSelection.Max(), 1 );
+
+ // take care of input-sequence-checking now
+ if (bIsUserInput && !rStr.isEmpty())
+ {
+ SAL_WARN_IF( rStr.getLength() != 1, "vcl", "unexpected string length. User input is expected to provide 1 char only!" );
+
+ // determine if input-sequence-checking should be applied or not
+
+ uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
+ bool bIsInputSequenceChecking = rStr.getLength() == 1 &&
+ officecfg::Office::Common::I18N::CTL::CTLFont::get() &&
+ officecfg::Office::Common::I18N::CTL::CTLSequenceChecking::get() &&
+ aSelection.Min() > 0 && /* first char needs not to be checked */
+ xBI.is() && i18n::ScriptType::COMPLEX == xBI->getScriptType( rStr, 0 );
+
+ if (bIsInputSequenceChecking)
+ {
+ uno::Reference < i18n::XExtendedInputSequenceChecker > xISC = ImplGetInputSequenceChecker();
+ if (xISC.is())
+ {
+ sal_Unicode cChar = rStr[0];
+ sal_Int32 nTmpPos = aSelection.Min();
+ sal_Int16 nCheckMode = officecfg::Office::Common::I18N::CTL::CTLSequenceCheckingRestricted::get()?
+ i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
+
+ // the text that needs to be checked is only the one
+ // before the current cursor position
+ const OUString aOldText( maText.subView(0, nTmpPos) );
+ OUString aTmpText( aOldText );
+ if (officecfg::Office::Common::I18N::CTL::CTLSequenceCheckingTypeAndReplace::get())
+ {
+ xISC->correctInputSequence( aTmpText, nTmpPos - 1, cChar, nCheckMode );
+
+ // find position of first character that has changed
+ sal_Int32 nOldLen = aOldText.getLength();
+ sal_Int32 nTmpLen = aTmpText.getLength();
+ const sal_Unicode *pOldTxt = aOldText.getStr();
+ const sal_Unicode *pTmpTxt = aTmpText.getStr();
+ sal_Int32 nChgPos = 0;
+ while ( nChgPos < nOldLen && nChgPos < nTmpLen &&
+ pOldTxt[nChgPos] == pTmpTxt[nChgPos] )
+ ++nChgPos;
+
+ const OUString aChgText( aTmpText.copy( nChgPos ) );
+
+ // remove text from first pos to be changed to current pos
+ maText.remove( nChgPos, nTmpPos - nChgPos );
+
+ if (!aChgText.isEmpty())
+ {
+ aNewText = aChgText;
+ aSelection.Min() = nChgPos; // position for new text to be inserted
+ }
+ else
+ aNewText.clear();
+ }
+ else
+ {
+ // should the character be ignored (i.e. not get inserted) ?
+ if (!xISC->checkInputSequence( aOldText, nTmpPos - 1, cChar, nCheckMode ))
+ aNewText.clear();
+ }
+ }
+ }
+
+ // at this point now we will insert the non-empty text 'normally' some lines below...
+ }
+
+ if ( !aNewText.isEmpty() )
+ maText.insert( aSelection.Min(), aNewText );
+
+ if ( !pNewSel )
+ {
+ maSelection.Min() = aSelection.Min() + aNewText.getLength();
+ maSelection.Max() = maSelection.Min();
+ }
+ else
+ {
+ maSelection = *pNewSel;
+ if ( maSelection.Min() > maText.getLength() )
+ maSelection.Min() = maText.getLength();
+ if ( maSelection.Max() > maText.getLength() )
+ maSelection.Max() = maText.getLength();
+ }
+
+ ImplAlignAndPaint();
+ mbInternModified = true;
+}
+
+void Edit::ImplSetText( const OUString& rText, const Selection* pNewSelection )
+{
+ // we delete text by "selecting" the old text completely then calling InsertText; this is flicker free
+ if ( ( rText.getLength() > mnMaxTextLen ) ||
+ ( std::u16string_view(rText) == std::u16string_view(maText)
+ && (!pNewSelection || (*pNewSelection == maSelection)) ) )
+ return;
+
+ ImplClearLayoutData();
+ maSelection.Min() = 0;
+ maSelection.Max() = maText.getLength();
+ if ( mnXOffset || HasPaintEvent() )
+ {
+ mnXOffset = 0;
+ maText = ImplGetValidString( rText );
+
+ // #i54929# recalculate mnXOffset before ImplSetSelection,
+ // else cursor ends up in wrong position
+ ImplAlign();
+
+ if ( pNewSelection )
+ ImplSetSelection( *pNewSelection, false );
+
+ if ( mnXOffset && !pNewSelection )
+ maSelection.Max() = 0;
+
+ Invalidate();
+ }
+ else
+ ImplInsertText( rText, pNewSelection );
+
+ CallEventListeners( VclEventId::EditModify );
+}
+
+ControlType Edit::ImplGetNativeControlType() const
+{
+ ControlType nCtrl = ControlType::Generic;
+ const vcl::Window* pControl = mbIsSubEdit ? GetParent() : this;
+
+ switch (pControl->GetType())
+ {
+ case WindowType::COMBOBOX:
+ case WindowType::PATTERNBOX:
+ case WindowType::NUMERICBOX:
+ case WindowType::METRICBOX:
+ case WindowType::CURRENCYBOX:
+ case WindowType::DATEBOX:
+ case WindowType::TIMEBOX:
+ case WindowType::LONGCURRENCYBOX:
+ nCtrl = ControlType::Combobox;
+ break;
+
+ case WindowType::MULTILINEEDIT:
+ if ( GetWindow( GetWindowType::Border ) != this )
+ nCtrl = ControlType::MultilineEditbox;
+ else
+ nCtrl = ControlType::EditboxNoBorder;
+ 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 (pControl->GetStyle() & WB_SPIN)
+ nCtrl = ControlType::Spinbox;
+ else
+ {
+ if (GetWindow(GetWindowType::Border) != this)
+ nCtrl = ControlType::Editbox;
+ else
+ nCtrl = ControlType::EditboxNoBorder;
+ }
+ break;
+
+ default:
+ nCtrl = ControlType::Editbox;
+ }
+ return nCtrl;
+}
+
+void Edit::ImplClearBackground(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRectangle, tools::Long nXStart, tools::Long nXEnd )
+{
+ /*
+ * note: at this point the cursor must be switched off already
+ */
+ tools::Rectangle aRect(Point(), GetOutputSizePixel());
+ aRect.SetLeft( nXStart );
+ aRect.SetRight( nXEnd );
+
+ if( !(ImplUseNativeBorder(rRenderContext, GetStyle()) || IsPaintTransparent()))
+ rRenderContext.Erase(aRect);
+ else if (SupportsDoubleBuffering() && mbIsSubEdit)
+ {
+ // ImplPaintBorder() is a NOP, we have a native border, and this is a sub-edit of a control.
+ // That means we have to draw the parent native widget to paint the edit area to clear our background.
+ vcl::PaintBufferGuard g(ImplGetWindowImpl()->mpFrameData, GetParent());
+ GetParent()->Paint(rRenderContext, rRectangle);
+ }
+}
+
+void Edit::ImplPaintBorder(vcl::RenderContext const & rRenderContext)
+{
+ // this is not needed when double-buffering
+ if (SupportsDoubleBuffering())
+ return;
+
+ if (!(ImplUseNativeBorder(rRenderContext, GetStyle()) || IsPaintTransparent()))
+ return;
+
+ // draw the inner part by painting the whole control using its border window
+ vcl::Window* pBorder = GetWindow(GetWindowType::Border);
+ if (pBorder == this)
+ {
+ // we have no border, use parent
+ vcl::Window* pControl = mbIsSubEdit ? GetParent() : this;
+ pBorder = pControl->GetWindow(GetWindowType::Border);
+ if (pBorder == this)
+ pBorder = GetParent();
+ }
+
+ if (!pBorder)
+ return;
+
+ // set proper clipping region to not overdraw the whole control
+ vcl::Region aClipRgn = GetPaintRegion();
+ if (!aClipRgn.IsNull())
+ {
+ // transform clipping region to border window's coordinate system
+ if (IsRTLEnabled() != pBorder->IsRTLEnabled() && AllSettings::GetLayoutRTL())
+ {
+ // need to mirror in case border is not RTL but edit is (or vice versa)
+
+ // mirror
+ tools::Rectangle aBounds(aClipRgn.GetBoundRect());
+ int xNew = GetOutputSizePixel().Width() - aBounds.GetWidth() - aBounds.Left();
+ aClipRgn.Move(xNew - aBounds.Left(), 0);
+
+ // move offset of border window
+ Point aBorderOffs = pBorder->ScreenToOutputPixel(OutputToScreenPixel(Point()));
+ aClipRgn.Move(aBorderOffs.X(), aBorderOffs.Y());
+ }
+ else
+ {
+ // normal case
+ Point aBorderOffs = pBorder->ScreenToOutputPixel(OutputToScreenPixel(Point()));
+ aClipRgn.Move(aBorderOffs.X(), aBorderOffs.Y());
+ }
+
+ vcl::Region oldRgn(pBorder->GetOutDev()->GetClipRegion());
+ pBorder->GetOutDev()->SetClipRegion(aClipRgn);
+
+ pBorder->Paint(*pBorder->GetOutDev(), tools::Rectangle());
+
+ pBorder->GetOutDev()->SetClipRegion(oldRgn);
+ }
+ else
+ {
+ pBorder->Paint(*pBorder->GetOutDev(), tools::Rectangle());
+ }
+}
+
+void Edit::ImplShowCursor( bool bOnlyIfVisible )
+{
+ if ( !IsUpdateMode() || ( bOnlyIfVisible && !IsReallyVisible() ) )
+ return;
+
+ vcl::Cursor* pCursor = GetCursor();
+ OUString aText = ImplGetText();
+
+ tools::Long nTextPos = 0;
+
+ if( !aText.isEmpty() )
+ {
+ KernArray aDX;
+ GetOutDev()->GetCaretPositions(aText, aDX, 0, aText.getLength());
+
+ if( maSelection.Max() < aText.getLength() )
+ nTextPos = aDX[ 2*maSelection.Max() ];
+ else
+ nTextPos = aDX[ 2*aText.getLength()-1 ];
+ }
+
+ tools::Long nCursorWidth = 0;
+ if ( !mbInsertMode && !maSelection.Len() && (maSelection.Max() < aText.getLength()) )
+ nCursorWidth = GetTextWidth(aText, maSelection.Max(), 1);
+ tools::Long nCursorPosX = nTextPos + mnXOffset + ImplGetExtraXOffset();
+
+ // cursor should land in visible area
+ const Size aOutSize = GetOutputSizePixel();
+ if ( (nCursorPosX < 0) || (nCursorPosX >= aOutSize.Width()) )
+ {
+ tools::Long nOldXOffset = mnXOffset;
+
+ if ( nCursorPosX < 0 )
+ {
+ mnXOffset = - nTextPos;
+ tools::Long nMaxX = 0;
+ mnXOffset += aOutSize.Width() / 5;
+ if ( mnXOffset > nMaxX )
+ mnXOffset = nMaxX;
+ }
+ else
+ {
+ mnXOffset = (aOutSize.Width()-ImplGetExtraXOffset()) - nTextPos;
+ // Something more?
+ if ( (aOutSize.Width()-ImplGetExtraXOffset()) < nTextPos )
+ {
+ tools::Long nMaxNegX = (aOutSize.Width()-ImplGetExtraXOffset()) - GetTextWidth( aText );
+ mnXOffset -= aOutSize.Width() / 5;
+ if ( mnXOffset < nMaxNegX ) // both negative...
+ mnXOffset = nMaxNegX;
+ }
+ }
+
+ nCursorPosX = nTextPos + mnXOffset + ImplGetExtraXOffset();
+ if ( nCursorPosX == aOutSize.Width() ) // then invisible...
+ nCursorPosX--;
+
+ if ( mnXOffset != nOldXOffset )
+ ImplInvalidateOrRepaint();
+ }
+
+ const tools::Long nTextHeight = GetTextHeight();
+ const tools::Long nCursorPosY = ImplGetTextYPosition();
+ if (pCursor)
+ {
+ pCursor->SetPos( Point( nCursorPosX, nCursorPosY ) );
+ pCursor->SetSize( Size( nCursorWidth, nTextHeight ) );
+ pCursor->Show();
+ }
+}
+
+void Edit::ImplAlign()
+{
+ if (mnAlign == EDIT_ALIGN_LEFT && !mnXOffset)
+ {
+ // short circuit common case and avoid slow GetTextWidth() calc
+ return;
+ }
+
+ tools::Long nTextWidth = GetTextWidth( ImplGetText() );
+ tools::Long nOutWidth = GetOutputSizePixel().Width();
+
+ if ( mnAlign == EDIT_ALIGN_LEFT )
+ {
+ if (nTextWidth < nOutWidth)
+ mnXOffset = 0;
+ }
+ else if ( mnAlign == EDIT_ALIGN_RIGHT )
+ {
+ tools::Long nMinXOffset = nOutWidth - nTextWidth - 1 - ImplGetExtraXOffset();
+ bool bRTL = IsRTLEnabled();
+ if( mbIsSubEdit && GetParent() )
+ bRTL = GetParent()->IsRTLEnabled();
+ if( bRTL )
+ {
+ if( nTextWidth < nOutWidth )
+ mnXOffset = nMinXOffset;
+ }
+ else
+ {
+ if( nTextWidth < nOutWidth )
+ mnXOffset = nMinXOffset;
+ else if ( mnXOffset < nMinXOffset )
+ mnXOffset = nMinXOffset;
+ }
+ }
+ else if( mnAlign == EDIT_ALIGN_CENTER )
+ {
+ // would be nicer with check while scrolling but then it's not centred in scrolled state
+ mnXOffset = (nOutWidth - nTextWidth) / 2;
+ }
+}
+
+void Edit::ImplAlignAndPaint()
+{
+ ImplAlign();
+ ImplInvalidateOrRepaint();
+ ImplShowCursor();
+}
+
+sal_Int32 Edit::ImplGetCharPos( const Point& rWindowPos ) const
+{
+ sal_Int32 nIndex = EDIT_NOLIMIT;
+ OUString aText = ImplGetText();
+
+ if (aText.isEmpty())
+ return nIndex;
+
+ KernArray aDX;
+ GetOutDev()->GetCaretPositions(aText, aDX, 0, aText.getLength());
+ tools::Long nX = rWindowPos.X() - mnXOffset - ImplGetExtraXOffset();
+ for (sal_Int32 i = 0; i < aText.getLength(); aText.iterateCodePoints(&i))
+ {
+ if( (aDX[2*i] >= nX && aDX[2*i+1] <= nX) ||
+ (aDX[2*i+1] >= nX && aDX[2*i] <= nX))
+ {
+ nIndex = i;
+ if( aDX[2*i] < aDX[2*i+1] )
+ {
+ if( nX > (aDX[2*i]+aDX[2*i+1])/2 )
+ aText.iterateCodePoints(&nIndex);
+ }
+ else
+ {
+ if( nX < (aDX[2*i]+aDX[2*i+1])/2 )
+ aText.iterateCodePoints(&nIndex);
+ }
+ break;
+ }
+ }
+ if( nIndex == EDIT_NOLIMIT )
+ {
+ nIndex = 0;
+ sal_Int32 nFinalIndex = 0;
+ tools::Long nDiff = std::abs( aDX[0]-nX );
+ sal_Int32 i = 0;
+ if (!aText.isEmpty())
+ {
+ aText.iterateCodePoints(&i); //skip the first character
+ }
+ while (i < aText.getLength())
+ {
+ tools::Long nNewDiff = std::abs( aDX[2*i]-nX );
+
+ if( nNewDiff < nDiff )
+ {
+ nIndex = i;
+ nDiff = nNewDiff;
+ }
+
+ nFinalIndex = i;
+
+ aText.iterateCodePoints(&i);
+ }
+ if (nIndex == nFinalIndex && std::abs( aDX[2*nIndex+1] - nX ) < nDiff)
+ nIndex = EDIT_NOLIMIT;
+ }
+
+ return nIndex;
+}
+
+void Edit::ImplSetCursorPos( sal_Int32 nChar, bool bSelect )
+{
+ Selection aSelection( maSelection );
+ aSelection.Max() = nChar;
+ if ( !bSelect )
+ aSelection.Min() = aSelection.Max();
+ ImplSetSelection( aSelection );
+}
+
+void Edit::ImplCopyToSelectionClipboard()
+{
+ if ( GetSelection().Len() )
+ {
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
+ ImplCopy( aSelection );
+ }
+}
+
+void Edit::ImplCopy( uno::Reference< datatransfer::clipboard::XClipboard > const & rxClipboard )
+{
+ vcl::unohelper::TextDataObject::CopyStringTo( GetSelected(), rxClipboard );
+}
+
+void Edit::ImplPaste( uno::Reference< datatransfer::clipboard::XClipboard > const & rxClipboard )
+{
+ if ( !rxClipboard.is() )
+ return;
+
+ uno::Reference< datatransfer::XTransferable > xDataObj;
+
+ try
+ {
+ SolarMutexReleaser aReleaser;
+ xDataObj = rxClipboard->getContents();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ if ( !xDataObj.is() )
+ return;
+
+ datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ try
+ {
+ uno::Any aData = xDataObj->getTransferData( aFlavor );
+ OUString aText;
+ aData >>= aText;
+
+ // tdf#127588 - extend selection to the entire field or paste the text
+ // from the clipboard to the current position if there is no selection
+ if (mnMaxTextLen < EDIT_NOLIMIT && maSelection.Len() == 0)
+ {
+ const sal_Int32 aTextLen = aText.getLength();
+ if (aTextLen == mnMaxTextLen)
+ {
+ maSelection.Min() = 0;
+ maSelection.Max() = mnMaxTextLen;
+ } else
+ maSelection.Max() = std::min<sal_Int32>(maSelection.Min() + aTextLen, mnMaxTextLen);
+ }
+
+ Selection aSelection(maSelection);
+ aSelection.Normalize();
+ if (ImplTruncateToMaxLen(aText, aSelection.Len()))
+ ShowTruncationWarning(GetFrameWeld());
+
+ ReplaceSelected( aText );
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+}
+
+void Edit::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( mpSubEdit )
+ {
+ Control::MouseButtonDown( rMEvt );
+ return;
+ }
+
+ sal_Int32 nCharPos = ImplGetCharPos( rMEvt.GetPosPixel() );
+ Selection aSelection( maSelection );
+ aSelection.Normalize();
+
+ if ( rMEvt.GetClicks() < 4 )
+ {
+ mbClickedInSelection = false;
+ if ( rMEvt.GetClicks() == 3 )
+ {
+ ImplSetSelection( Selection( 0, EDIT_NOLIMIT) );
+ ImplCopyToSelectionClipboard();
+
+ }
+ else if ( rMEvt.GetClicks() == 2 )
+ {
+ uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
+ i18n::Boundary aBoundary = xBI->getWordBoundary( maText.toString(), aSelection.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
+ ImplSetSelection( Selection( aBoundary.startPos, aBoundary.endPos ) );
+ ImplCopyToSelectionClipboard();
+ }
+ else if ( !rMEvt.IsShift() && HasFocus() && aSelection.Contains( nCharPos ) )
+ mbClickedInSelection = true;
+ else if ( rMEvt.IsLeft() )
+ ImplSetCursorPos( nCharPos, rMEvt.IsShift() );
+
+ if ( !mbClickedInSelection && rMEvt.IsLeft() && ( rMEvt.GetClicks() == 1 ) )
+ StartTracking( StartTrackingFlags::ScrollRepeat );
+ }
+
+ GrabFocus();
+}
+
+void Edit::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ if ( mbClickedInSelection && rMEvt.IsLeft() )
+ {
+ sal_Int32 nCharPos = ImplGetCharPos( rMEvt.GetPosPixel() );
+ ImplSetCursorPos( nCharPos, false );
+ mbClickedInSelection = false;
+ }
+ else if ( rMEvt.IsMiddle() && !mbReadOnly &&
+ ( GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) )
+ {
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
+ ImplPaste( aSelection );
+ Modify();
+ }
+}
+
+void Edit::Tracking( const TrackingEvent& rTEvt )
+{
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ if ( mbClickedInSelection )
+ {
+ sal_Int32 nCharPos = ImplGetCharPos( rTEvt.GetMouseEvent().GetPosPixel() );
+ ImplSetCursorPos( nCharPos, false );
+ mbClickedInSelection = false;
+ }
+ else if ( rTEvt.GetMouseEvent().IsLeft() )
+ {
+ ImplCopyToSelectionClipboard();
+ }
+ }
+ else
+ {
+ if( !mbClickedInSelection )
+ {
+ sal_Int32 nCharPos = ImplGetCharPos( rTEvt.GetMouseEvent().GetPosPixel() );
+ ImplSetCursorPos( nCharPos, true );
+ }
+ }
+}
+
+bool Edit::ImplHandleKeyEvent( const KeyEvent& rKEvt )
+{
+ bool bDone = false;
+ sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
+ KeyFuncType eFunc = rKEvt.GetKeyCode().GetFunction();
+
+ mbInternModified = false;
+
+ if ( eFunc != KeyFuncType::DONTKNOW )
+ {
+ switch ( eFunc )
+ {
+ case KeyFuncType::CUT:
+ {
+ if ( !mbReadOnly && maSelection.Len() && !mbPassword )
+ {
+ Cut();
+ Modify();
+ bDone = true;
+ }
+ }
+ break;
+
+ case KeyFuncType::COPY:
+ {
+ if ( !mbPassword )
+ {
+ Copy();
+ bDone = true;
+ }
+ }
+ break;
+
+ case KeyFuncType::PASTE:
+ {
+ if ( !mbReadOnly )
+ {
+ Paste();
+ bDone = true;
+ }
+ }
+ break;
+
+ case KeyFuncType::UNDO:
+ {
+ if ( !mbReadOnly )
+ {
+ Undo();
+ bDone = true;
+ }
+ }
+ break;
+
+ default:
+ eFunc = KeyFuncType::DONTKNOW;
+ }
+ }
+
+ if ( !bDone && rKEvt.GetKeyCode().IsMod1() && !rKEvt.GetKeyCode().IsMod2() )
+ {
+ if ( nCode == KEY_A )
+ {
+ ImplSetSelection( Selection( 0, maText.getLength() ) );
+ bDone = true;
+ }
+ else if ( rKEvt.GetKeyCode().IsShift() && (nCode == KEY_S) )
+ {
+ if ( pImplFncGetSpecialChars )
+ {
+ Selection aSaveSel = GetSelection(); // if someone changes the selection in Get/LoseFocus, e.g. URL bar
+ OUString aChars = pImplFncGetSpecialChars( GetFrameWeld(), GetFont() );
+ SetSelection( aSaveSel );
+ if ( !aChars.isEmpty() )
+ {
+ ImplInsertText( aChars );
+ Modify();
+ }
+ bDone = true;
+ }
+ }
+ }
+
+ if ( eFunc == KeyFuncType::DONTKNOW && ! bDone )
+ {
+ switch ( nCode )
+ {
+ case css::awt::Key::SELECT_ALL:
+ {
+ ImplSetSelection( Selection( 0, maText.getLength() ) );
+ bDone = true;
+ }
+ break;
+
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ case KEY_HOME:
+ case KEY_END:
+ case css::awt::Key::MOVE_WORD_FORWARD:
+ case css::awt::Key::SELECT_WORD_FORWARD:
+ case css::awt::Key::MOVE_WORD_BACKWARD:
+ case css::awt::Key::SELECT_WORD_BACKWARD:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
+ case css::awt::Key::MOVE_TO_END_OF_LINE:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
+ case css::awt::Key::SELECT_TO_END_OF_LINE:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
+ case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
+ case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
+ case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
+ case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
+ {
+ if ( !rKEvt.GetKeyCode().IsMod2() )
+ {
+ ImplClearLayoutData();
+ uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
+
+ Selection aSel( maSelection );
+ bool bWord = rKEvt.GetKeyCode().IsMod1();
+ bool bSelect = rKEvt.GetKeyCode().IsShift();
+ bool bGoLeft = (nCode == KEY_LEFT);
+ bool bGoRight = (nCode == KEY_RIGHT);
+ bool bGoHome = (nCode == KEY_HOME);
+ bool bGoEnd = (nCode == KEY_END);
+
+ switch( nCode )
+ {
+ case css::awt::Key::MOVE_WORD_FORWARD:
+ bGoRight = bWord = true;break;
+ case css::awt::Key::SELECT_WORD_FORWARD:
+ bGoRight = bSelect = bWord = true;break;
+ case css::awt::Key::MOVE_WORD_BACKWARD:
+ bGoLeft = bWord = true;break;
+ case css::awt::Key::SELECT_WORD_BACKWARD:
+ bGoLeft = bSelect = bWord = true;break;
+ case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
+ bSelect = true;
+ [[fallthrough]];
+ case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
+ bGoHome = true;break;
+ case css::awt::Key::SELECT_TO_END_OF_LINE:
+ case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
+ case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
+ bSelect = true;
+ [[fallthrough]];
+ case css::awt::Key::MOVE_TO_END_OF_LINE:
+ case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
+ case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
+ bGoEnd = true;break;
+ default:
+ break;
+ }
+
+ // range is checked in ImplSetSelection ...
+ if ( bGoLeft && aSel.Max() )
+ {
+ if ( bWord )
+ {
+ const OUString sText = maText.toString();
+ i18n::Boundary aBoundary = xBI->getWordBoundary( sText, aSel.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
+ if ( aBoundary.startPos == aSel.Max() )
+ aBoundary = xBI->previousWord( sText, aSel.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
+ aSel.Max() = aBoundary.startPos;
+ }
+ else
+ {
+ sal_Int32 nCount = 1;
+ aSel.Max() = xBI->previousCharacters( maText.toString(), aSel.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
+ }
+ }
+ else if ( bGoRight && ( aSel.Max() < maText.getLength() ) )
+ {
+ if ( bWord )
+ {
+ i18n::Boundary aBoundary = xBI->nextWord( maText.toString(), aSel.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
+ aSel.Max() = aBoundary.startPos;
+ }
+ else
+ {
+ sal_Int32 nCount = 1;
+ aSel.Max() = xBI->nextCharacters( maText.toString(), aSel.Max(),
+ GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
+ }
+ }
+ else if ( bGoHome )
+ {
+ aSel.Max() = 0;
+ }
+ else if ( bGoEnd )
+ {
+ aSel.Max() = EDIT_NOLIMIT;
+ }
+
+ if ( !bSelect )
+ aSel.Min() = aSel.Max();
+
+ if ( aSel != GetSelection() )
+ {
+ ImplSetSelection( aSel );
+ ImplCopyToSelectionClipboard();
+ }
+
+ if (bGoEnd && maAutocompleteHdl.IsSet() && !rKEvt.GetKeyCode().GetModifier())
+ {
+ if ( (maSelection.Min() == maSelection.Max()) && (maSelection.Min() == maText.getLength()) )
+ {
+ maAutocompleteHdl.Call(*this);
+ }
+ }
+
+ bDone = true;
+ }
+ }
+ break;
+
+ case css::awt::Key::DELETE_WORD_BACKWARD:
+ case css::awt::Key::DELETE_WORD_FORWARD:
+ case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
+ case css::awt::Key::DELETE_TO_END_OF_LINE:
+ case KEY_BACKSPACE:
+ case KEY_DELETE:
+ {
+ if ( !mbReadOnly && !rKEvt.GetKeyCode().IsMod2() )
+ {
+ sal_uInt8 nDel = (nCode == KEY_DELETE) ? EDIT_DEL_RIGHT : EDIT_DEL_LEFT;
+ sal_uInt8 nMode = rKEvt.GetKeyCode().IsMod1() ? EDIT_DELMODE_RESTOFWORD : EDIT_DELMODE_SIMPLE;
+ if ( (nMode == EDIT_DELMODE_RESTOFWORD) && rKEvt.GetKeyCode().IsShift() )
+ nMode = EDIT_DELMODE_RESTOFCONTENT;
+ switch( nCode )
+ {
+ case css::awt::Key::DELETE_WORD_BACKWARD:
+ nDel = EDIT_DEL_LEFT;
+ nMode = EDIT_DELMODE_RESTOFWORD;
+ break;
+ case css::awt::Key::DELETE_WORD_FORWARD:
+ nDel = EDIT_DEL_RIGHT;
+ nMode = EDIT_DELMODE_RESTOFWORD;
+ break;
+ case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
+ nDel = EDIT_DEL_LEFT;
+ nMode = EDIT_DELMODE_RESTOFCONTENT;
+ break;
+ case css::awt::Key::DELETE_TO_END_OF_LINE:
+ nDel = EDIT_DEL_RIGHT;
+ nMode = EDIT_DELMODE_RESTOFCONTENT;
+ break;
+ default: break;
+ }
+ sal_Int32 nOldLen = maText.getLength();
+ ImplDelete( maSelection, nDel, nMode );
+ if ( maText.getLength() != nOldLen )
+ Modify();
+ bDone = true;
+ }
+ }
+ break;
+
+ case KEY_INSERT:
+ {
+ if ( !mpIMEInfos && !mbReadOnly && !rKEvt.GetKeyCode().IsMod2() )
+ {
+ SetInsertMode( !mbInsertMode );
+ bDone = true;
+ }
+ }
+ break;
+
+ case KEY_RETURN:
+ if (maActivateHdl.IsSet() && !rKEvt.GetKeyCode().GetModifier())
+ bDone = maActivateHdl.Call(*this);
+ break;
+
+ default:
+ {
+ if ( IsCharInput( rKEvt ) )
+ {
+ bDone = true; // read characters also when in ReadOnly
+ if ( !mbReadOnly )
+ {
+ ImplInsertText(OUString(rKEvt.GetCharCode()), nullptr, true);
+ if (maAutocompleteHdl.IsSet())
+ {
+ if ( (maSelection.Min() == maSelection.Max()) && (maSelection.Min() == maText.getLength()) )
+ {
+ maAutocompleteHdl.Call(*this);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ( mbInternModified )
+ Modify();
+
+ return bDone;
+}
+
+void Edit::KeyInput( const KeyEvent& rKEvt )
+{
+ if ( mpSubEdit || !ImplHandleKeyEvent( rKEvt ) )
+ Control::KeyInput( rKEvt );
+}
+
+void Edit::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const_cast<Edit*>(this)->Invalidate();
+}
+
+void Edit::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRectangle)
+{
+ if (!mpSubEdit)
+ ImplRepaint(rRenderContext, rRectangle);
+}
+
+void Edit::Resize()
+{
+ if ( !mpSubEdit && IsReallyVisible() )
+ {
+ Control::Resize();
+ // because of vertical centering...
+ mnXOffset = 0;
+ ImplAlign();
+ Invalidate();
+ ImplShowCursor();
+ }
+}
+
+void Edit::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags )
+{
+ ApplySettings(*pDev);
+
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ vcl::Font aFont = GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ pDev->SetTextFillColor();
+
+ // Border/Background
+ pDev->SetLineColor();
+ pDev->SetFillColor();
+ bool bBorder = (GetStyle() & WB_BORDER);
+ bool bBackground = IsControlBackground();
+ if ( bBorder || bBackground )
+ {
+ tools::Rectangle aRect( aPos, aSize );
+ if ( bBorder )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ if ( bBackground )
+ {
+ pDev->SetFillColor( GetControlBackground() );
+ pDev->DrawRect( aRect );
+ }
+ }
+
+ // Content
+ if ( nFlags & SystemTextColorFlags::Mono )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ {
+ if ( !IsEnabled() )
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ pDev->SetTextColor( rStyleSettings.GetDisableColor() );
+ }
+ else
+ {
+ pDev->SetTextColor( GetTextColor() );
+ }
+ }
+
+ const tools::Long nOnePixel = GetDrawPixel( pDev, 1 );
+ const tools::Long nOffX = 3*nOnePixel;
+ DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
+ tools::Rectangle aTextRect( aPos, aSize );
+
+ if ( GetStyle() & WB_CENTER )
+ nTextStyle |= DrawTextFlags::Center;
+ else if ( GetStyle() & WB_RIGHT )
+ nTextStyle |= DrawTextFlags::Right;
+ else
+ nTextStyle |= DrawTextFlags::Left;
+
+ aTextRect.AdjustLeft(nOffX );
+ aTextRect.AdjustRight( -nOffX );
+
+ OUString aText = ImplGetText();
+ tools::Long nTextHeight = pDev->GetTextHeight();
+ tools::Long nTextWidth = pDev->GetTextWidth( aText );
+ tools::Long nOffY = (aSize.Height() - nTextHeight) / 2;
+
+ // Clipping?
+ if ( (nOffY < 0) ||
+ ((nOffY+nTextHeight) > aSize.Height()) ||
+ ((nOffX+nTextWidth) > aSize.Width()) )
+ {
+ tools::Rectangle aClip( aPos, aSize );
+ if ( nTextHeight > aSize.Height() )
+ aClip.AdjustBottom(nTextHeight-aSize.Height()+1 ); // prevent HP printers from 'optimizing'
+ pDev->IntersectClipRegion( aClip );
+ }
+
+ pDev->DrawText( aTextRect, aText, nTextStyle );
+ pDev->Pop();
+
+ if ( GetSubEdit() )
+ {
+ Size aOrigSize(GetSubEdit()->GetSizePixel());
+ GetSubEdit()->SetSizePixel(GetSizePixel());
+ GetSubEdit()->Draw(pDev, rPos, nFlags);
+ GetSubEdit()->SetSizePixel(aOrigSize);
+ }
+}
+
+void Edit::ImplInvalidateOutermostBorder( vcl::Window* pWin )
+{
+ // allow control to show focused state
+ vcl::Window *pInvalWin = pWin;
+ for (;;)
+ {
+ vcl::Window* pBorder = pInvalWin->GetWindow( GetWindowType::Border );
+ if (pBorder == pInvalWin || !pBorder ||
+ pInvalWin->ImplGetFrame() != pBorder->ImplGetFrame() )
+ break;
+ pInvalWin = pBorder;
+ }
+
+ pInvalWin->Invalidate( InvalidateFlags::Children | InvalidateFlags::Update );
+}
+
+void Edit::GetFocus()
+{
+ Control::GetFocus();
+
+ if ( mpSubEdit )
+ mpSubEdit->ImplGrabFocus( GetGetFocusFlags() );
+ else if ( !mbActivePopup )
+ {
+ maUndoText = maText.toString();
+ SelectionOptions nSelOptions = GetSettings().GetStyleSettings().GetSelectionOptions();
+ if ( !( GetStyle() & (WB_NOHIDESELECTION|WB_READONLY) )
+ && ( GetGetFocusFlags() & (GetFocusFlags::Init|GetFocusFlags::Tab|GetFocusFlags::CURSOR|GetFocusFlags::Mnemonic) ) )
+ {
+ if ( nSelOptions & SelectionOptions::ShowFirst )
+ {
+ maSelection.Min() = maText.getLength();
+ maSelection.Max() = 0;
+ }
+ else
+ {
+ maSelection.Min() = 0;
+ maSelection.Max() = maText.getLength();
+ }
+ if ( mbIsSubEdit )
+ static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditSelectionChanged );
+ else
+ CallEventListeners( VclEventId::EditSelectionChanged );
+ }
+
+ ImplShowCursor();
+
+ if (IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Editbox, ControlPart::Entire ))
+ {
+ ImplInvalidateOutermostBorder( mbIsSubEdit ? GetParent() : this );
+ }
+ else if ( maSelection.Len() )
+ {
+ // paint the selection
+ if ( !HasPaintEvent() )
+ ImplInvalidateOrRepaint();
+ else
+ Invalidate();
+ }
+
+ SetInputContext( InputContext( GetFont(), !IsReadOnly() ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
+ }
+}
+
+void Edit::LoseFocus()
+{
+ if ( !mpSubEdit )
+ {
+ if (IsNativeWidgetEnabled() &&
+ IsNativeControlSupported(ControlType::Editbox, ControlPart::Entire))
+ {
+ ImplInvalidateOutermostBorder( mbIsSubEdit ? GetParent() : this );
+ }
+
+ if ( !mbActivePopup && !( GetStyle() & WB_NOHIDESELECTION ) && maSelection.Len() )
+ ImplInvalidateOrRepaint(); // paint the selection
+ }
+
+ Control::LoseFocus();
+}
+
+bool Edit::PreNotify(NotifyEvent& rNEvt)
+{
+ if (rNEvt.GetType() == NotifyEventType::MOUSEMOVE)
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if (pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged())
+ {
+ // trigger redraw if mouse over state has changed
+ if (pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
+ {
+ if (IsNativeWidgetEnabled() &&
+ IsNativeControlSupported(ControlType::Editbox, ControlPart::Entire))
+ {
+ ImplInvalidateOutermostBorder(this);
+ }
+ }
+ }
+ }
+
+ return Control::PreNotify(rNEvt);
+}
+
+void Edit::Command( const CommandEvent& rCEvt )
+{
+ if ( rCEvt.GetCommand() == CommandEventId::ContextMenu )
+ {
+ VclPtr<PopupMenu> pPopup = Edit::CreatePopupMenu();
+
+ bool bEnableCut = true;
+ bool bEnableCopy = true;
+ bool bEnableDelete = true;
+ bool bEnablePaste = true;
+ bool bEnableSpecialChar = true;
+
+ if ( !maSelection.Len() )
+ {
+ bEnableCut = false;
+ bEnableCopy = false;
+ bEnableDelete = false;
+ }
+
+ if ( IsReadOnly() )
+ {
+ bEnableCut = false;
+ bEnablePaste = false;
+ bEnableDelete = false;
+ bEnableSpecialChar = false;
+ }
+ else
+ {
+ // only paste if text available in clipboard
+ bool bData = false;
+ uno::Reference< datatransfer::clipboard::XClipboard > xClipboard = GetClipboard();
+
+ if ( xClipboard.is() )
+ {
+ uno::Reference< datatransfer::XTransferable > xDataObj;
+ {
+ SolarMutexReleaser aReleaser;
+ xDataObj = xClipboard->getContents();
+ }
+ if ( xDataObj.is() )
+ {
+ datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ bData = xDataObj->isDataFlavorSupported( aFlavor );
+ }
+ }
+ bEnablePaste = bData;
+ }
+
+ pPopup->EnableItem(pPopup->GetItemId(u"cut"), bEnableCut);
+ pPopup->EnableItem(pPopup->GetItemId(u"copy"), bEnableCopy);
+ pPopup->EnableItem(pPopup->GetItemId(u"delete"), bEnableDelete);
+ pPopup->EnableItem(pPopup->GetItemId(u"paste"), bEnablePaste);
+ pPopup->SetItemText(pPopup->GetItemId(u"specialchar"),
+ BuilderUtils::convertMnemonicMarkup(VclResId(STR_SPECIAL_CHARACTER_MENU_ENTRY)));
+ pPopup->EnableItem(pPopup->GetItemId(u"specialchar"), bEnableSpecialChar);
+ pPopup->EnableItem(
+ pPopup->GetItemId(u"undo"),
+ std::u16string_view(maUndoText) != std::u16string_view(maText));
+ bool bAllSelected = maSelection.Min() == 0 && maSelection.Max() == maText.getLength();
+ pPopup->EnableItem(pPopup->GetItemId(u"selectall"), !bAllSelected);
+ pPopup->ShowItem(pPopup->GetItemId(u"specialchar"), pImplFncGetSpecialChars != nullptr);
+
+ mbActivePopup = true;
+ Selection aSaveSel = GetSelection(); // if someone changes selection in Get/LoseFocus, e.g. URL bar
+ Point aPos = rCEvt.GetMousePosPixel();
+ if ( !rCEvt.IsMouseEvent() )
+ {
+ // Show menu eventually centered in selection
+ Size aSize = GetOutputSizePixel();
+ aPos = Point( aSize.Width()/2, aSize.Height()/2 );
+ }
+ sal_uInt16 n = pPopup->Execute( this, aPos );
+ SetSelection( aSaveSel );
+ OUString sCommand = pPopup->GetItemIdent(n);
+ if (sCommand == "undo")
+ {
+ Undo();
+ Modify();
+ }
+ else if (sCommand == "cut")
+ {
+ Cut();
+ Modify();
+ }
+ else if (sCommand == "copy")
+ {
+ Copy();
+ }
+ else if (sCommand == "paste")
+ {
+ Paste();
+ Modify();
+ }
+ else if (sCommand == "delete")
+ {
+ DeleteSelected();
+ Modify();
+ }
+ else if (sCommand == "selectall")
+ {
+ ImplSetSelection( Selection( 0, maText.getLength() ) );
+ }
+ else if (sCommand == "specialchar" && pImplFncGetSpecialChars)
+ {
+ OUString aChars = pImplFncGetSpecialChars(GetFrameWeld(), GetFont());
+ if (!isDisposed()) // destroyed while the insert special character dialog was still open
+ {
+ SetSelection( aSaveSel );
+ if (!aChars.isEmpty())
+ {
+ ImplInsertText( aChars );
+ Modify();
+ }
+ }
+ }
+ pPopup.clear();
+ mbActivePopup = false;
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
+ {
+ DeleteSelected();
+ sal_Int32 nPos = maSelection.Max();
+ mpIMEInfos.reset(new Impl_IMEInfos( nPos, maText.copy(nPos).makeStringAndClear() ));
+ mpIMEInfos->bWasCursorOverwrite = !IsInsertMode();
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
+ {
+ bool bInsertMode = !mpIMEInfos->bWasCursorOverwrite;
+ mpIMEInfos.reset();
+
+ SetInsertMode(bInsertMode);
+ Modify();
+
+ Invalidate();
+
+ // #i25161# call auto complete handler for ext text commit also
+ if (maAutocompleteHdl.IsSet())
+ {
+ if ( (maSelection.Min() == maSelection.Max()) && (maSelection.Min() == maText.getLength()) )
+ {
+ maAutocompleteHdl.Call(*this);
+ }
+ }
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput )
+ {
+ const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData();
+
+ maText.remove( mpIMEInfos->nPos, mpIMEInfos->nLen );
+ maText.insert( mpIMEInfos->nPos, pData->GetText() );
+ if ( mpIMEInfos->bWasCursorOverwrite )
+ {
+ const sal_Int32 nOldIMETextLen = mpIMEInfos->nLen;
+ const sal_Int32 nNewIMETextLen = pData->GetText().getLength();
+ if ( ( nOldIMETextLen > nNewIMETextLen ) &&
+ ( nNewIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
+ {
+ // restore old characters
+ const sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen;
+ maText.insert( mpIMEInfos->nPos + nNewIMETextLen, mpIMEInfos->aOldTextAfterStartPos.subView( nNewIMETextLen, nRestore ) );
+ }
+ else if ( ( nOldIMETextLen < nNewIMETextLen ) &&
+ ( nOldIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
+ {
+ const sal_Int32 nOverwrite = ( nNewIMETextLen > mpIMEInfos->aOldTextAfterStartPos.getLength()
+ ? mpIMEInfos->aOldTextAfterStartPos.getLength() : nNewIMETextLen ) - nOldIMETextLen;
+ maText.remove( mpIMEInfos->nPos + nNewIMETextLen, nOverwrite );
+ }
+ }
+
+ if ( pData->GetTextAttr() )
+ {
+ mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() );
+ mpIMEInfos->bCursor = pData->IsCursorVisible();
+ }
+ else
+ {
+ mpIMEInfos->DestroyAttribs();
+ }
+
+ ImplAlignAndPaint();
+ sal_Int32 nCursorPos = mpIMEInfos->nPos + pData->GetCursorPos();
+ SetSelection( Selection( nCursorPos, nCursorPos ) );
+ SetInsertMode( !pData->IsCursorOverwrite() );
+
+ if ( pData->IsCursorVisible() )
+ GetCursor()->Show();
+ else
+ GetCursor()->Hide();
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
+ {
+ if ( mpIMEInfos )
+ {
+ sal_Int32 nCursorPos = GetSelection().Max();
+ SetCursorRect( nullptr, GetTextWidth( maText.toString(), nCursorPos, mpIMEInfos->nPos+mpIMEInfos->nLen-nCursorPos ) );
+ }
+ else
+ {
+ SetCursorRect();
+ }
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::SelectionChange )
+ {
+ const CommandSelectionChangeData *pData = rCEvt.GetSelectionChangeData();
+ Selection aSelection( pData->GetStart(), pData->GetEnd() );
+ SetSelection(aSelection);
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::QueryCharPosition )
+ {
+ if (mpIMEInfos && mpIMEInfos->nLen > 0)
+ {
+ OUString aText = ImplGetText();
+ KernArray aDX;
+ GetOutDev()->GetCaretPositions(aText, aDX, 0, aText.getLength());
+
+ tools::Long nTH = GetTextHeight();
+ Point aPos( mnXOffset, ImplGetTextYPosition() );
+
+ std::vector<tools::Rectangle> aRects(mpIMEInfos->nLen);
+ for ( int nIndex = 0; nIndex < mpIMEInfos->nLen; ++nIndex )
+ {
+ tools::Rectangle aRect( aPos, Size( 10, nTH ) );
+ aRect.SetLeft( aDX[2*(nIndex+mpIMEInfos->nPos)] + mnXOffset + ImplGetExtraXOffset() );
+ aRects[ nIndex ] = aRect;
+ }
+ SetCompositionCharRect(aRects.data(), mpIMEInfos->nLen);
+ }
+ }
+ else
+ Control::Command( rCEvt );
+}
+
+void Edit::StateChanged( StateChangedType nType )
+{
+ if (nType == StateChangedType::InitShow)
+ {
+ if (!mpSubEdit)
+ {
+ mnXOffset = 0; // if GrabFocus before while size was still wrong
+ ImplAlign();
+ if (!mpSubEdit)
+ ImplShowCursor(false);
+ Invalidate();
+ }
+ }
+ else if (nType == StateChangedType::Enable)
+ {
+ if (!mpSubEdit)
+ {
+ // change text color only
+ ImplInvalidateOrRepaint();
+ }
+ }
+ else if (nType == StateChangedType::Style || nType == StateChangedType::Mirroring)
+ {
+ WinBits nStyle = GetStyle();
+ if (nType == StateChangedType::Style)
+ {
+ nStyle = ImplInitStyle(GetStyle());
+ SetStyle(nStyle);
+ }
+
+ sal_uInt16 nOldAlign = mnAlign;
+ mnAlign = EDIT_ALIGN_LEFT;
+
+ // hack: right align until keyinput and cursor travelling works
+ // edits are always RTL disabled
+ // however the parent edits contain the correct setting
+ if (mbIsSubEdit && GetParent()->IsRTLEnabled())
+ {
+ if (GetParent()->GetStyle() & WB_LEFT)
+ mnAlign = EDIT_ALIGN_RIGHT;
+ if (nType == StateChangedType::Mirroring)
+ GetOutDev()->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft);
+ }
+ else if (mbIsSubEdit && !GetParent()->IsRTLEnabled())
+ {
+ if (nType == StateChangedType::Mirroring)
+ GetOutDev()->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::TextOriginLeft);
+ }
+
+ if (nStyle & WB_RIGHT)
+ mnAlign = EDIT_ALIGN_RIGHT;
+ else if (nStyle & WB_CENTER)
+ mnAlign = EDIT_ALIGN_CENTER;
+ if (!maText.isEmpty() && (mnAlign != nOldAlign))
+ {
+ ImplAlign();
+ Invalidate();
+ }
+
+ }
+ else if ((nType == StateChangedType::Zoom) || (nType == StateChangedType::ControlFont))
+ {
+ if (!mpSubEdit)
+ {
+ ApplySettings(*GetOutDev());
+ ImplShowCursor();
+ Invalidate();
+ }
+ }
+ else if ((nType == StateChangedType::ControlForeground) || (nType == StateChangedType::ControlBackground))
+ {
+ if (!mpSubEdit)
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ }
+
+ Control::StateChanged(nType);
+}
+
+void Edit::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ if ( !mpSubEdit )
+ {
+ ApplySettings(*GetOutDev());
+ ImplShowCursor();
+ Invalidate();
+ }
+ }
+
+ Control::DataChanged( rDCEvt );
+}
+
+void Edit::ImplShowDDCursor()
+{
+ if (!mpDDInfo->bVisCursor)
+ {
+ tools::Long nTextWidth = GetTextWidth( maText.toString(), 0, mpDDInfo->nDropPos );
+ tools::Long nTextHeight = GetTextHeight();
+ tools::Rectangle aCursorRect( Point( nTextWidth + mnXOffset, (GetOutDev()->GetOutputSize().Height()-nTextHeight)/2 ), Size( 2, nTextHeight ) );
+ mpDDInfo->aCursor.SetWindow( this );
+ mpDDInfo->aCursor.SetPos( aCursorRect.TopLeft() );
+ mpDDInfo->aCursor.SetSize( aCursorRect.GetSize() );
+ mpDDInfo->aCursor.Show();
+ mpDDInfo->bVisCursor = true;
+ }
+}
+
+void Edit::ImplHideDDCursor()
+{
+ if ( mpDDInfo && mpDDInfo->bVisCursor )
+ {
+ mpDDInfo->aCursor.Hide();
+ mpDDInfo->bVisCursor = false;
+ }
+}
+
+TextFilter::TextFilter(OUString _aForbiddenChars)
+ : sForbiddenChars(std::move(_aForbiddenChars))
+{
+}
+
+TextFilter::~TextFilter()
+{
+}
+
+OUString TextFilter::filter(const OUString &rText)
+{
+ OUString sTemp(rText);
+ for (sal_Int32 i = 0; i < sForbiddenChars.getLength(); ++i)
+ {
+ sTemp = sTemp.replaceAll(OUStringChar(sForbiddenChars[i]), "");
+ }
+ return sTemp;
+}
+
+void Edit::filterText()
+{
+ Selection aSel = GetSelection();
+ const OUString sOrig = GetText();
+ const OUString sNew = mpFilterText->filter(GetText());
+ if (sOrig != sNew)
+ {
+ sal_Int32 nDiff = sOrig.getLength() - sNew.getLength();
+ if (nDiff)
+ {
+ aSel.setMin(aSel.getMin() - nDiff);
+ aSel.setMax(aSel.getMin());
+ }
+ SetText(sNew);
+ SetSelection(aSel);
+ }
+}
+
+void Edit::Modify()
+{
+ if (mpFilterText)
+ filterText();
+
+ if ( mbIsSubEdit )
+ {
+ static_cast<Edit*>(GetParent())->Modify();
+ }
+ else
+ {
+ if ( ImplCallEventListenersAndHandler( VclEventId::EditModify, [this] () { maModifyHdl.Call(*this); } ) )
+ // have been destroyed while calling into the handlers
+ return;
+
+ // #i13677# notify edit listeners about caret position change
+ CallEventListeners( VclEventId::EditCaretChanged );
+ // FIXME: this is currently only on macOS
+ // check for other platforms that need similar handling
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
+ IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Editbox, ControlPart::Entire ) )
+ {
+ ImplInvalidateOutermostBorder( this );
+ }
+ }
+}
+
+void Edit::SetEchoChar( sal_Unicode c )
+{
+ mcEchoChar = c;
+ if ( mpSubEdit )
+ mpSubEdit->SetEchoChar( c );
+}
+
+void Edit::SetReadOnly( bool bReadOnly )
+{
+ if ( mbReadOnly != bReadOnly )
+ {
+ mbReadOnly = bReadOnly;
+ if ( mpSubEdit )
+ mpSubEdit->SetReadOnly( bReadOnly );
+
+ CompatStateChanged( StateChangedType::ReadOnly );
+ }
+}
+
+void Edit::SetInsertMode( bool bInsert )
+{
+ if ( bInsert != mbInsertMode )
+ {
+ mbInsertMode = bInsert;
+ if ( mpSubEdit )
+ mpSubEdit->SetInsertMode( bInsert );
+ else
+ ImplShowCursor();
+ }
+}
+
+bool Edit::IsInsertMode() const
+{
+ if ( mpSubEdit )
+ return mpSubEdit->IsInsertMode();
+ else
+ return mbInsertMode;
+}
+
+void Edit::SetMaxTextLen(sal_Int32 nMaxLen)
+{
+ mnMaxTextLen = nMaxLen > 0 ? nMaxLen : EDIT_NOLIMIT;
+
+ if ( mpSubEdit )
+ mpSubEdit->SetMaxTextLen( mnMaxTextLen );
+ else
+ {
+ if ( maText.getLength() > mnMaxTextLen )
+ ImplDelete( Selection( mnMaxTextLen, maText.getLength() ), EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+ }
+}
+
+void Edit::SetSelection( const Selection& rSelection )
+{
+ // If the selection was changed from outside, e.g. by MouseButtonDown, don't call Tracking()
+ // directly afterwards which would change the selection again
+ if ( IsTracking() )
+ EndTracking();
+ else if ( mpSubEdit && mpSubEdit->IsTracking() )
+ mpSubEdit->EndTracking();
+
+ ImplSetSelection( rSelection );
+}
+
+void Edit::ImplSetSelection( const Selection& rSelection, bool bPaint )
+{
+ if ( mpSubEdit )
+ mpSubEdit->ImplSetSelection( rSelection );
+ else
+ {
+ if ( rSelection != maSelection )
+ {
+ Selection aOld( maSelection );
+ Selection aNew( rSelection );
+
+ if ( aNew.Min() > maText.getLength() )
+ aNew.Min() = maText.getLength();
+ if ( aNew.Max() > maText.getLength() )
+ aNew.Max() = maText.getLength();
+ if ( aNew.Min() < 0 )
+ aNew.Min() = 0;
+ if ( aNew.Max() < 0 )
+ aNew.Max() = 0;
+
+ if ( aNew != maSelection )
+ {
+ ImplClearLayoutData();
+ Selection aTemp = maSelection;
+ maSelection = aNew;
+
+ if ( bPaint && ( aOld.Len() || aNew.Len() || IsPaintTransparent() ) )
+ ImplInvalidateOrRepaint();
+ ImplShowCursor();
+
+ bool bCaret = false, bSelection = false;
+ tools::Long nB=aNew.Max(), nA=aNew.Min(),oB=aTemp.Max(), oA=aTemp.Min();
+ tools::Long nGap = nB-nA, oGap = oB-oA;
+ if (nB != oB)
+ bCaret = true;
+ if (nGap != 0 || oGap != 0)
+ bSelection = true;
+
+ if (bSelection)
+ {
+ if ( mbIsSubEdit )
+ static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditSelectionChanged );
+ else
+ CallEventListeners( VclEventId::EditSelectionChanged );
+ }
+
+ if (bCaret)
+ {
+ if ( mbIsSubEdit )
+ static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditCaretChanged );
+ else
+ CallEventListeners( VclEventId::EditCaretChanged );
+ }
+
+ // #103511# notify combobox listeners of deselection
+ if( !maSelection && GetParent() && GetParent()->GetType() == WindowType::COMBOBOX )
+ static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::ComboboxDeselect );
+ }
+ }
+ }
+}
+
+const Selection& Edit::GetSelection() const
+{
+ if ( mpSubEdit )
+ return mpSubEdit->GetSelection();
+ else
+ return maSelection;
+}
+
+void Edit::ReplaceSelected( const OUString& rStr )
+{
+ if ( mpSubEdit )
+ mpSubEdit->ReplaceSelected( rStr );
+ else
+ ImplInsertText( rStr );
+}
+
+void Edit::DeleteSelected()
+{
+ if ( mpSubEdit )
+ mpSubEdit->DeleteSelected();
+ else
+ {
+ if ( maSelection.Len() )
+ ImplDelete( maSelection, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+ }
+}
+
+OUString Edit::GetSelected() const
+{
+ if ( mpSubEdit )
+ return mpSubEdit->GetSelected();
+ else
+ {
+ Selection aSelection( maSelection );
+ aSelection.Normalize();
+ return OUString( maText.getStr() + aSelection.Min(), aSelection.Len() );
+ }
+}
+
+void Edit::Cut()
+{
+ if ( !mbPassword )
+ {
+ Copy();
+ ReplaceSelected( OUString() );
+ }
+}
+
+void Edit::Copy()
+{
+ if ( !mbPassword )
+ {
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetClipboard());
+ ImplCopy( aClipboard );
+ }
+}
+
+void Edit::Paste()
+{
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetClipboard());
+ ImplPaste( aClipboard );
+}
+
+void Edit::Undo()
+{
+ if ( mpSubEdit )
+ mpSubEdit->Undo();
+ else
+ {
+ const OUString aText( maText.toString() );
+ ImplDelete( Selection( 0, aText.getLength() ), EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+ ImplInsertText( maUndoText );
+ ImplSetSelection( Selection( 0, maUndoText.getLength() ) );
+ maUndoText = aText;
+ }
+}
+
+void Edit::SetText( const OUString& rStr )
+{
+ if ( mpSubEdit )
+ mpSubEdit->SetText( rStr ); // not directly ImplSetText if SetText overridden
+ else
+ {
+ Selection aNewSel( 0, 0 ); // prevent scrolling
+ ImplSetText( rStr, &aNewSel );
+ }
+}
+
+void Edit::SetText( const OUString& rStr, const Selection& rSelection )
+{
+ if ( mpSubEdit )
+ mpSubEdit->SetText( rStr, rSelection );
+ else
+ ImplSetText( rStr, &rSelection );
+}
+
+OUString Edit::GetText() const
+{
+ if ( mpSubEdit )
+ return mpSubEdit->GetText();
+ else
+ return maText.toString();
+}
+
+void Edit::SetCursorAtLast(){
+ ImplSetCursorPos( GetText().getLength(), false );
+}
+
+void Edit::SetPlaceholderText( const OUString& rStr )
+{
+ if ( mpSubEdit )
+ mpSubEdit->SetPlaceholderText( rStr );
+ else if ( maPlaceholderText != rStr )
+ {
+ maPlaceholderText = rStr;
+ if ( GetText().isEmpty() )
+ Invalidate();
+ }
+}
+
+void Edit::SetModifyFlag()
+{
+}
+
+void Edit::SetSubEdit(Edit* pEdit)
+{
+ mpSubEdit.disposeAndClear();
+ mpSubEdit.set(pEdit);
+
+ if (mpSubEdit)
+ {
+ SetPointer(PointerStyle::Arrow); // Only SubEdit has the BEAM...
+ mpSubEdit->mbIsSubEdit = true;
+
+ mpSubEdit->SetReadOnly(mbReadOnly);
+ mpSubEdit->maAutocompleteHdl = maAutocompleteHdl;
+ }
+}
+
+Size Edit::CalcMinimumSizeForText(const OUString &rString) const
+{
+ ControlType eCtrlType = ImplGetNativeControlType();
+
+ Size aSize;
+ if (mnWidthInChars != -1)
+ {
+ //CalcSize calls CalcWindowSize, but we will call that also in this
+ //function, so undo the first one with CalcOutputSize
+ aSize = CalcOutputSize(CalcSize(mnWidthInChars));
+ }
+ else
+ {
+ OUString aString;
+ if (mnMaxWidthChars != -1 && mnMaxWidthChars < rString.getLength())
+ aString = rString.copy(0, mnMaxWidthChars);
+ else
+ aString = rString;
+
+ aSize.setHeight( GetTextHeight() );
+ aSize.setWidth( GetTextWidth(aString) );
+ aSize.AdjustWidth(ImplGetExtraXOffset() * 2 );
+
+ // do not create edit fields in which one cannot enter anything
+ // a default minimum width should exist for at least 3 characters
+
+ //CalcSize calls CalcWindowSize, but we will call that also in this
+ //function, so undo the first one with CalcOutputSize
+ Size aMinSize(CalcOutputSize(CalcSize(3)));
+ if (aSize.Width() < aMinSize.Width())
+ aSize.setWidth( aMinSize.Width() );
+ }
+
+ aSize.AdjustHeight(ImplGetExtraYOffset() * 2 );
+
+ aSize = CalcWindowSize( aSize );
+
+ // ask NWF what if it has an opinion, too
+ ImplControlValue aControlValue;
+ tools::Rectangle aRect( Point( 0, 0 ), aSize );
+ tools::Rectangle aContent, aBound;
+ if (GetNativeControlRegion(eCtrlType, ControlPart::Entire, aRect, ControlState::NONE,
+ aControlValue, aBound, aContent))
+ {
+ if (aBound.GetHeight() > aSize.Height())
+ aSize.setHeight( aBound.GetHeight() );
+ }
+ return aSize;
+}
+
+Size Edit::CalcMinimumSize() const
+{
+ return CalcMinimumSizeForText(GetText());
+}
+
+Size Edit::GetOptimalSize() const
+{
+ return CalcMinimumSize();
+}
+
+Size Edit::CalcSize(sal_Int32 nChars) const
+{
+ // width for N characters, independent from content.
+ // works only correct for fixed fonts, average otherwise
+ float fUnitWidth = std::max(approximate_char_width(), approximate_digit_width());
+ Size aSz(fUnitWidth * nChars, GetTextHeight());
+ aSz.AdjustWidth(ImplGetExtraXOffset() * 2 );
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+sal_Int32 Edit::GetMaxVisChars() const
+{
+ const vcl::Window* pW = mpSubEdit ? mpSubEdit : this;
+ sal_Int32 nOutWidth = pW->GetOutputSizePixel().Width();
+ float fUnitWidth = std::max(approximate_char_width(), approximate_digit_width());
+ return nOutWidth / fUnitWidth;
+}
+
+namespace vcl
+{
+ void SetGetSpecialCharsFunction( FncGetSpecialChars fn )
+ {
+ pImplFncGetSpecialChars = fn;
+ }
+
+ FncGetSpecialChars GetGetSpecialCharsFunction()
+ {
+ return pImplFncGetSpecialChars;
+ }
+}
+
+VclPtr<PopupMenu> Edit::CreatePopupMenu()
+{
+ if (!mpUIBuilder)
+ mpUIBuilder.reset(new VclBuilder(nullptr, AllSettings::GetUIRootDir(), "vcl/ui/editmenu.ui", ""));
+ VclPtr<PopupMenu> pPopup = mpUIBuilder->get_menu(u"menu");
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ if (rStyleSettings.GetHideDisabledMenuItems())
+ pPopup->SetMenuFlags( MenuFlags::HideDisabledEntries );
+ else
+ pPopup->SetMenuFlags ( MenuFlags::AlwaysShowDisabledEntries );
+ if (rStyleSettings.GetContextMenuShortcuts())
+ {
+ pPopup->SetAccelKey(pPopup->GetItemId(u"undo"), vcl::KeyCode( KeyFuncType::UNDO));
+ pPopup->SetAccelKey(pPopup->GetItemId(u"cut"), vcl::KeyCode( KeyFuncType::CUT));
+ pPopup->SetAccelKey(pPopup->GetItemId(u"copy"), vcl::KeyCode( KeyFuncType::COPY));
+ pPopup->SetAccelKey(pPopup->GetItemId(u"paste"), vcl::KeyCode( KeyFuncType::PASTE));
+ pPopup->SetAccelKey(pPopup->GetItemId(u"delete"), vcl::KeyCode( KeyFuncType::DELETE));
+ pPopup->SetAccelKey(pPopup->GetItemId(u"selectall"), vcl::KeyCode( KEY_A, false, true, false, false));
+ pPopup->SetAccelKey(pPopup->GetItemId(u"specialchar"), vcl::KeyCode( KEY_S, true, true, false, false));
+ }
+ return pPopup;
+}
+
+// css::datatransfer::dnd::XDragGestureListener
+void Edit::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE )
+{
+ SolarMutexGuard aVclGuard;
+
+ if ( !(!IsTracking() && maSelection.Len() &&
+ !mbPassword && (!mpDDInfo || !mpDDInfo->bStarterOfDD)) ) // no repeated D&D
+ return;
+
+ Selection aSel( maSelection );
+ aSel.Normalize();
+
+ // only if mouse in the selection...
+ Point aMousePos( rDGE.DragOriginX, rDGE.DragOriginY );
+ sal_Int32 nCharPos = ImplGetCharPos( aMousePos );
+ if ( (nCharPos < aSel.Min()) || (nCharPos >= aSel.Max()) )
+ return;
+
+ if ( !mpDDInfo )
+ mpDDInfo.reset(new DDInfo);
+
+ mpDDInfo->bStarterOfDD = true;
+ mpDDInfo->aDndStartSel = aSel;
+
+ if ( IsTracking() )
+ EndTracking(); // before D&D disable tracking
+
+ rtl::Reference<vcl::unohelper::TextDataObject> pDataObj = new vcl::unohelper::TextDataObject( GetSelected() );
+ sal_Int8 nActions = datatransfer::dnd::DNDConstants::ACTION_COPY;
+ if ( !IsReadOnly() )
+ nActions |= datatransfer::dnd::DNDConstants::ACTION_MOVE;
+ rDGE.DragSource->startDrag( rDGE, nActions, 0 /*cursor*/, 0 /*image*/, pDataObj, mxDnDListener );
+ if ( GetCursor() )
+ GetCursor()->Hide();
+}
+
+// css::datatransfer::dnd::XDragSourceListener
+void Edit::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& rDSDE )
+{
+ SolarMutexGuard aVclGuard;
+
+ if (rDSDE.DropSuccess && (rDSDE.DropAction & datatransfer::dnd::DNDConstants::ACTION_MOVE) && mpDDInfo)
+ {
+ Selection aSel( mpDDInfo->aDndStartSel );
+ if ( mpDDInfo->bDroppedInMe )
+ {
+ if ( aSel.Max() > mpDDInfo->nDropPos )
+ {
+ tools::Long nLen = aSel.Len();
+ aSel.Min() += nLen;
+ aSel.Max() += nLen;
+ }
+ }
+ ImplDelete( aSel, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+ Modify();
+ }
+
+ ImplHideDDCursor();
+ mpDDInfo.reset();
+}
+
+// css::datatransfer::dnd::XDropTargetListener
+void Edit::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE )
+{
+ SolarMutexGuard aVclGuard;
+
+ bool bChanges = false;
+ if ( !mbReadOnly && mpDDInfo )
+ {
+ ImplHideDDCursor();
+
+ Selection aSel( maSelection );
+ aSel.Normalize();
+
+ if ( aSel.Len() && !mpDDInfo->bStarterOfDD )
+ ImplDelete( aSel, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
+
+ mpDDInfo->bDroppedInMe = true;
+
+ aSel.Min() = mpDDInfo->nDropPos;
+ aSel.Max() = mpDDInfo->nDropPos;
+ ImplSetSelection( aSel );
+
+ uno::Reference< datatransfer::XTransferable > xDataObj = rDTDE.Transferable;
+ if ( xDataObj.is() )
+ {
+ datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ if ( xDataObj->isDataFlavorSupported( aFlavor ) )
+ {
+ uno::Any aData = xDataObj->getTransferData( aFlavor );
+ OUString aText;
+ aData >>= aText;
+ ImplInsertText( aText );
+ bChanges = true;
+ Modify();
+ }
+ }
+
+ if ( !mpDDInfo->bStarterOfDD )
+ {
+ mpDDInfo.reset();
+ }
+ }
+
+ rDTDE.Context->dropComplete( bChanges );
+}
+
+void Edit::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& rDTDE )
+{
+ if ( !mpDDInfo )
+ {
+ mpDDInfo.reset(new DDInfo);
+ }
+ // search for string data type
+ const Sequence< css::datatransfer::DataFlavor >& rFlavors( rDTDE.SupportedDataFlavors );
+ mpDDInfo->bIsStringSupported = std::any_of(rFlavors.begin(), rFlavors.end(),
+ [](const css::datatransfer::DataFlavor& rFlavor) {
+ sal_Int32 nIndex = 0;
+ const std::u16string_view aMimetype = o3tl::getToken(rFlavor.MimeType, 0, ';', nIndex );
+ return aMimetype == u"text/plain";
+ });
+}
+
+void Edit::dragExit( const css::datatransfer::dnd::DropTargetEvent& )
+{
+ SolarMutexGuard aVclGuard;
+
+ ImplHideDDCursor();
+}
+
+void Edit::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& rDTDE )
+{
+ SolarMutexGuard aVclGuard;
+
+ Point aMousePos( rDTDE.LocationX, rDTDE.LocationY );
+
+ sal_Int32 nPrevDropPos = mpDDInfo->nDropPos;
+ mpDDInfo->nDropPos = ImplGetCharPos( aMousePos );
+
+ /*
+ Size aOutSize = GetOutputSizePixel();
+ if ( ( aMousePos.X() < 0 ) || ( aMousePos.X() > aOutSize.Width() ) )
+ {
+ // Scroll?
+ // No, I will not receive events in this case...
+ }
+ */
+
+ Selection aSel( maSelection );
+ aSel.Normalize();
+
+ // Don't accept drop in selection or read-only field...
+ if ( IsReadOnly() || aSel.Contains( mpDDInfo->nDropPos ) || ! mpDDInfo->bIsStringSupported )
+ {
+ ImplHideDDCursor();
+ rDTDE.Context->rejectDrag();
+ }
+ else
+ {
+ // draw the old cursor away...
+ if ( !mpDDInfo->bVisCursor || ( nPrevDropPos != mpDDInfo->nDropPos ) )
+ {
+ ImplHideDDCursor();
+ ImplShowDDCursor();
+ }
+ rDTDE.Context->acceptDrag( rDTDE.DropAction );
+ }
+}
+
+OUString Edit::GetSurroundingText() const
+{
+ if (mpSubEdit)
+ return mpSubEdit->GetSurroundingText();
+ return maText.toString();
+}
+
+Selection Edit::GetSurroundingTextSelection() const
+{
+ return GetSelection();
+}
+
+bool Edit::DeleteSurroundingText(const Selection& rSelection)
+{
+ SetSelection(rSelection);
+ DeleteSelected();
+ // maybe we should update mpIMEInfos here
+ return true;
+}
+
+FactoryFunction Edit::GetUITestFactory() const
+{
+ return EditUIObject::create;
+}
+
+
+void Edit::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+
+ if (!maPlaceholderText.isEmpty())
+ rJsonWriter.put("placeholder", maPlaceholderText);
+
+ if (IsPassword())
+ rJsonWriter.put("password", true);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/field.cxx b/vcl/source/control/field.cxx
new file mode 100644
index 0000000000..d85b235b0e
--- /dev/null
+++ b/vcl/source/control/field.cxx
@@ -0,0 +1,1882 @@
+/* -*- 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 <cmath>
+#include <string_view>
+
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+
+#include <comphelper/string.hxx>
+#include <tools/UnitConversion.hxx>
+
+#include <vcl/builder.hxx>
+#include <vcl/fieldvalues.hxx>
+#include <vcl/toolkit/field.hxx>
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/uitest/metricfielduiobject.hxx>
+
+#include <svdata.hxx>
+
+#include <i18nutil/unicode.hxx>
+
+#include <rtl/math.hxx>
+
+#include <unotools/localedatawrapper.hxx>
+#include <boost/property_tree/ptree.hpp>
+#include <tools/json_writer.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::comphelper;
+
+namespace
+{
+
+std::string FieldUnitToString(FieldUnit unit)
+{
+ switch(unit)
+ {
+ case FieldUnit::NONE:
+ return "";
+
+ case FieldUnit::MM:
+ return "mm";
+
+ case FieldUnit::CM:
+ return "cm";
+
+ case FieldUnit::M:
+ return "m";
+
+ case FieldUnit::KM:
+ return "km";
+
+ case FieldUnit::TWIP:
+ return "twip";
+
+ case FieldUnit::POINT:
+ return "point";
+
+ case FieldUnit::PICA:
+ return "pica";
+
+ case FieldUnit::INCH:
+ return "inch";
+
+ case FieldUnit::FOOT:
+ return "foot";
+
+ case FieldUnit::MILE:
+ return "mile";
+
+ case FieldUnit::CHAR:
+ return "char";
+
+ case FieldUnit::LINE:
+ return "line";
+
+ case FieldUnit::CUSTOM:
+ return "custom";
+
+ case FieldUnit::PERCENT:
+ return "percent";
+
+ case FieldUnit::MM_100TH:
+ return "mm100th";
+
+ case FieldUnit::PIXEL:
+ return "pixel";
+
+ case FieldUnit::DEGREE:
+ return "degree";
+
+ case FieldUnit::SECOND:
+ return "second";
+
+ case FieldUnit::MILLISECOND:
+ return "millisecond";
+ }
+
+ return "";
+}
+
+sal_Int64 ImplPower10( sal_uInt16 n )
+{
+ sal_uInt16 i;
+ sal_Int64 nValue = 1;
+
+ for ( i=0; i < n; i++ )
+ nValue *= 10;
+
+ return nValue;
+}
+
+bool ImplNumericProcessKeyInput( const KeyEvent& rKEvt,
+ bool bStrictFormat, bool bThousandSep,
+ const LocaleDataWrapper& rLocaleDataWrapper )
+{
+ if ( !bStrictFormat )
+ return false;
+ else
+ {
+ sal_Unicode cChar = rKEvt.GetCharCode();
+ sal_uInt16 nGroup = rKEvt.GetKeyCode().GetGroup();
+
+ return !((nGroup == KEYGROUP_FKEYS) ||
+ (nGroup == KEYGROUP_CURSOR) ||
+ (nGroup == KEYGROUP_MISC) ||
+ ((cChar >= '0') && (cChar <= '9')) ||
+ rLocaleDataWrapper.getNumDecimalSep() == OUStringChar(cChar) ||
+ (bThousandSep && rLocaleDataWrapper.getNumThousandSep() == OUStringChar(cChar)) ||
+ rLocaleDataWrapper.getNumDecimalSepAlt() == OUStringChar(cChar) ||
+ (cChar == '-'));
+ }
+}
+
+bool ImplNumericGetValue( const OUString& rStr, sal_Int64& rValue,
+ sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper,
+ bool bCurrency = false )
+{
+ OUString aStr = rStr;
+ OUStringBuffer aStr1, aStr2, aStrNum, aStrDenom;
+ bool bNegative = false;
+ bool bFrac = false;
+ sal_Int32 nDecPos, nFracDivPos;
+ sal_Int64 nValue;
+
+ // react on empty string
+ if ( rStr.isEmpty() )
+ return false;
+
+ // remove leading and trailing spaces
+ aStr = aStr.trim();
+
+
+ // find position of decimal point
+ nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSep() );
+ if (nDecPos < 0 && !rLocaleDataWrapper.getNumDecimalSepAlt().isEmpty())
+ nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSepAlt() );
+ // find position of fraction
+ nFracDivPos = aStr.indexOf( '/' );
+
+ // parse fractional strings
+ if (nFracDivPos > 0)
+ {
+ bFrac = true;
+ sal_Int32 nFracNumPos = aStr.lastIndexOf(' ', nFracDivPos);
+
+ // If in "a b/c" format.
+ if(nFracNumPos != -1 )
+ {
+ aStr1.append(aStr.subView(0, nFracNumPos));
+ aStrNum.append(aStr.subView(nFracNumPos+1, nFracDivPos-nFracNumPos-1));
+ aStrDenom.append(aStr.subView(nFracDivPos+1));
+ }
+ // "a/b" format, or not a fraction at all
+ else
+ {
+ aStrNum.append(aStr.subView(0, nFracDivPos));
+ aStrDenom.append(aStr.subView(nFracDivPos+1));
+ }
+
+ }
+ // parse decimal strings
+ else if ( nDecPos >= 0)
+ {
+ aStr1.append(aStr.subView(0, nDecPos));
+ aStr2.append(aStr.subView(nDecPos+1));
+ }
+ else
+ aStr1 = aStr;
+
+ // negative?
+ if ( bCurrency )
+ {
+ if ( aStr.startsWith("(") && aStr.endsWith(")") )
+ bNegative = true;
+ if ( !bNegative )
+ {
+ for (sal_Int32 i=0; i < aStr.getLength(); i++ )
+ {
+ if ( (aStr[i] >= '0') && (aStr[i] <= '9') )
+ break;
+ else if ( aStr[i] == '-' )
+ {
+ bNegative = true;
+ break;
+ }
+ }
+ }
+ if (!bNegative && !aStr.isEmpty())
+ {
+ sal_uInt16 nFormat = rLocaleDataWrapper.getCurrNegativeFormat();
+ if ( (nFormat == 3) || (nFormat == 6) || // $1- || 1-$
+ (nFormat == 7) || (nFormat == 10) ) // 1$- || 1 $-
+ {
+ for (sal_Int32 i = aStr.getLength()-1; i > 0; --i )
+ {
+ if ( (aStr[i] >= '0') && (aStr[i] <= '9') )
+ break;
+ else if ( aStr[i] == '-' )
+ {
+ bNegative = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ if ( !aStr1.isEmpty() && aStr1[0] == '-')
+ bNegative = true;
+ if ( !aStrNum.isEmpty() && aStrNum[0] == '-') // For non-mixed fractions
+ bNegative = true;
+ }
+
+ // remove all unwanted characters
+ // For whole number
+ for (sal_Int32 i=0; i < aStr1.getLength(); )
+ {
+ if ( (aStr1[i] >= '0') && (aStr1[i] <= '9') )
+ i++;
+ else
+ aStr1.remove( i, 1 );
+ }
+ // For decimal
+ if (!bFrac) {
+ for (sal_Int32 i=0; i < aStr2.getLength(); )
+ {
+ if ((aStr2[i] >= '0') && (aStr2[i] <= '9'))
+ ++i;
+ else
+ aStr2.remove(i, 1);
+ }
+ }
+ else {
+ // for numerator
+ for (sal_Int32 i=0; i < aStrNum.getLength(); )
+ {
+ if ((aStrNum[i] >= '0') && (aStrNum[i] <= '9'))
+ ++i;
+ else
+ aStrNum.remove(i, 1);
+ }
+ // for denominator
+ for (sal_Int32 i=0; i < aStrDenom.getLength(); )
+ {
+ if ((aStrDenom[i] >= '0') && (aStrDenom[i] <= '9'))
+ ++i;
+ else
+ aStrDenom.remove(i, 1);
+ }
+ }
+
+
+ if ( !bFrac && aStr1.isEmpty() && aStr2.isEmpty() )
+ return false;
+ else if ( bFrac && aStr1.isEmpty() && (aStrNum.isEmpty() || aStrDenom.isEmpty()) )
+ return false;
+
+ if ( aStr1.isEmpty() )
+ aStr1 = "0";
+ if ( bNegative )
+ aStr1.insert(0, "-");
+
+ // Convert fractional strings
+ if (bFrac) {
+ // Convert to fraction
+ sal_Int64 nWholeNum = o3tl::toInt64(aStr1);
+ aStr1.setLength(0);
+ sal_Int64 nNum = o3tl::toInt64(aStrNum);
+ sal_Int64 nDenom = o3tl::toInt64(aStrDenom);
+ if (nDenom == 0) return false; // Division by zero
+ double nFrac2Dec = nWholeNum + static_cast<double>(nNum)/nDenom; // Convert to double for floating point precision
+ OUStringBuffer aStrFrac(OUString::number(nFrac2Dec));
+ // Reconvert division result to string and parse
+ nDecPos = aStrFrac.indexOf('.');
+ if ( nDecPos >= 0)
+ {
+ aStr1.append(aStrFrac.getStr(), nDecPos);
+ aStr2.append(aStrFrac.getStr()+nDecPos+1);
+ }
+ else
+ aStr1 = aStrFrac;
+ }
+
+ // prune and round fraction
+ bool bRound = false;
+ if (aStr2.getLength() > nDecDigits)
+ {
+ if (aStr2[nDecDigits] >= '5')
+ bRound = true;
+ string::truncateToLength(aStr2, nDecDigits);
+ }
+ if (aStr2.getLength() < nDecDigits)
+ string::padToLength(aStr2, nDecDigits, '0');
+
+ aStr = aStr1 + aStr2;
+
+ // check range
+ nValue = aStr.toInt64();
+ if( nValue == 0 )
+ {
+ // check if string is equivalent to zero
+ sal_Int32 nIndex = bNegative ? 1 : 0;
+ while (nIndex < aStr.getLength() && aStr[nIndex] == '0')
+ ++nIndex;
+ if( nIndex < aStr.getLength() )
+ {
+ rValue = bNegative ? SAL_MIN_INT64 : SAL_MAX_INT64;
+ return true;
+ }
+ }
+ if (bRound)
+ {
+ if ( !bNegative )
+ nValue++;
+ else
+ nValue--;
+ }
+
+ rValue = nValue;
+
+ return true;
+}
+
+void ImplUpdateSeparatorString( OUString& io_rText,
+ std::u16string_view rOldDecSep, std::u16string_view rNewDecSep,
+ std::u16string_view rOldThSep, std::u16string_view rNewThSep )
+{
+ OUStringBuffer aBuf( io_rText.getLength() );
+ sal_Int32 nIndexDec = 0, nIndexTh = 0, nIndex = 0;
+
+ const sal_Unicode* pBuffer = io_rText.getStr();
+ while( nIndex != -1 )
+ {
+ nIndexDec = io_rText.indexOf( rOldDecSep, nIndex );
+ nIndexTh = io_rText.indexOf( rOldThSep, nIndex );
+ if( (nIndexTh != -1 && nIndexDec != -1 && nIndexTh < nIndexDec )
+ || (nIndexTh != -1 && nIndexDec == -1)
+ )
+ {
+ aBuf.append( OUString::Concat(std::u16string_view(pBuffer + nIndex, nIndexTh - nIndex )) + rNewThSep );
+ nIndex = nIndexTh + rOldThSep.size();
+ }
+ else if( nIndexDec != -1 )
+ {
+ aBuf.append( OUString::Concat(std::u16string_view(pBuffer + nIndex, nIndexDec - nIndex )) + rNewDecSep );
+ nIndex = nIndexDec + rOldDecSep.size();
+ }
+ else
+ {
+ aBuf.append( pBuffer + nIndex );
+ nIndex = -1;
+ }
+ }
+
+ io_rText = aBuf.makeStringAndClear();
+}
+
+void ImplUpdateSeparators( std::u16string_view rOldDecSep, std::u16string_view rNewDecSep,
+ std::u16string_view rOldThSep, std::u16string_view rNewThSep,
+ Edit* pEdit )
+{
+ bool bChangeDec = (rOldDecSep != rNewDecSep);
+ bool bChangeTh = (rOldThSep != rNewThSep );
+
+ if( !(bChangeDec || bChangeTh) )
+ return;
+
+ bool bUpdateMode = pEdit->IsUpdateMode();
+ pEdit->SetUpdateMode( false );
+ OUString aText = pEdit->GetText();
+ ImplUpdateSeparatorString( aText, rOldDecSep, rNewDecSep, rOldThSep, rNewThSep );
+ pEdit->SetText( aText );
+
+ ComboBox* pCombo = dynamic_cast<ComboBox*>(pEdit);
+ if( pCombo )
+ {
+ // update box entries
+ sal_Int32 nEntryCount = pCombo->GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; i++ )
+ {
+ aText = pCombo->GetEntry( i );
+ void* pEntryData = pCombo->GetEntryData( i );
+ ImplUpdateSeparatorString( aText, rOldDecSep, rNewDecSep, rOldThSep, rNewThSep );
+ pCombo->RemoveEntryAt(i);
+ pCombo->InsertEntry( aText, i );
+ pCombo->SetEntryData( i, pEntryData );
+ }
+ }
+ if( bUpdateMode )
+ pEdit->SetUpdateMode( bUpdateMode );
+}
+
+} // namespace
+
+FormatterBase::FormatterBase(Edit* pField)
+{
+ mpField = pField;
+ mpLocaleDataWrapper = nullptr;
+ mbReformat = false;
+ mbStrictFormat = false;
+ mbEmptyFieldValue = false;
+ mbEmptyFieldValueEnabled = false;
+}
+
+FormatterBase::~FormatterBase()
+{
+}
+
+LocaleDataWrapper& FormatterBase::ImplGetLocaleDataWrapper() const
+{
+ if ( !mpLocaleDataWrapper )
+ {
+ mpLocaleDataWrapper.reset( new LocaleDataWrapper( GetLanguageTag() ) );
+ }
+ return *mpLocaleDataWrapper;
+}
+
+/** reset the LocaleDataWrapper when the language tag changes */
+void FormatterBase::ImplResetLocaleDataWrapper() const
+{
+ // just get rid of, the next time it is requested, it will get loaded with the right
+ // language tag
+ mpLocaleDataWrapper.reset();
+}
+
+const LocaleDataWrapper& FormatterBase::GetLocaleDataWrapper() const
+{
+ return ImplGetLocaleDataWrapper();
+}
+
+void FormatterBase::Reformat()
+{
+}
+
+void FormatterBase::ReformatAll()
+{
+ Reformat();
+};
+
+void FormatterBase::SetStrictFormat( bool bStrict )
+{
+ if ( bStrict != mbStrictFormat )
+ {
+ mbStrictFormat = bStrict;
+ if ( mbStrictFormat )
+ ReformatAll();
+ }
+}
+
+const lang::Locale& FormatterBase::GetLocale() const
+{
+ if ( mpField )
+ return mpField->GetSettings().GetLanguageTag().getLocale();
+ else
+ return Application::GetSettings().GetLanguageTag().getLocale();
+}
+
+const LanguageTag& FormatterBase::GetLanguageTag() const
+{
+ if ( mpField )
+ return mpField->GetSettings().GetLanguageTag();
+ else
+ return Application::GetSettings().GetLanguageTag();
+}
+
+void FormatterBase::ImplSetText( const OUString& rText, Selection const * pNewSelection )
+{
+ if ( mpField )
+ {
+ if (pNewSelection)
+ mpField->SetText(rText, *pNewSelection);
+ else
+ {
+ Selection aSel = mpField->GetSelection();
+ aSel.Min() = aSel.Max();
+ mpField->SetText(rText, aSel);
+ }
+ MarkToBeReformatted( false );
+ }
+}
+
+void FormatterBase::SetEmptyFieldValue()
+{
+ if ( mpField )
+ mpField->SetText( OUString() );
+ mbEmptyFieldValue = true;
+}
+
+bool FormatterBase::IsEmptyFieldValue() const
+{
+ return (!mpField || mpField->GetText().isEmpty());
+}
+
+void NumericFormatter::FormatValue(Selection const * pNewSelection)
+{
+ mbFormatting = true;
+ ImplSetText(CreateFieldText(mnLastValue), pNewSelection);
+ mbFormatting = false;
+}
+
+void NumericFormatter::ImplNumericReformat()
+{
+ mnLastValue = GetValue();
+ FormatValue();
+}
+
+NumericFormatter::NumericFormatter(Edit* pEdit)
+ : FormatterBase(pEdit)
+ , mnLastValue(0)
+ , mnMin(0)
+ // a "large" value substantially smaller than SAL_MAX_INT64, to avoid
+ // overflow in computations using this "dummy" value
+ , mnMax(SAL_MAX_INT32)
+ , mbFormatting(false)
+ , mnSpinSize(1)
+ // for fields
+ , mnFirst(mnMin)
+ , mnLast(mnMax)
+ , mnDecimalDigits(0)
+ , mbThousandSep(true)
+{
+ ReformatAll();
+}
+
+NumericFormatter::~NumericFormatter()
+{
+}
+
+void NumericFormatter::SetMin( sal_Int64 nNewMin )
+{
+ mnMin = nNewMin;
+ if ( !IsEmptyFieldValue() )
+ ReformatAll();
+}
+
+void NumericFormatter::SetMax( sal_Int64 nNewMax )
+{
+ mnMax = nNewMax;
+ if ( !IsEmptyFieldValue() )
+ ReformatAll();
+}
+
+void NumericFormatter::SetUseThousandSep( bool bValue )
+{
+ mbThousandSep = bValue;
+ ReformatAll();
+}
+
+void NumericFormatter::SetDecimalDigits( sal_uInt16 nDigits )
+{
+ mnDecimalDigits = nDigits;
+ ReformatAll();
+}
+
+void NumericFormatter::SetValue( sal_Int64 nNewValue )
+{
+ SetUserValue( nNewValue );
+ SetEmptyFieldValueData( false );
+}
+
+OUString NumericFormatter::CreateFieldText( sal_Int64 nValue ) const
+{
+ return ImplGetLocaleDataWrapper().getNum( nValue, GetDecimalDigits(), IsUseThousandSep(), /*ShowTrailingZeros*/true );
+}
+
+void NumericFormatter::ImplSetUserValue( sal_Int64 nNewValue, Selection const * pNewSelection )
+{
+ nNewValue = ClipAgainstMinMax(nNewValue);
+ mnLastValue = nNewValue;
+
+ if ( GetField() )
+ FormatValue(pNewSelection);
+}
+
+void NumericFormatter::SetUserValue( sal_Int64 nNewValue )
+{
+ ImplSetUserValue( nNewValue );
+}
+
+sal_Int64 NumericFormatter::GetValueFromString(const OUString& rStr) const
+{
+ sal_Int64 nTempValue;
+
+ if (ImplNumericGetValue(rStr, nTempValue,
+ GetDecimalDigits(), ImplGetLocaleDataWrapper()))
+ {
+ return ClipAgainstMinMax(nTempValue);
+ }
+ else
+ return mnLastValue;
+}
+
+OUString NumericFormatter::GetValueString() const
+{
+ return Application::GetSettings().GetNeutralLocaleDataWrapper().
+ getNum(GetValue(), GetDecimalDigits(), false, false);
+}
+
+// currently used by online
+void NumericFormatter::SetValueFromString(const OUString& rStr)
+{
+ sal_Int64 nValue;
+
+ if (ImplNumericGetValue(rStr, nValue, GetDecimalDigits(),
+ Application::GetSettings().GetNeutralLocaleDataWrapper()))
+ {
+ ImplNewFieldValue(nValue);
+ }
+ else
+ {
+ SAL_WARN("vcl", "fail to convert the value: " << rStr );
+ }
+}
+
+sal_Int64 NumericFormatter::GetValue() const
+{
+ if (mbFormatting) //don't parse the entry if we're currently formatting what to put in it
+ return mnLastValue;
+
+ return GetField() ? GetValueFromString(GetField()->GetText()) : 0;
+}
+
+sal_Int64 NumericFormatter::Normalize( sal_Int64 nValue ) const
+{
+ return (nValue * ImplPower10( GetDecimalDigits() ) );
+}
+
+sal_Int64 NumericFormatter::Denormalize( sal_Int64 nValue ) const
+{
+ sal_Int64 nFactor = ImplPower10( GetDecimalDigits() );
+
+ if ((nValue < ( SAL_MIN_INT64 + nFactor )) ||
+ (nValue > ( SAL_MAX_INT64 - nFactor )))
+ {
+ return ( nValue / nFactor );
+ }
+
+ if( nValue < 0 )
+ {
+ sal_Int64 nHalf = nFactor / 2;
+ return ((nValue - nHalf) / nFactor );
+ }
+ else
+ {
+ sal_Int64 nHalf = nFactor / 2;
+ return ((nValue + nHalf) / nFactor );
+ }
+}
+
+void NumericFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() )
+ return;
+
+ ImplNumericReformat();
+}
+
+void NumericFormatter::FieldUp()
+{
+ sal_Int64 nValue = GetValue();
+ sal_Int64 nRemainder = nValue % mnSpinSize;
+ if (nValue >= 0)
+ nValue = (nRemainder == 0) ? nValue + mnSpinSize : nValue + mnSpinSize - nRemainder;
+ else
+ nValue = (nRemainder == 0) ? nValue + mnSpinSize : nValue - nRemainder;
+
+ nValue = ClipAgainstMinMax(nValue);
+
+ ImplNewFieldValue( nValue );
+}
+
+void NumericFormatter::FieldDown()
+{
+ sal_Int64 nValue = GetValue();
+ sal_Int64 nRemainder = nValue % mnSpinSize;
+ if (nValue >= 0)
+ nValue = (nRemainder == 0) ? nValue - mnSpinSize : nValue - nRemainder;
+ else
+ nValue = (nRemainder == 0) ? nValue - mnSpinSize : nValue - mnSpinSize - nRemainder;
+
+ nValue = ClipAgainstMinMax(nValue);
+
+ ImplNewFieldValue( nValue );
+}
+
+void NumericFormatter::FieldFirst()
+{
+ ImplNewFieldValue( mnFirst );
+}
+
+void NumericFormatter::FieldLast()
+{
+ ImplNewFieldValue( mnLast );
+}
+
+void NumericFormatter::ImplNewFieldValue( sal_Int64 nNewValue )
+{
+ if ( !GetField() )
+ return;
+
+ // !!! We should check why we do not validate in ImplSetUserValue() if the value was
+ // changed. This should be done there as well since otherwise the call to Modify would not
+ // be allowed. Anyway, the paths from ImplNewFieldValue, ImplSetUserValue, and ImplSetText
+ // should be checked and clearly traced (with comment) in order to find out what happens.
+
+ Selection aSelection = GetField()->GetSelection();
+ aSelection.Normalize();
+ OUString aText = GetField()->GetText();
+ // leave it as is if selected until end
+ if ( static_cast<sal_Int32>(aSelection.Max()) == aText.getLength() )
+ {
+ if ( !aSelection.Len() )
+ aSelection.Min() = SELECTION_MAX;
+ aSelection.Max() = SELECTION_MAX;
+ }
+
+ sal_Int64 nOldLastValue = mnLastValue;
+ ImplSetUserValue( nNewValue, &aSelection );
+ mnLastValue = nOldLastValue;
+
+ // Modify during Edit is only set during KeyInput
+ if ( GetField()->GetText() != aText )
+ {
+ GetField()->SetModifyFlag();
+ GetField()->Modify();
+ }
+}
+
+sal_Int64 NumericFormatter::ClipAgainstMinMax(sal_Int64 nValue) const
+{
+ if (nValue > mnMax)
+ nValue = mnMax;
+ else if (nValue < mnMin)
+ nValue = mnMin;
+ return nValue;
+}
+
+namespace
+{
+ Size calcMinimumSize(const Edit &rSpinField, const NumericFormatter &rFormatter)
+ {
+ OUStringBuffer aBuf;
+ sal_Int32 nTextLen;
+
+ nTextLen = std::u16string_view(OUString::number(rFormatter.GetMin())).size();
+ string::padToLength(aBuf, nTextLen, '9');
+ Size aMinTextSize = rSpinField.CalcMinimumSizeForText(
+ rFormatter.CreateFieldText(OUString::unacquired(aBuf).toInt64()));
+ aBuf.setLength(0);
+
+ nTextLen = std::u16string_view(OUString::number(rFormatter.GetMax())).size();
+ string::padToLength(aBuf, nTextLen, '9');
+ Size aMaxTextSize = rSpinField.CalcMinimumSizeForText(
+ rFormatter.CreateFieldText(OUString::unacquired(aBuf).toInt64()));
+ aBuf.setLength(0);
+
+ Size aRet(std::max(aMinTextSize.Width(), aMaxTextSize.Width()),
+ std::max(aMinTextSize.Height(), aMaxTextSize.Height()));
+
+ OUStringBuffer sBuf("999999999");
+ sal_uInt16 nDigits = rFormatter.GetDecimalDigits();
+ if (nDigits)
+ {
+ sBuf.append('.');
+ string::padToLength(aBuf, aBuf.getLength() + nDigits, '9');
+ }
+ aMaxTextSize = rSpinField.CalcMinimumSizeForText(sBuf.makeStringAndClear());
+ aRet.setWidth( std::min(aRet.Width(), aMaxTextSize.Width()) );
+
+ return aRet;
+ }
+}
+
+NumericBox::NumericBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , NumericFormatter(this)
+{
+ Reformat();
+ if ( !(nWinStyle & WB_HIDE ) )
+ Show();
+}
+
+void NumericBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+Size NumericBox::CalcMinimumSize() const
+{
+ Size aRet(calcMinimumSize(*this, *this));
+
+ if (IsDropDownBox())
+ {
+ Size aComboSugg(ComboBox::CalcMinimumSize());
+ aRet.setWidth( std::max(aRet.Width(), aComboSugg.Width()) );
+ aRet.setHeight( std::max(aRet.Height(), aComboSugg.Height()) );
+ }
+
+ return aRet;
+}
+
+bool NumericBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplNumericProcessKeyInput( *rNEvt.GetKeyEvent(), IsStrictFormat(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool NumericBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void NumericBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplResetLocaleDataWrapper();
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void NumericBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void NumericBox::ImplNumericReformat( const OUString& rStr, sal_Int64& rValue,
+ OUString& rOutStr )
+{
+ if (ImplNumericGetValue(rStr, rValue, GetDecimalDigits(), ImplGetLocaleDataWrapper()))
+ {
+ sal_Int64 nTempVal = ClipAgainstMinMax(rValue);
+ rOutStr = CreateFieldText( nTempVal );
+ }
+}
+
+void NumericBox::ReformatAll()
+{
+ sal_Int64 nValue;
+ OUString aStr;
+ SetUpdateMode( false );
+ sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; i++ )
+ {
+ ImplNumericReformat( GetEntry( i ), nValue, aStr );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ NumericFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+static bool ImplMetricProcessKeyInput( const KeyEvent& rKEvt,
+ bool bUseThousandSep, const LocaleDataWrapper& rWrapper )
+{
+ // no meaningful strict format; therefore allow all characters
+ return ImplNumericProcessKeyInput( rKEvt, false, bUseThousandSep, rWrapper );
+}
+
+static OUString ImplMetricGetUnitText(std::u16string_view rStr)
+{
+ // fetch unit text
+ OUStringBuffer aStr;
+ for (sal_Int32 i = static_cast<sal_Int32>(rStr.size())-1; i >= 0; --i)
+ {
+ sal_Unicode c = rStr[i];
+ if ( (c == '\'') || (c == '\"') || (c == '%') || (c == 0x2032) || (c == 0x2033) || unicode::isAlpha(c) || unicode::isControl(c) )
+ aStr.insert(0, c);
+ else
+ {
+ if (!aStr.isEmpty())
+ break;
+ }
+ }
+ return aStr.makeStringAndClear();
+}
+
+// #104355# support localized measurements
+
+static OUString ImplMetricToString( FieldUnit rUnit )
+{
+ // return unit's default string (ie, the first one )
+ for (auto const& elem : ImplGetFieldUnits())
+ {
+ if (elem.second == rUnit)
+ return elem.first;
+ }
+
+ return OUString();
+}
+
+namespace
+{
+ FieldUnit StringToMetric(const OUString &rMetricString)
+ {
+ // return FieldUnit
+ OUString aStr = rMetricString.toAsciiLowerCase().replaceAll(" ", "");
+ for (auto const& elem : ImplGetCleanedFieldUnits())
+ {
+ if ( elem.first == aStr )
+ return elem.second;
+ }
+
+ return FieldUnit::NONE;
+ }
+}
+
+static FieldUnit ImplMetricGetUnit(std::u16string_view rStr)
+{
+ OUString aStr = ImplMetricGetUnitText(rStr);
+ return StringToMetric(aStr);
+}
+
+static FieldUnit ImplMap2FieldUnit( MapUnit meUnit, tools::Long& nDecDigits )
+{
+ switch( meUnit )
+ {
+ case MapUnit::Map100thMM :
+ nDecDigits -= 2;
+ return FieldUnit::MM;
+ case MapUnit::Map10thMM :
+ nDecDigits -= 1;
+ return FieldUnit::MM;
+ case MapUnit::MapMM :
+ return FieldUnit::MM;
+ case MapUnit::MapCM :
+ return FieldUnit::CM;
+ case MapUnit::Map1000thInch :
+ nDecDigits -= 3;
+ return FieldUnit::INCH;
+ case MapUnit::Map100thInch :
+ nDecDigits -= 2;
+ return FieldUnit::INCH;
+ case MapUnit::Map10thInch :
+ nDecDigits -= 1;
+ return FieldUnit::INCH;
+ case MapUnit::MapInch :
+ return FieldUnit::INCH;
+ case MapUnit::MapPoint :
+ return FieldUnit::POINT;
+ case MapUnit::MapTwip :
+ return FieldUnit::TWIP;
+ default:
+ OSL_FAIL( "default eInUnit" );
+ break;
+ }
+ return FieldUnit::NONE;
+}
+
+static double nonValueDoubleToValueDouble( double nValue )
+{
+ return std::isfinite( nValue ) ? nValue : 0.0;
+}
+
+namespace vcl
+{
+ sal_Int64 ConvertValue(sal_Int64 nValue, sal_Int64 mnBaseValue, sal_uInt16 nDecDigits,
+ FieldUnit eInUnit, FieldUnit eOutUnit)
+ {
+ double nDouble = nonValueDoubleToValueDouble(vcl::ConvertDoubleValue(
+ static_cast<double>(nValue), mnBaseValue, nDecDigits, eInUnit, eOutUnit));
+ sal_Int64 nLong ;
+
+ // caution: precision loss in double cast
+ if ( nDouble <= double(SAL_MIN_INT64) )
+ nLong = SAL_MIN_INT64;
+ else if ( nDouble >= double(SAL_MAX_INT64) )
+ nLong = SAL_MAX_INT64;
+ else
+ nLong = static_cast<sal_Int64>( std::round(nDouble) );
+
+ return nLong;
+ }
+}
+
+namespace {
+
+bool checkConversionUnits(MapUnit eInUnit, FieldUnit eOutUnit)
+{
+ return eOutUnit != FieldUnit::PERCENT
+ && eOutUnit != FieldUnit::CUSTOM
+ && eOutUnit != FieldUnit::NONE
+ && eInUnit != MapUnit::MapPixel
+ && eInUnit != MapUnit::MapSysFont
+ && eInUnit != MapUnit::MapAppFont
+ && eInUnit != MapUnit::MapRelative;
+}
+
+double convertValue( double nValue, tools::Long nDigits, FieldUnit eInUnit, FieldUnit eOutUnit )
+{
+ if ( nDigits < 0 )
+ {
+ while ( nDigits )
+ {
+ nValue += 5;
+ nValue /= 10;
+ nDigits++;
+ }
+ }
+ else
+ {
+ nValue *= ImplPower10(nDigits);
+ }
+
+ if ( eInUnit != eOutUnit )
+ {
+ const o3tl::Length eFrom = FieldToO3tlLength(eInUnit), eTo = FieldToO3tlLength(eOutUnit);
+ if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid)
+ nValue = o3tl::convert(nValue, eFrom, eTo);
+ }
+
+ return nValue;
+}
+
+}
+
+namespace vcl
+{
+ sal_Int64 ConvertValue( sal_Int64 nValue, sal_uInt16 nDigits,
+ MapUnit eInUnit, FieldUnit eOutUnit )
+ {
+ if ( !checkConversionUnits(eInUnit, eOutUnit) )
+ {
+ OSL_FAIL( "invalid parameters" );
+ return nValue;
+ }
+
+ tools::Long nDecDigits = nDigits;
+ FieldUnit eFieldUnit = ImplMap2FieldUnit( eInUnit, nDecDigits );
+
+ // Avoid sal_Int64 <-> double conversion issues if possible:
+ if (eFieldUnit == eOutUnit && nDigits == 0)
+ {
+ return nValue;
+ }
+
+ return static_cast<sal_Int64>(
+ nonValueDoubleToValueDouble(
+ convertValue( nValue, nDecDigits, eFieldUnit, eOutUnit ) ) );
+ }
+
+ double ConvertDoubleValue(double nValue, sal_Int64 mnBaseValue, sal_uInt16 nDecDigits,
+ FieldUnit eInUnit, FieldUnit eOutUnit)
+ {
+ if ( eInUnit != eOutUnit )
+ {
+ if (eInUnit == FieldUnit::PERCENT && mnBaseValue > 0 && nValue > 0)
+ {
+ sal_Int64 nDiv = 100 * ImplPower10(nDecDigits);
+
+ if (mnBaseValue != 1)
+ nValue *= mnBaseValue;
+
+ nValue += nDiv / 2;
+ nValue /= nDiv;
+ }
+ else
+ {
+ const o3tl::Length eFrom = FieldToO3tlLength(eInUnit, o3tl::Length::invalid);
+ const o3tl::Length eTo = FieldToO3tlLength(eOutUnit, o3tl::Length::invalid);
+ if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid)
+ nValue = o3tl::convert(nValue, eFrom, eTo);
+ }
+ }
+
+ return nValue;
+ }
+
+ double ConvertDoubleValue(double nValue, sal_uInt16 nDigits,
+ MapUnit eInUnit, FieldUnit eOutUnit)
+ {
+ if ( !checkConversionUnits(eInUnit, eOutUnit) )
+ {
+ OSL_FAIL( "invalid parameters" );
+ return nValue;
+ }
+
+ tools::Long nDecDigits = nDigits;
+ FieldUnit eFieldUnit = ImplMap2FieldUnit( eInUnit, nDecDigits );
+
+ return convertValue(nValue, nDecDigits, eFieldUnit, eOutUnit);
+ }
+
+ double ConvertDoubleValue(double nValue, sal_uInt16 nDigits,
+ FieldUnit eInUnit, MapUnit eOutUnit)
+ {
+ if ( eInUnit == FieldUnit::PERCENT ||
+ eInUnit == FieldUnit::CUSTOM ||
+ eInUnit == FieldUnit::NONE ||
+ eInUnit == FieldUnit::DEGREE ||
+ eInUnit == FieldUnit::SECOND ||
+ eInUnit == FieldUnit::MILLISECOND ||
+ eInUnit == FieldUnit::PIXEL ||
+ eOutUnit == MapUnit::MapPixel ||
+ eOutUnit == MapUnit::MapSysFont ||
+ eOutUnit == MapUnit::MapAppFont ||
+ eOutUnit == MapUnit::MapRelative )
+ {
+ OSL_FAIL( "invalid parameters" );
+ return nValue;
+ }
+
+ tools::Long nDecDigits = nDigits;
+ FieldUnit eFieldUnit = ImplMap2FieldUnit( eOutUnit, nDecDigits );
+
+ if ( nDecDigits < 0 )
+ {
+ nValue *= ImplPower10(-nDecDigits);
+ }
+ else
+ {
+ nValue /= ImplPower10(nDecDigits);
+ }
+
+ if ( eFieldUnit != eInUnit )
+ {
+ const o3tl::Length eFrom = FieldToO3tlLength(eInUnit, o3tl::Length::invalid);
+ const o3tl::Length eTo = FieldToO3tlLength(eFieldUnit, o3tl::Length::invalid);
+ if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid)
+ nValue = o3tl::convert(nValue, eFrom, eTo);
+ }
+ return nValue;
+ }
+}
+
+namespace vcl
+{
+ bool TextToValue(const OUString& rStr, double& rValue, sal_Int64 nBaseValue,
+ sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper, FieldUnit eUnit)
+ {
+ // Get value
+ sal_Int64 nValue;
+ if ( !ImplNumericGetValue( rStr, nValue, nDecDigits, rLocaleDataWrapper ) )
+ return false;
+
+ // Determine unit
+ FieldUnit eEntryUnit = ImplMetricGetUnit( rStr );
+
+ // Recalculate unit
+ // caution: conversion to double loses precision
+ rValue = vcl::ConvertDoubleValue(static_cast<double>(nValue), nBaseValue, nDecDigits, eEntryUnit, eUnit);
+
+ return true;
+ }
+}
+
+void MetricFormatter::ImplMetricReformat( const OUString& rStr, double& rValue, OUString& rOutStr )
+{
+ if (!vcl::TextToValue(rStr, rValue, 0, GetDecimalDigits(), ImplGetLocaleDataWrapper(), meUnit))
+ return;
+
+ double nTempVal = rValue;
+ // caution: precision loss in double cast
+ if ( nTempVal > GetMax() )
+ nTempVal = static_cast<double>(GetMax());
+ else if ( nTempVal < GetMin())
+ nTempVal = static_cast<double>(GetMin());
+ rOutStr = CreateFieldText( static_cast<sal_Int64>(nTempVal) );
+}
+
+MetricFormatter::MetricFormatter(Edit* pEdit)
+ : NumericFormatter(pEdit)
+ , meUnit(FieldUnit::NONE)
+{
+}
+
+MetricFormatter::~MetricFormatter()
+{
+}
+
+void MetricFormatter::SetUnit( FieldUnit eNewUnit )
+{
+ if (eNewUnit == FieldUnit::MM_100TH)
+ {
+ SetDecimalDigits( GetDecimalDigits() + 2 );
+ meUnit = FieldUnit::MM;
+ }
+ else
+ meUnit = eNewUnit;
+ ReformatAll();
+}
+
+void MetricFormatter::SetCustomUnitText( const OUString& rStr )
+{
+ maCustomUnitText = rStr;
+ ReformatAll();
+}
+
+void MetricFormatter::SetValue( sal_Int64 nNewValue, FieldUnit eInUnit )
+{
+ SetUserValue( nNewValue, eInUnit );
+}
+
+OUString MetricFormatter::CreateFieldText( sal_Int64 nValue ) const
+{
+ //whether percent is separated from its number is locale
+ //specific, pawn it off to icu to decide
+ if (meUnit == FieldUnit::PERCENT)
+ {
+ double dValue = nValue;
+ dValue /= ImplPower10(GetDecimalDigits());
+ return unicode::formatPercent(dValue, GetLanguageTag());
+ }
+
+ OUString aStr = NumericFormatter::CreateFieldText( nValue );
+
+ if( meUnit == FieldUnit::CUSTOM )
+ aStr += maCustomUnitText;
+ else
+ {
+ OUString aSuffix = ImplMetricToString( meUnit );
+ if (meUnit != FieldUnit::NONE && meUnit != FieldUnit::DEGREE && meUnit != FieldUnit::INCH && meUnit != FieldUnit::FOOT)
+ aStr += " ";
+ if (meUnit == FieldUnit::INCH)
+ {
+ OUString sDoublePrime = u"\u2033"_ustr;
+ if (aSuffix != "\"" && aSuffix != sDoublePrime)
+ aStr += " ";
+ else
+ aSuffix = sDoublePrime;
+ }
+ else if (meUnit == FieldUnit::FOOT)
+ {
+ OUString sPrime = u"\u2032"_ustr;
+ if (aSuffix != "'" && aSuffix != sPrime)
+ aStr += " ";
+ else
+ aSuffix = sPrime;
+ }
+
+ assert(meUnit != FieldUnit::PERCENT);
+ aStr += aSuffix;
+ }
+ return aStr;
+}
+
+void MetricFormatter::SetUserValue( sal_Int64 nNewValue, FieldUnit eInUnit )
+{
+ // convert to previously configured units
+ nNewValue = vcl::ConvertValue( nNewValue, 0, GetDecimalDigits(), eInUnit, meUnit );
+ NumericFormatter::SetUserValue( nNewValue );
+}
+
+sal_Int64 MetricFormatter::GetValueFromStringUnit(const OUString& rStr, FieldUnit eOutUnit) const
+{
+ double nTempValue;
+ // caution: precision loss in double cast
+ if (!vcl::TextToValue(rStr, nTempValue, 0, GetDecimalDigits(), ImplGetLocaleDataWrapper(), meUnit))
+ nTempValue = static_cast<double>(mnLastValue);
+
+ // caution: precision loss in double cast
+ if (nTempValue > mnMax)
+ nTempValue = static_cast<double>(mnMax);
+ else if (nTempValue < mnMin)
+ nTempValue = static_cast<double>(mnMin);
+
+ // convert to requested units
+ return vcl::ConvertValue(static_cast<sal_Int64>(nTempValue), 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+sal_Int64 MetricFormatter::GetValueFromString(const OUString& rStr) const
+{
+ return GetValueFromStringUnit(rStr, FieldUnit::NONE);
+}
+
+sal_Int64 MetricFormatter::GetValue( FieldUnit eOutUnit ) const
+{
+ return GetField() ? GetValueFromStringUnit(GetField()->GetText(), eOutUnit) : 0;
+}
+
+void MetricFormatter::SetValue( sal_Int64 nValue )
+{
+ // Implementation not inline, because it is a virtual Function
+ SetValue( nValue, FieldUnit::NONE );
+}
+
+void MetricFormatter::SetMin( sal_Int64 nNewMin, FieldUnit eInUnit )
+{
+ // convert to requested units
+ NumericFormatter::SetMin(vcl::ConvertValue(nNewMin, 0, GetDecimalDigits(), eInUnit, meUnit));
+}
+
+sal_Int64 MetricFormatter::GetMin( FieldUnit eOutUnit ) const
+{
+ // convert to requested units
+ return vcl::ConvertValue(NumericFormatter::GetMin(), 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+void MetricFormatter::SetMax( sal_Int64 nNewMax, FieldUnit eInUnit )
+{
+ // convert to requested units
+ NumericFormatter::SetMax(vcl::ConvertValue(nNewMax, 0, GetDecimalDigits(), eInUnit, meUnit));
+}
+
+sal_Int64 MetricFormatter::GetMax( FieldUnit eOutUnit ) const
+{
+ // convert to requested units
+ return vcl::ConvertValue(NumericFormatter::GetMax(), 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+void MetricFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ OUString aText = GetField()->GetText();
+
+ OUString aStr;
+ // caution: precision loss in double cast
+ double nTemp = static_cast<double>(mnLastValue);
+ ImplMetricReformat( aText, nTemp, aStr );
+ mnLastValue = static_cast<sal_Int64>(nTemp);
+
+ if ( !aStr.isEmpty() )
+ {
+ ImplSetText( aStr );
+ }
+ else
+ SetValue( mnLastValue );
+}
+
+sal_Int64 MetricFormatter::GetCorrectedValue( FieldUnit eOutUnit ) const
+{
+ // convert to requested units
+ return vcl::ConvertValue(0/*nCorrectedValue*/, 0, GetDecimalDigits(),
+ meUnit, eOutUnit);
+}
+
+MetricField::MetricField(vcl::Window* pParent, WinBits nWinStyle)
+ : SpinField(pParent, nWinStyle, WindowType::METRICFIELD)
+ , MetricFormatter(this)
+{
+ Reformat();
+}
+
+void MetricField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+Size MetricField::CalcMinimumSize() const
+{
+ return calcMinimumSize(*this, *this);
+}
+
+bool MetricField::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "digits")
+ SetDecimalDigits(rValue.toInt32());
+ else if (rKey == "spin-size")
+ SetSpinSize(rValue.toInt32());
+ else
+ return SpinField::set_property(rKey, rValue);
+ return true;
+}
+
+void MetricField::SetUnit( FieldUnit nNewUnit )
+{
+ sal_Int64 nRawMax = GetMax( nNewUnit );
+ sal_Int64 nMax = Denormalize( nRawMax );
+ sal_Int64 nMin = Denormalize( GetMin( nNewUnit ) );
+ sal_Int64 nFirst = Denormalize( GetFirst( nNewUnit ) );
+ sal_Int64 nLast = Denormalize( GetLast( nNewUnit ) );
+
+ MetricFormatter::SetUnit( nNewUnit );
+
+ SetMax( Normalize( nMax ), nNewUnit );
+ SetMin( Normalize( nMin ), nNewUnit );
+ SetFirst( Normalize( nFirst ), nNewUnit );
+ SetLast( Normalize( nLast ), nNewUnit );
+}
+
+void MetricField::SetFirst( sal_Int64 nNewFirst, FieldUnit eInUnit )
+{
+ // convert
+ nNewFirst = vcl::ConvertValue(nNewFirst, 0, GetDecimalDigits(), eInUnit, meUnit);
+ mnFirst = nNewFirst;
+}
+
+sal_Int64 MetricField::GetFirst( FieldUnit eOutUnit ) const
+{
+ // convert
+ return vcl::ConvertValue(mnFirst, 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+void MetricField::SetLast( sal_Int64 nNewLast, FieldUnit eInUnit )
+{
+ // convert
+ nNewLast = vcl::ConvertValue(nNewLast, 0, GetDecimalDigits(), eInUnit, meUnit);
+ mnLast = nNewLast;
+}
+
+sal_Int64 MetricField::GetLast( FieldUnit eOutUnit ) const
+{
+ // convert
+ return vcl::ConvertValue(mnLast, 0, GetDecimalDigits(), meUnit, eOutUnit);
+}
+
+bool MetricField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplMetricProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool MetricField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void MetricField::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SpinField::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplResetLocaleDataWrapper();
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void MetricField::Modify()
+{
+ MarkToBeReformatted( true );
+ SpinField::Modify();
+}
+
+void MetricField::Up()
+{
+ FieldUp();
+ SpinField::Up();
+}
+
+void MetricField::Down()
+{
+ FieldDown();
+ SpinField::Down();
+}
+
+void MetricField::First()
+{
+ FieldFirst();
+ SpinField::First();
+}
+
+void MetricField::Last()
+{
+ FieldLast();
+ SpinField::Last();
+}
+
+void MetricField::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ SpinField::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("min", GetMin());
+ rJsonWriter.put("max", GetMax());
+ rJsonWriter.put("unit", FieldUnitToString(GetUnit()));
+ OUString sValue = Application::GetSettings().GetNeutralLocaleDataWrapper().
+ getNum(GetValue(), GetDecimalDigits(), false, false);
+ rJsonWriter.put("value", sValue);
+}
+
+FactoryFunction MetricField::GetUITestFactory() const
+{
+ return MetricFieldUIObject::create;
+}
+
+MetricBox::MetricBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , MetricFormatter(this)
+{
+ Reformat();
+}
+
+void MetricBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+Size MetricBox::CalcMinimumSize() const
+{
+ Size aRet(calcMinimumSize(*this, *this));
+
+ if (IsDropDownBox())
+ {
+ Size aComboSugg(ComboBox::CalcMinimumSize());
+ aRet.setWidth( std::max(aRet.Width(), aComboSugg.Width()) );
+ aRet.setHeight( std::max(aRet.Height(), aComboSugg.Height()) );
+ }
+
+ return aRet;
+}
+
+bool MetricBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplMetricProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool MetricBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void MetricBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplResetLocaleDataWrapper();
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void MetricBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void MetricBox::ReformatAll()
+{
+ double nValue;
+ OUString aStr;
+ SetUpdateMode( false );
+ sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; i++ )
+ {
+ ImplMetricReformat( GetEntry( i ), nValue, aStr );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ MetricFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+static bool ImplCurrencyProcessKeyInput( const KeyEvent& rKEvt,
+ bool bUseThousandSep, const LocaleDataWrapper& rWrapper )
+{
+ // no strict format set; therefore allow all characters
+ return ImplNumericProcessKeyInput( rKEvt, false, bUseThousandSep, rWrapper );
+}
+
+static bool ImplCurrencyGetValue( const OUString& rStr, sal_Int64& rValue,
+ sal_uInt16 nDecDigits, const LocaleDataWrapper& rWrapper )
+{
+ // fetch number
+ return ImplNumericGetValue( rStr, rValue, nDecDigits, rWrapper, true );
+}
+
+void CurrencyFormatter::ImplCurrencyReformat( const OUString& rStr, OUString& rOutStr )
+{
+ sal_Int64 nValue;
+ if ( !ImplNumericGetValue( rStr, nValue, GetDecimalDigits(), ImplGetLocaleDataWrapper(), true ) )
+ return;
+
+ sal_Int64 nTempVal = nValue;
+ if ( nTempVal > GetMax() )
+ nTempVal = GetMax();
+ else if ( nTempVal < GetMin())
+ nTempVal = GetMin();
+ rOutStr = CreateFieldText( nTempVal );
+}
+
+CurrencyFormatter::CurrencyFormatter(Edit* pField)
+ : NumericFormatter(pField)
+{
+}
+
+CurrencyFormatter::~CurrencyFormatter()
+{
+}
+
+void CurrencyFormatter::SetValue( sal_Int64 nNewValue )
+{
+ SetUserValue( nNewValue );
+ SetEmptyFieldValueData( false );
+}
+
+OUString CurrencyFormatter::CreateFieldText( sal_Int64 nValue ) const
+{
+ return ImplGetLocaleDataWrapper().getCurr( nValue, GetDecimalDigits(),
+ ImplGetLocaleDataWrapper().getCurrSymbol(),
+ IsUseThousandSep() );
+}
+
+sal_Int64 CurrencyFormatter::GetValueFromString(const OUString& rStr) const
+{
+ sal_Int64 nTempValue;
+ if ( ImplCurrencyGetValue( rStr, nTempValue, GetDecimalDigits(), ImplGetLocaleDataWrapper() ) )
+ {
+ return ClipAgainstMinMax(nTempValue);
+ }
+ else
+ return mnLastValue;
+}
+
+void CurrencyFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ OUString aStr;
+ ImplCurrencyReformat( GetField()->GetText(), aStr );
+
+ if ( !aStr.isEmpty() )
+ {
+ ImplSetText( aStr );
+ sal_Int64 nTemp = mnLastValue;
+ ImplCurrencyGetValue( aStr, nTemp, GetDecimalDigits(), ImplGetLocaleDataWrapper() );
+ mnLastValue = nTemp;
+ }
+ else
+ SetValue( mnLastValue );
+}
+
+CurrencyField::CurrencyField(vcl::Window* pParent, WinBits nWinStyle)
+ : SpinField(pParent, nWinStyle)
+ , CurrencyFormatter(this)
+{
+ Reformat();
+}
+
+void CurrencyField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+bool CurrencyField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplCurrencyProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool CurrencyField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void CurrencyField::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SpinField::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplResetLocaleDataWrapper();
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void CurrencyField::Modify()
+{
+ MarkToBeReformatted( true );
+ SpinField::Modify();
+}
+
+void CurrencyField::Up()
+{
+ FieldUp();
+ SpinField::Up();
+}
+
+void CurrencyField::Down()
+{
+ FieldDown();
+ SpinField::Down();
+}
+
+void CurrencyField::First()
+{
+ FieldFirst();
+ SpinField::First();
+}
+
+void CurrencyField::Last()
+{
+ FieldLast();
+ SpinField::Last();
+}
+
+CurrencyBox::CurrencyBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , CurrencyFormatter(this)
+{
+ Reformat();
+}
+
+void CurrencyBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+bool CurrencyBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplCurrencyProcessKeyInput( *rNEvt.GetKeyEvent(), IsUseThousandSep(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool CurrencyBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void CurrencyBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ OUString sOldDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sOldThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplResetLocaleDataWrapper();
+ OUString sNewDecSep = ImplGetLocaleDataWrapper().getNumDecimalSep();
+ OUString sNewThSep = ImplGetLocaleDataWrapper().getNumThousandSep();
+ ImplUpdateSeparators( sOldDecSep, sNewDecSep, sOldThSep, sNewThSep, this );
+ ReformatAll();
+ }
+}
+
+void CurrencyBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void CurrencyBox::ReformatAll()
+{
+ OUString aStr;
+ SetUpdateMode( false );
+ sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; i++ )
+ {
+ ImplCurrencyReformat( GetEntry( i ), aStr );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ CurrencyFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/field2.cxx b/vcl/source/control/field2.cxx
new file mode 100644
index 0000000000..8552d2510d
--- /dev/null
+++ b/vcl/source/control/field2.cxx
@@ -0,0 +1,3188 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <algorithm>
+#include <string_view>
+
+#include <comphelper/diagnose_ex.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <o3tl/string_view.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/field.hxx>
+#include <vcl/unohelp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/weldutils.hxx>
+
+#include <svdata.hxx>
+
+#include <com/sun/star/i18n/XCharacterClassification.hpp>
+#include <com/sun/star/i18n/CalendarFieldIndex.hdl>
+
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/calendarwrapper.hxx>
+#include <unotools/charclass.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <tools/duration.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::comphelper;
+
+#define EDITMASK_LITERAL 'L'
+#define EDITMASK_ALPHA 'a'
+#define EDITMASK_UPPERALPHA 'A'
+#define EDITMASK_ALPHANUM 'c'
+#define EDITMASK_UPPERALPHANUM 'C'
+#define EDITMASK_NUM 'N'
+#define EDITMASK_NUMSPACE 'n'
+#define EDITMASK_ALLCHAR 'x'
+#define EDITMASK_UPPERALLCHAR 'X'
+
+uno::Reference< i18n::XCharacterClassification > const & ImplGetCharClass()
+{
+ ImplSVData *const pSVData = ImplGetSVData();
+ assert(pSVData);
+
+ if (!pSVData->m_xCharClass.is())
+ {
+ pSVData->m_xCharClass = vcl::unohelper::CreateCharacterClassification();
+ }
+
+ return pSVData->m_xCharClass;
+}
+
+static sal_Unicode* ImplAddString( sal_Unicode* pBuf, const OUString& rStr )
+{
+ memcpy( pBuf, rStr.getStr(), rStr.getLength() * sizeof(sal_Unicode) );
+ pBuf += rStr.getLength();
+ return pBuf;
+}
+
+static sal_Unicode* ImplAddNum( sal_Unicode* pBuf, sal_uLong nNumber, int nMinLen )
+{
+ // fill temp buffer with digits
+ sal_Unicode aTempBuf[30];
+ sal_Unicode* pTempBuf = aTempBuf;
+ do
+ {
+ *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0';
+ pTempBuf++;
+ nNumber /= 10;
+ if ( nMinLen )
+ nMinLen--;
+ }
+ while ( nNumber );
+
+ // fill with zeros up to the minimal length
+ while ( nMinLen > 0 )
+ {
+ *pBuf = '0';
+ pBuf++;
+ nMinLen--;
+ }
+
+ // copy temp buffer to real buffer
+ do
+ {
+ pTempBuf--;
+ *pBuf = *pTempBuf;
+ pBuf++;
+ }
+ while ( pTempBuf != aTempBuf );
+
+ return pBuf;
+}
+
+static sal_Unicode* ImplAddSNum( sal_Unicode* pBuf, sal_Int32 nNumber, int nMinLen )
+{
+ if (nNumber < 0)
+ {
+ *pBuf++ = '-';
+ nNumber = -nNumber;
+ }
+ return ImplAddNum( pBuf, nNumber, nMinLen);
+}
+
+static sal_uInt16 ImplGetNum( const sal_Unicode*& rpBuf, bool& rbError )
+{
+ if ( !*rpBuf )
+ {
+ rbError = true;
+ return 0;
+ }
+
+ sal_uInt16 nNumber = 0;
+ while( ( *rpBuf >= '0' ) && ( *rpBuf <= '9' ) )
+ {
+ nNumber *= 10;
+ nNumber += *rpBuf - '0';
+ rpBuf++;
+ }
+
+ return nNumber;
+}
+
+static void ImplSkipDelimiters( const sal_Unicode*& rpBuf )
+{
+ while( ( *rpBuf == ',' ) || ( *rpBuf == '.' ) || ( *rpBuf == ';' ) ||
+ ( *rpBuf == ':' ) || ( *rpBuf == '-' ) || ( *rpBuf == '/' ) )
+ {
+ rpBuf++;
+ }
+}
+
+static bool ImplIsPatternChar( sal_Unicode cChar, char cEditMask )
+{
+ sal_Int32 nType = 0;
+
+ try
+ {
+ OUString aCharStr(cChar);
+ nType = ImplGetCharClass()->getCharacterType( aCharStr, 0,
+ Application::GetSettings().GetLanguageTag().getLocale() );
+ }
+ catch (const css::uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("vcl.control");
+ return false;
+ }
+
+ if ( (cEditMask == EDITMASK_ALPHA) || (cEditMask == EDITMASK_UPPERALPHA) )
+ {
+ if( !CharClass::isLetterType( nType ) )
+ return false;
+ }
+ else if ( cEditMask == EDITMASK_NUM )
+ {
+ if( !CharClass::isNumericType( nType ) )
+ return false;
+ }
+ else if ( (cEditMask == EDITMASK_ALPHANUM) || (cEditMask == EDITMASK_UPPERALPHANUM) )
+ {
+ if( !CharClass::isLetterNumericType( nType ) )
+ return false;
+ }
+ else if ( (cEditMask == EDITMASK_ALLCHAR) || (cEditMask == EDITMASK_UPPERALLCHAR) )
+ {
+ if ( cChar < 32 )
+ return false;
+ }
+ else if ( cEditMask == EDITMASK_NUMSPACE )
+ {
+ if ( !CharClass::isNumericType( nType ) && ( cChar != ' ' ) )
+ return false;
+ }
+ else
+ return false;
+
+ return true;
+}
+
+static sal_Unicode ImplPatternChar( sal_Unicode cChar, char cEditMask )
+{
+ if ( ImplIsPatternChar( cChar, cEditMask ) )
+ {
+ if ( (cEditMask == EDITMASK_UPPERALPHA) ||
+ (cEditMask == EDITMASK_UPPERALPHANUM) ||
+ ( cEditMask == EDITMASK_UPPERALLCHAR ) )
+ {
+ cChar = ImplGetCharClass()->toUpper(OUString(cChar), 0, 1,
+ Application::GetSettings().GetLanguageTag().getLocale())[0];
+ }
+ return cChar;
+ }
+ else
+ return 0;
+}
+
+static bool ImplCommaPointCharEqual( sal_Unicode c1, sal_Unicode c2 )
+{
+ if ( c1 == c2 )
+ return true;
+ else if ( ((c1 == '.') || (c1 == ',')) &&
+ ((c2 == '.') || (c2 == ',')) )
+ return true;
+ else
+ return false;
+}
+
+static OUString ImplPatternReformat( const OUString& rStr,
+ const OString& rEditMask,
+ std::u16string_view rLiteralMask,
+ sal_uInt16 nFormatFlags )
+{
+ if (rEditMask.isEmpty())
+ return rStr;
+
+ OUStringBuffer aOutStr(rLiteralMask);
+ sal_Unicode cTempChar;
+ sal_Unicode cChar;
+ sal_Unicode cLiteral;
+ char cMask;
+ sal_Int32 nStrIndex = 0;
+ sal_Int32 i = 0;
+ sal_Int32 n;
+
+ while ( i < rEditMask.getLength() )
+ {
+ if ( nStrIndex >= rStr.getLength() )
+ break;
+
+ cChar = rStr[nStrIndex];
+ cLiteral = rLiteralMask[i];
+ cMask = rEditMask[i];
+
+ // current position is a literal
+ if ( cMask == EDITMASK_LITERAL )
+ {
+ // if it is a literal copy otherwise ignore because it might be the next valid
+ // character of the string
+ if ( ImplCommaPointCharEqual( cChar, cLiteral ) )
+ nStrIndex++;
+ else
+ {
+ // Otherwise we check if it is an invalid character. This is the case if it does not
+ // fit in the pattern of the next non-literal character.
+ n = i+1;
+ while ( n < rEditMask.getLength() )
+ {
+ if ( rEditMask[n] != EDITMASK_LITERAL )
+ {
+ if ( !ImplIsPatternChar( cChar, rEditMask[n] ) )
+ nStrIndex++;
+ break;
+ }
+
+ n++;
+ }
+ }
+ }
+ else
+ {
+ // valid character at this position
+ cTempChar = ImplPatternChar( cChar, cMask );
+ if ( cTempChar )
+ {
+ // use this character
+ aOutStr[i] = cTempChar;
+ nStrIndex++;
+ }
+ else
+ {
+ // copy if it is a literal character
+ if ( cLiteral == cChar )
+ nStrIndex++;
+ else
+ {
+ // If the invalid character might be the next literal character then we jump
+ // ahead to it, otherwise we ignore it. Do only if empty literals are allowed.
+ if ( nFormatFlags & PATTERN_FORMAT_EMPTYLITERALS )
+ {
+ n = i;
+ while ( n < rEditMask.getLength() )
+ {
+ if ( rEditMask[n] == EDITMASK_LITERAL )
+ {
+ if ( ImplCommaPointCharEqual( cChar, rLiteralMask[n] ) )
+ i = n+1;
+
+ break;
+ }
+
+ n++;
+ }
+ }
+
+ nStrIndex++;
+ continue;
+ }
+ }
+ }
+
+ i++;
+ }
+
+ return aOutStr.makeStringAndClear();
+}
+
+static void ImplPatternMaxPos( std::u16string_view rStr, const OString& rEditMask,
+ sal_uInt16 nFormatFlags, bool bSameMask,
+ sal_Int32 nCursorPos, sal_Int32& rPos )
+{
+
+ // last position must not be longer than the contained string
+ sal_Int32 nMaxPos = rStr.size();
+
+ // if non empty literals are allowed ignore blanks at the end as well
+ if ( bSameMask && !(nFormatFlags & PATTERN_FORMAT_EMPTYLITERALS) )
+ {
+ while ( nMaxPos )
+ {
+ if ( (rEditMask[nMaxPos-1] != EDITMASK_LITERAL) &&
+ (rStr[nMaxPos-1] != ' ') )
+ break;
+ nMaxPos--;
+ }
+
+ // if we are in front of a literal, continue search until first character after the literal
+ sal_Int32 nTempPos = nMaxPos;
+ while ( nTempPos < rEditMask.getLength() )
+ {
+ if ( rEditMask[nTempPos] != EDITMASK_LITERAL )
+ {
+ nMaxPos = nTempPos;
+ break;
+ }
+ nTempPos++;
+ }
+ }
+
+ if ( rPos > nMaxPos )
+ rPos = nMaxPos;
+
+ // character should not move left
+ if ( rPos < nCursorPos )
+ rPos = nCursorPos;
+}
+
+static OUString ImplPatternProcessStrictModify(const OUString& rText,
+ const OString& rEditMask,
+ std::u16string_view rLiteralMask,
+ bool bSameMask)
+{
+ OUString aText(rText);
+
+ // remove leading blanks
+ if (bSameMask && !rEditMask.isEmpty())
+ {
+ sal_Int32 i = 0;
+ sal_Int32 nMaxLen = aText.getLength();
+ while ( i < nMaxLen )
+ {
+ if ( (rEditMask[i] != EDITMASK_LITERAL) &&
+ (aText[i] != ' ') )
+ break;
+
+ i++;
+ }
+ // keep all literal characters
+ while ( i && (rEditMask[i] == EDITMASK_LITERAL) )
+ i--;
+ aText = aText.copy( i );
+ }
+
+ return ImplPatternReformat(aText, rEditMask, rLiteralMask, 0);
+}
+
+static void ImplPatternProcessStrictModify( Edit* pEdit,
+ const OString& rEditMask,
+ std::u16string_view rLiteralMask,
+ bool bSameMask )
+{
+ OUString aText = pEdit->GetText();
+ OUString aNewText = ImplPatternProcessStrictModify(aText,
+ rEditMask,
+ rLiteralMask,
+ bSameMask);
+
+ if ( aNewText == aText )
+ return;
+
+ // adjust selection such that it remains at the end if it was there before
+ Selection aSel = pEdit->GetSelection();
+ sal_Int64 nMaxSel = std::max( aSel.Min(), aSel.Max() );
+ if ( nMaxSel >= aText.getLength() )
+ {
+ sal_Int32 nMaxPos = aNewText.getLength();
+ ImplPatternMaxPos(aNewText, rEditMask, 0, bSameMask, nMaxSel, nMaxPos);
+ if ( aSel.Min() == aSel.Max() )
+ {
+ aSel.Min() = nMaxPos;
+ aSel.Max() = aSel.Min();
+ }
+ else if ( aSel.Min() > aSel.Max() )
+ aSel.Min() = nMaxPos;
+ else
+ aSel.Max() = nMaxPos;
+ }
+ pEdit->SetText( aNewText, aSel );
+}
+
+static void ImplPatternProcessStrictModify( weld::Entry& rEntry,
+ const OString& rEditMask,
+ std::u16string_view rLiteralMask,
+ bool bSameMask )
+{
+ OUString aText = rEntry.get_text();
+ OUString aNewText = ImplPatternProcessStrictModify(aText,
+ rEditMask,
+ rLiteralMask,
+ bSameMask);
+
+ if (aNewText == aText)
+ return;
+
+ // adjust selection such that it remains at the end if it was there before
+ int nStartPos, nEndPos;
+ rEntry.get_selection_bounds(nStartPos, nEndPos);
+
+ int nMaxSel = std::max(nStartPos, nEndPos);
+ if (nMaxSel >= aText.getLength())
+ {
+ sal_Int32 nMaxPos = aNewText.getLength();
+ ImplPatternMaxPos(aNewText, rEditMask, 0, bSameMask, nMaxSel, nMaxPos);
+ if (nStartPos == nEndPos)
+ {
+ nStartPos = nMaxPos;
+ nEndPos = nMaxPos;
+ }
+ else if (nStartPos > nMaxPos)
+ nStartPos = nMaxPos;
+ else
+ nEndPos = nMaxPos;
+ }
+ rEntry.set_text(aNewText);
+ rEntry.select_region(nStartPos, nEndPos);
+}
+
+static sal_Int32 ImplPatternLeftPos(std::string_view rEditMask, sal_Int32 nCursorPos)
+{
+ // search non-literal predecessor
+ sal_Int32 nNewPos = nCursorPos;
+ sal_Int32 nTempPos = nNewPos;
+ while ( nTempPos )
+ {
+ if ( rEditMask[nTempPos-1] != EDITMASK_LITERAL )
+ {
+ nNewPos = nTempPos-1;
+ break;
+ }
+ nTempPos--;
+ }
+ return nNewPos;
+}
+
+static sal_Int32 ImplPatternRightPos( std::u16string_view rStr, const OString& rEditMask,
+ sal_uInt16 nFormatFlags, bool bSameMask,
+ sal_Int32 nCursorPos )
+{
+ // search non-literal successor
+ sal_Int32 nNewPos = nCursorPos;
+ ;
+ for(sal_Int32 nTempPos = nNewPos+1; nTempPos < rEditMask.getLength(); ++nTempPos )
+ {
+ if ( rEditMask[nTempPos] != EDITMASK_LITERAL )
+ {
+ nNewPos = nTempPos;
+ break;
+ }
+ }
+ ImplPatternMaxPos( rStr, rEditMask, nFormatFlags, bSameMask, nCursorPos, nNewPos );
+ return nNewPos;
+}
+
+namespace
+{
+ class IEditImplementation
+ {
+ public:
+ virtual ~IEditImplementation() {}
+
+ virtual OUString GetText() const = 0;
+ virtual void SetText(const OUString& rStr, const Selection& rSelection) = 0;
+
+ virtual Selection GetSelection() const = 0;
+ virtual void SetSelection(const Selection& rSelection) = 0;
+
+ virtual bool IsInsertMode() const = 0;
+
+ virtual void SetModified() = 0;
+ };
+}
+
+static bool ImplPatternProcessKeyInput( IEditImplementation& rEdit, const KeyEvent& rKEvt,
+ const OString& rEditMask,
+ std::u16string_view rLiteralMask,
+ bool bStrictFormat,
+ bool bSameMask,
+ bool& rbInKeyInput )
+{
+ if ( rEditMask.isEmpty() || !bStrictFormat )
+ return false;
+
+ sal_uInt16 nFormatFlags = 0;
+ Selection aOldSel = rEdit.GetSelection();
+ vcl::KeyCode aCode = rKEvt.GetKeyCode();
+ sal_Unicode cChar = rKEvt.GetCharCode();
+ sal_uInt16 nKeyCode = aCode.GetCode();
+ bool bShift = aCode.IsShift();
+ sal_Int32 nCursorPos = static_cast<sal_Int32>(aOldSel.Max());
+ sal_Int32 nNewPos;
+ sal_Int32 nTempPos;
+
+ if ( nKeyCode && !aCode.IsMod1() && !aCode.IsMod2() )
+ {
+ if ( nKeyCode == KEY_LEFT )
+ {
+ Selection aSel( ImplPatternLeftPos( rEditMask, nCursorPos ) );
+ if ( bShift )
+ aSel.Min() = aOldSel.Min();
+ rEdit.SetSelection( aSel );
+ return true;
+ }
+ else if ( nKeyCode == KEY_RIGHT )
+ {
+ // Use the start of selection as minimum; even a small position is allowed in case that
+ // all was selected by the focus
+ Selection aSel( aOldSel );
+ aSel.Normalize();
+ nCursorPos = aSel.Min();
+ aSel.Max() = ImplPatternRightPos( rEdit.GetText(), rEditMask, nFormatFlags, bSameMask, nCursorPos );
+ if ( bShift )
+ aSel.Min() = aOldSel.Min();
+ else
+ aSel.Min() = aSel.Max();
+ rEdit.SetSelection( aSel );
+ return true;
+ }
+ else if ( nKeyCode == KEY_HOME )
+ {
+ // Home is the position of the first non-literal character
+ nNewPos = 0;
+ while ( (nNewPos < rEditMask.getLength()) &&
+ (rEditMask[nNewPos] == EDITMASK_LITERAL) )
+ nNewPos++;
+
+ // Home should not move to the right
+ if ( nCursorPos < nNewPos )
+ nNewPos = nCursorPos;
+ Selection aSel( nNewPos );
+ if ( bShift )
+ aSel.Min() = aOldSel.Min();
+ rEdit.SetSelection( aSel );
+ return true;
+ }
+ else if ( nKeyCode == KEY_END )
+ {
+ // End is position of last non-literal character
+ nNewPos = rEditMask.getLength();
+ while ( nNewPos &&
+ (rEditMask[nNewPos-1] == EDITMASK_LITERAL) )
+ nNewPos--;
+ // Use the start of selection as minimum; even a small position is allowed in case that
+ // all was selected by the focus
+ Selection aSel( aOldSel );
+ aSel.Normalize();
+ nCursorPos = static_cast<sal_Int32>(aSel.Min());
+ ImplPatternMaxPos( rEdit.GetText(), rEditMask, nFormatFlags, bSameMask, nCursorPos, nNewPos );
+ aSel.Max() = nNewPos;
+ if ( bShift )
+ aSel.Min() = aOldSel.Min();
+ else
+ aSel.Min() = aSel.Max();
+ rEdit.SetSelection( aSel );
+ return true;
+ }
+ else if ( (nKeyCode == KEY_BACKSPACE) || (nKeyCode == KEY_DELETE) )
+ {
+ OUString aOldStr( rEdit.GetText() );
+ OUStringBuffer aStr( aOldStr );
+ Selection aSel = aOldSel;
+
+ aSel.Normalize();
+ nNewPos = static_cast<sal_Int32>(aSel.Min());
+
+ // if selection then delete it
+ if ( aSel.Len() )
+ {
+ if ( bSameMask )
+ aStr.remove( static_cast<sal_Int32>(aSel.Min()), static_cast<sal_Int32>(aSel.Len()) );
+ else
+ {
+ std::u16string_view aRep = rLiteralMask.substr( static_cast<sal_Int32>(aSel.Min()), static_cast<sal_Int32>(aSel.Len()) );
+ aStr.remove( aSel.Min(), aRep.size() );
+ aStr.insert( aSel.Min(), aRep );
+ }
+ }
+ else
+ {
+ if ( nKeyCode == KEY_BACKSPACE )
+ {
+ nTempPos = nNewPos;
+ nNewPos = ImplPatternLeftPos( rEditMask, nTempPos );
+ }
+ else
+ nTempPos = ImplPatternRightPos( aStr, rEditMask, nFormatFlags, bSameMask, nNewPos );
+
+ if ( nNewPos != nTempPos )
+ {
+ if ( bSameMask )
+ {
+ if ( rEditMask[nNewPos] != EDITMASK_LITERAL )
+ aStr.remove( nNewPos, 1 );
+ }
+ else
+ {
+ aStr[nNewPos] = rLiteralMask[nNewPos];
+ }
+ }
+ }
+
+ OUString sStr = aStr.makeStringAndClear();
+ if ( aOldStr != sStr )
+ {
+ if ( bSameMask )
+ sStr = ImplPatternReformat( sStr, rEditMask, rLiteralMask, nFormatFlags );
+ rbInKeyInput = true;
+ rEdit.SetText( sStr, Selection( nNewPos ) );
+ rEdit.SetModified();
+ rbInKeyInput = false;
+ }
+ else
+ rEdit.SetSelection( Selection( nNewPos ) );
+
+ return true;
+ }
+ else if ( nKeyCode == KEY_INSERT )
+ {
+ // you can only set InsertMode for a PatternField if the
+ // mask is equal at all input positions
+ if ( !bSameMask )
+ {
+ return true;
+ }
+ }
+ }
+
+ if ( rKEvt.GetKeyCode().IsMod2() || (cChar < 32) || (cChar == 127) )
+ return false;
+
+ Selection aSel = aOldSel;
+ aSel.Normalize();
+ nNewPos = aSel.Min();
+
+ if ( nNewPos < rEditMask.getLength() )
+ {
+ sal_Unicode cPattChar = ImplPatternChar( cChar, rEditMask[nNewPos] );
+ if ( cPattChar )
+ cChar = cPattChar;
+ else
+ {
+ // If no valid character, check if the user wanted to jump to next literal. We do this
+ // only if we're after a character, so that literals that were skipped automatically
+ // do not influence the position anymore.
+ if ( nNewPos &&
+ (rEditMask[nNewPos-1] != EDITMASK_LITERAL) &&
+ !aSel.Len() )
+ {
+ // search for next character not being a literal
+ nTempPos = nNewPos;
+ while ( nTempPos < rEditMask.getLength() )
+ {
+ if ( rEditMask[nTempPos] == EDITMASK_LITERAL )
+ {
+ // only valid if no literal present
+ if ( (rEditMask[nTempPos+1] != EDITMASK_LITERAL ) &&
+ ImplCommaPointCharEqual( cChar, rLiteralMask[nTempPos] ) )
+ {
+ nTempPos++;
+ ImplPatternMaxPos( rEdit.GetText(), rEditMask, nFormatFlags, bSameMask, nNewPos, nTempPos );
+ if ( nTempPos > nNewPos )
+ {
+ rEdit.SetSelection( Selection( nTempPos ) );
+ return true;
+ }
+ }
+ break;
+ }
+ nTempPos++;
+ }
+ }
+
+ cChar = 0;
+ }
+ }
+ else
+ cChar = 0;
+ if ( cChar )
+ {
+ OUStringBuffer aStr(rEdit.GetText());
+ bool bError = false;
+ if ( bSameMask && rEdit.IsInsertMode() )
+ {
+ // crop spaces and literals at the end until current position
+ sal_Int32 n = aStr.getLength();
+ while ( n && (n > nNewPos) )
+ {
+ if ( (aStr[n-1] != ' ') &&
+ ((n > rEditMask.getLength()) || (rEditMask[n-1] != EDITMASK_LITERAL)) )
+ break;
+
+ n--;
+ }
+ aStr.truncate( n );
+
+ if ( aSel.Len() )
+ aStr.remove( aSel.Min(), aSel.Len() );
+
+ if ( aStr.getLength() < rEditMask.getLength() )
+ {
+ // possibly extend string until cursor position
+ if ( aStr.getLength() < nNewPos )
+ aStr.append( rLiteralMask.substr(aStr.getLength(), nNewPos-aStr.getLength()) );
+ if ( nNewPos < aStr.getLength() )
+ aStr.insert( cChar, nNewPos );
+ else if ( nNewPos < rEditMask.getLength() )
+ aStr.append(cChar);
+ aStr = ImplPatternReformat( aStr.toString(), rEditMask, rLiteralMask, nFormatFlags );
+ }
+ else
+ bError = true;
+ }
+ else
+ {
+ if ( aSel.Len() )
+ {
+ // delete selection
+ std::u16string_view aRep = rLiteralMask.substr( aSel.Min(), aSel.Len() );
+ aStr.remove( aSel.Min(), aRep.size() );
+ aStr.insert( aSel.Min(), aRep );
+ }
+
+ if ( nNewPos < aStr.getLength() )
+ aStr[nNewPos] = cChar;
+ else if ( nNewPos < rEditMask.getLength() )
+ aStr.append(cChar);
+ }
+
+ if ( !bError )
+ {
+ rbInKeyInput = true;
+ const OUString sStr = aStr.makeStringAndClear();
+ Selection aNewSel( ImplPatternRightPos( sStr, rEditMask, nFormatFlags, bSameMask, nNewPos ) );
+ rEdit.SetText( sStr, aNewSel );
+ rEdit.SetModified();
+ rbInKeyInput = false;
+ }
+ }
+
+ return true;
+}
+
+namespace
+{
+ bool ImplSetMask(const OString& rEditMask, OUString& rLiteralMask)
+ {
+ bool bSameMask = true;
+
+ if (rEditMask.getLength() != rLiteralMask.getLength())
+ {
+ OUStringBuffer aBuf(rLiteralMask);
+ if (rEditMask.getLength() < aBuf.getLength())
+ aBuf.setLength(rEditMask.getLength());
+ else
+ comphelper::string::padToLength(aBuf, rEditMask.getLength(), ' ');
+ rLiteralMask = aBuf.makeStringAndClear();
+ }
+
+ // Strict mode allows only the input mode if only equal characters are allowed as mask and if
+ // only spaces are specified which are not allowed by the mask
+ sal_Int32 i = 0;
+ char c = 0;
+ while ( i < rEditMask.getLength() )
+ {
+ char cTemp = rEditMask[i];
+ if ( cTemp != EDITMASK_LITERAL )
+ {
+ if ( (cTemp == EDITMASK_ALLCHAR) ||
+ (cTemp == EDITMASK_UPPERALLCHAR) ||
+ (cTemp == EDITMASK_NUMSPACE) )
+ {
+ bSameMask = false;
+ break;
+ }
+ if ( i < rLiteralMask.getLength() )
+ {
+ if ( rLiteralMask[i] != ' ' )
+ {
+ bSameMask = false;
+ break;
+ }
+ }
+ if ( !c )
+ c = cTemp;
+ if ( cTemp != c )
+ {
+ bSameMask = false;
+ break;
+ }
+ }
+ i++;
+ }
+
+ return bSameMask;
+ }
+}
+
+PatternFormatter::PatternFormatter(Edit* pEdit)
+ : FormatterBase(pEdit)
+{
+ mbSameMask = true;
+ mbInPattKeyInput = false;
+}
+
+PatternFormatter::~PatternFormatter()
+{
+}
+
+void PatternFormatter::SetMask( const OString& rEditMask,
+ const OUString& rLiteralMask )
+{
+ m_aEditMask = rEditMask;
+ maLiteralMask = rLiteralMask;
+ mbSameMask = ImplSetMask(m_aEditMask, maLiteralMask);
+ ReformatAll();
+}
+
+namespace
+{
+ class EntryImplementation : public IEditImplementation
+ {
+ public:
+ EntryImplementation(weld::PatternFormatter& rFormatter)
+ : m_rFormatter(rFormatter)
+ , m_rEntry(rFormatter.get_widget())
+ {
+ }
+
+ virtual OUString GetText() const override
+ {
+ return m_rEntry.get_text();
+ }
+
+ virtual void SetText(const OUString& rStr, const Selection& rSelection) override
+ {
+ m_rEntry.set_text(rStr);
+ SetSelection(rSelection);
+ }
+
+ virtual Selection GetSelection() const override
+ {
+ int nStartPos, nEndPos;
+ m_rEntry.get_selection_bounds(nStartPos, nEndPos);
+ return Selection(nStartPos, nEndPos);
+ }
+
+ virtual void SetSelection(const Selection& rSelection) override
+ {
+ auto nMin = rSelection.Min();
+ auto nMax = rSelection.Max();
+ m_rEntry.select_region(nMin < 0 ? 0 : nMin, nMax == SELECTION_MAX ? -1 : nMax);
+ }
+
+ virtual bool IsInsertMode() const override
+ {
+ return !m_rEntry.get_overwrite_mode();
+ }
+
+ virtual void SetModified() override
+ {
+ m_rFormatter.Modify();
+ }
+
+ private:
+ weld::PatternFormatter& m_rFormatter;
+ weld::Entry& m_rEntry;
+ };
+}
+
+namespace weld
+{
+ void PatternFormatter::SetStrictFormat(bool bStrict)
+ {
+ if (bStrict != m_bStrictFormat)
+ {
+ m_bStrictFormat = bStrict;
+ if (m_bStrictFormat)
+ ReformatAll();
+ }
+ }
+
+ void PatternFormatter::SetMask(const OString& rEditMask,
+ const OUString& rLiteralMask)
+ {
+ m_aEditMask = rEditMask;
+ m_aLiteralMask = rLiteralMask;
+ m_bSameMask = ImplSetMask(m_aEditMask, m_aLiteralMask);
+ ReformatAll();
+ }
+
+ void PatternFormatter::ReformatAll()
+ {
+ m_rEntry.set_text(ImplPatternReformat(m_rEntry.get_text(), m_aEditMask, m_aLiteralMask, 0/*nFormatFlags*/));
+ if (!m_bSameMask && m_bStrictFormat && m_rEntry.get_editable())
+ m_rEntry.set_overwrite_mode(true);
+ }
+
+ void PatternFormatter::EntryGainFocus()
+ {
+ m_bReformat = false;
+ }
+
+ void PatternFormatter::EntryLostFocus()
+ {
+ if (m_bReformat)
+ ReformatAll();
+ }
+
+ void PatternFormatter::Modify()
+ {
+ if (!m_bInPattKeyInput)
+ {
+ if (m_bStrictFormat)
+ ImplPatternProcessStrictModify(m_rEntry, m_aEditMask, m_aLiteralMask, m_bSameMask);
+ else
+ m_bReformat = true;
+ }
+ m_aModifyHdl.Call(m_rEntry);
+ }
+
+ IMPL_LINK(PatternFormatter, KeyInputHdl, const KeyEvent&, rKEvt, bool)
+ {
+ if (m_aKeyPressHdl.Call(rKEvt))
+ return true;
+ if (rKEvt.GetKeyCode().IsMod2())
+ return false;
+ EntryImplementation aAdapt(*this);
+ return ImplPatternProcessKeyInput(aAdapt, rKEvt, m_aEditMask, m_aLiteralMask,
+ m_bStrictFormat,
+ m_bSameMask, m_bInPattKeyInput);
+ }
+}
+
+void PatternFormatter::SetString( const OUString& rStr )
+{
+ if ( GetField() )
+ {
+ GetField()->SetText( rStr );
+ MarkToBeReformatted( false );
+ }
+}
+
+OUString PatternFormatter::GetString() const
+{
+ if ( !GetField() )
+ return OUString();
+ else
+ return ImplPatternReformat( GetField()->GetText(), m_aEditMask, maLiteralMask, 0/*nFormatFlags*/ );
+}
+
+void PatternFormatter::Reformat()
+{
+ if ( GetField() )
+ {
+ ImplSetText( ImplPatternReformat( GetField()->GetText(), m_aEditMask, maLiteralMask, 0/*nFormatFlags*/ ) );
+ if ( !mbSameMask && IsStrictFormat() && !GetField()->IsReadOnly() )
+ GetField()->SetInsertMode( false );
+ }
+}
+
+PatternField::PatternField(vcl::Window* pParent, WinBits nWinStyle)
+ : SpinField(pParent, nWinStyle)
+ , PatternFormatter(this)
+{
+ Reformat();
+}
+
+void PatternField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+namespace
+{
+ class EditImplementation : public IEditImplementation
+ {
+ public:
+ EditImplementation(Edit& rEdit)
+ : m_rEdit(rEdit)
+ {
+ }
+
+ virtual OUString GetText() const override
+ {
+ return m_rEdit.GetText();
+ }
+
+ virtual void SetText(const OUString& rStr, const Selection& rSelection) override
+ {
+ m_rEdit.SetText(rStr, rSelection);
+ }
+
+ virtual Selection GetSelection() const override
+ {
+ return m_rEdit.GetSelection();
+ }
+
+ virtual void SetSelection(const Selection& rSelection) override
+ {
+ m_rEdit.SetSelection(rSelection);
+ }
+
+ virtual bool IsInsertMode() const override
+ {
+ return m_rEdit.IsInsertMode();
+ }
+
+ virtual void SetModified() override
+ {
+ m_rEdit.SetModifyFlag();
+ m_rEdit.Modify();
+ }
+
+ private:
+ Edit& m_rEdit;
+ };
+}
+
+bool PatternField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ EditImplementation aAdapt(*GetField());
+ if ( ImplPatternProcessKeyInput( aAdapt, *rNEvt.GetKeyEvent(), GetEditMask(), GetLiteralMask(),
+ IsStrictFormat(),
+ ImplIsSameMask(), ImplGetInPattKeyInput() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool PatternField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void PatternField::Modify()
+{
+ if ( !ImplGetInPattKeyInput() )
+ {
+ if ( IsStrictFormat() )
+ ImplPatternProcessStrictModify( GetField(), GetEditMask(), GetLiteralMask(), ImplIsSameMask() );
+ else
+ MarkToBeReformatted( true );
+ }
+
+ SpinField::Modify();
+}
+
+PatternBox::PatternBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox( pParent, nWinStyle )
+ , PatternFormatter(this)
+{
+ Reformat();
+}
+
+void PatternBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+bool PatternBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ EditImplementation aAdapt(*GetField());
+ if ( ImplPatternProcessKeyInput( aAdapt, *rNEvt.GetKeyEvent(), GetEditMask(), GetLiteralMask(),
+ IsStrictFormat(),
+ ImplIsSameMask(), ImplGetInPattKeyInput() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool PatternBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void PatternBox::Modify()
+{
+ if ( !ImplGetInPattKeyInput() )
+ {
+ if ( IsStrictFormat() )
+ ImplPatternProcessStrictModify( GetField(), GetEditMask(), GetLiteralMask(), ImplIsSameMask() );
+ else
+ MarkToBeReformatted( true );
+ }
+
+ ComboBox::Modify();
+}
+
+void PatternBox::ReformatAll()
+{
+ OUString aStr;
+ SetUpdateMode( false );
+ const sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; ++i )
+ {
+ aStr = ImplPatternReformat( GetEntry( i ), GetEditMask(), GetLiteralMask(), 0/*nFormatFlags*/ );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ PatternFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+static ExtDateFieldFormat ImplGetExtFormat( LongDateOrder eOld )
+{
+ switch( eOld )
+ {
+ case LongDateOrder::YDM:
+ case LongDateOrder::DMY: return ExtDateFieldFormat::ShortDDMMYY;
+ case LongDateOrder::MDY: return ExtDateFieldFormat::ShortMMDDYY;
+ case LongDateOrder::YMD:
+ default: return ExtDateFieldFormat::ShortYYMMDD;
+ }
+}
+
+static sal_uInt16 ImplCutNumberFromString( OUString& rStr )
+{
+ sal_Int32 i1 = 0;
+ while (i1 != rStr.getLength() && (rStr[i1] < '0' || rStr[i1] > '9')) {
+ ++i1;
+ }
+ sal_Int32 i2 = i1;
+ while (i2 != rStr.getLength() && rStr[i2] >= '0' && rStr[i2] <= '9') {
+ ++i2;
+ }
+ sal_Int32 nValue = o3tl::toInt32(rStr.subView(i1, i2-i1));
+ rStr = rStr.copy(std::min(i2+1, rStr.getLength()));
+ return nValue;
+}
+
+static bool ImplCutMonthName( OUString& rStr, std::u16string_view _rLookupMonthName )
+{
+ sal_Int32 index = 0;
+ rStr = rStr.replaceFirst(_rLookupMonthName, "", &index);
+ return index >= 0;
+}
+
+static sal_uInt16 ImplGetMonthFromCalendarItem( OUString& rStr, const uno::Sequence< i18n::CalendarItem2 >& rMonths )
+{
+ const sal_uInt16 nMonths = rMonths.getLength();
+ for (sal_uInt16 i=0; i < nMonths; ++i)
+ {
+ // long month name?
+ if ( ImplCutMonthName( rStr, rMonths[i].FullName ) )
+ return i+1;
+
+ // short month name?
+ if ( ImplCutMonthName( rStr, rMonths[i].AbbrevName ) )
+ return i+1;
+ }
+ return 0;
+}
+
+static sal_uInt16 ImplCutMonthFromString( OUString& rStr, OUString& rCalendarName,
+ const LocaleDataWrapper& rLocaleData, const CalendarWrapper& rCalendarWrapper )
+{
+ const OUString aDefaultCalendarName( rCalendarWrapper.getUniqueID());
+ rCalendarName = aDefaultCalendarName;
+
+ // Search for a month name of the loaded default calendar.
+ const uno::Sequence< i18n::CalendarItem2 > aMonths = rCalendarWrapper.getMonths();
+ sal_uInt16 nMonth = ImplGetMonthFromCalendarItem( rStr, aMonths);
+ if (nMonth > 0)
+ return nMonth;
+
+ // And also possessive genitive and partitive month names.
+ const uno::Sequence< i18n::CalendarItem2 > aGenitiveMonths = rCalendarWrapper.getGenitiveMonths();
+ if (aGenitiveMonths != aMonths)
+ {
+ nMonth = ImplGetMonthFromCalendarItem( rStr, aGenitiveMonths);
+ if (nMonth > 0)
+ return nMonth;
+ }
+ const uno::Sequence< i18n::CalendarItem2 > aPartitiveMonths = rCalendarWrapper.getPartitiveMonths();
+ if (aPartitiveMonths != aMonths)
+ {
+ nMonth = ImplGetMonthFromCalendarItem( rStr, aPartitiveMonths);
+ if (nMonth > 0)
+ return nMonth;
+ }
+
+ // Check if there are more calendars and try them if so, as the long date
+ // format is obtained from the number formatter this is possible (e.g.
+ // ar_DZ "[~hijri] ...")
+ const uno::Sequence< i18n::Calendar2 > aCalendars = rLocaleData.getAllCalendars();
+ if (aCalendars.getLength() > 1)
+ {
+ for (const auto& rCalendar : aCalendars)
+ {
+ if (rCalendar.Name != aDefaultCalendarName)
+ {
+ rCalendarName = rCalendar.Name;
+
+ nMonth = ImplGetMonthFromCalendarItem( rStr, rCalendar.Months);
+ if (nMonth > 0)
+ return nMonth;
+
+ if (rCalendar.Months != rCalendar.GenitiveMonths)
+ {
+ nMonth = ImplGetMonthFromCalendarItem( rStr, rCalendar.GenitiveMonths);
+ if (nMonth > 0)
+ return nMonth;
+ }
+
+ if (rCalendar.Months != rCalendar.PartitiveMonths)
+ {
+ nMonth = ImplGetMonthFromCalendarItem( rStr, rCalendar.PartitiveMonths);
+ if (nMonth > 0)
+ return nMonth;
+ }
+
+ rCalendarName = aDefaultCalendarName;
+ }
+ }
+ }
+
+ return ImplCutNumberFromString( rStr );
+}
+
+static OUString ImplGetDateSep( const LocaleDataWrapper& rLocaleDataWrapper, ExtDateFieldFormat eFormat )
+{
+ if ( ( eFormat == ExtDateFieldFormat::ShortYYMMDD_DIN5008 ) || ( eFormat == ExtDateFieldFormat::ShortYYYYMMDD_DIN5008 ) )
+ return "-";
+ else
+ return rLocaleDataWrapper.getDateSep();
+}
+
+static bool ImplDateProcessKeyInput( const KeyEvent& rKEvt, ExtDateFieldFormat eFormat,
+ const LocaleDataWrapper& rLocaleDataWrapper )
+{
+ sal_Unicode cChar = rKEvt.GetCharCode();
+ sal_uInt16 nGroup = rKEvt.GetKeyCode().GetGroup();
+ return !((nGroup == KEYGROUP_FKEYS) ||
+ (nGroup == KEYGROUP_CURSOR) ||
+ (nGroup == KEYGROUP_MISC)||
+ ((cChar >= '0') && (cChar <= '9')) ||
+ (cChar == ImplGetDateSep( rLocaleDataWrapper, eFormat )[0]));
+}
+
+bool DateFormatter::TextToDate(const OUString& rStr, Date& rDate, ExtDateFieldFormat eDateOrder,
+ const LocaleDataWrapper& rLocaleDataWrapper, const CalendarWrapper& rCalendarWrapper)
+{
+ sal_uInt16 nDay = 0;
+ sal_uInt16 nMonth = 0;
+ sal_uInt16 nYear = 0;
+ bool bError = false;
+ OUString aStr( rStr );
+
+ if ( eDateOrder == ExtDateFieldFormat::SystemLong )
+ {
+ OUString aCalendarName;
+ LongDateOrder eFormat = rLocaleDataWrapper.getLongDateOrder();
+ switch( eFormat )
+ {
+ case LongDateOrder::MDY:
+ nMonth = ImplCutMonthFromString( aStr, aCalendarName, rLocaleDataWrapper, rCalendarWrapper );
+ nDay = ImplCutNumberFromString( aStr );
+ nYear = ImplCutNumberFromString( aStr );
+ break;
+ case LongDateOrder::DMY:
+ nDay = ImplCutNumberFromString( aStr );
+ nMonth = ImplCutMonthFromString( aStr, aCalendarName, rLocaleDataWrapper, rCalendarWrapper );
+ nYear = ImplCutNumberFromString( aStr );
+ break;
+ case LongDateOrder::YDM:
+ nYear = ImplCutNumberFromString( aStr );
+ nDay = ImplCutNumberFromString( aStr );
+ nMonth = ImplCutMonthFromString( aStr, aCalendarName, rLocaleDataWrapper, rCalendarWrapper );
+ break;
+ case LongDateOrder::YMD:
+ default:
+ nYear = ImplCutNumberFromString( aStr );
+ nMonth = ImplCutMonthFromString( aStr, aCalendarName, rLocaleDataWrapper, rCalendarWrapper );
+ nDay = ImplCutNumberFromString( aStr );
+ break;
+ }
+ if (aCalendarName != "gregorian")
+ {
+ // Calendar widget is Gregorian, convert date.
+ // Need full date.
+ bError = !nDay || !nMonth || !nYear;
+ if (!bError)
+ {
+ CalendarWrapper aCW( rLocaleDataWrapper.getComponentContext());
+ aCW.loadCalendar( aCalendarName, rLocaleDataWrapper.getLoadedLanguageTag().getLocale());
+ aCW.setDateTime(0.5); // get rid of current time, set some day noon
+ aCW.setValue( i18n::CalendarFieldIndex::DAY_OF_MONTH, nDay);
+ aCW.setValue( i18n::CalendarFieldIndex::MONTH, nMonth - 1);
+ aCW.setValue( i18n::CalendarFieldIndex::YEAR, nYear);
+ bError = !aCW.isValid();
+ if (!bError)
+ {
+ Date aDate = aCW.getEpochStart() + aCW.getDateTime();
+ nYear = aDate.GetYear();
+ nMonth = aDate.GetMonth();
+ nDay = aDate.GetDay();
+ }
+ }
+ }
+ }
+ else
+ {
+ bool bYear = true;
+
+ // Check if year is present:
+ OUString aDateSep = ImplGetDateSep( rLocaleDataWrapper, eDateOrder );
+ sal_Int32 nSepPos = aStr.indexOf( aDateSep );
+ if ( nSepPos < 0 )
+ return false;
+ nSepPos = aStr.indexOf( aDateSep, nSepPos+1 );
+ if ( ( nSepPos < 0 ) || ( nSepPos == (aStr.getLength()-1) ) )
+ {
+ bYear = false;
+ nYear = Date( Date::SYSTEM ).GetYearUnsigned();
+ }
+
+ const sal_Unicode* pBuf = aStr.getStr();
+ ImplSkipDelimiters( pBuf );
+
+ switch ( eDateOrder )
+ {
+ case ExtDateFieldFormat::ShortDDMMYY:
+ case ExtDateFieldFormat::ShortDDMMYYYY:
+ {
+ nDay = ImplGetNum( pBuf, bError );
+ ImplSkipDelimiters( pBuf );
+ nMonth = ImplGetNum( pBuf, bError );
+ ImplSkipDelimiters( pBuf );
+ if ( bYear )
+ nYear = ImplGetNum( pBuf, bError );
+ }
+ break;
+ case ExtDateFieldFormat::ShortMMDDYY:
+ case ExtDateFieldFormat::ShortMMDDYYYY:
+ {
+ nMonth = ImplGetNum( pBuf, bError );
+ ImplSkipDelimiters( pBuf );
+ nDay = ImplGetNum( pBuf, bError );
+ ImplSkipDelimiters( pBuf );
+ if ( bYear )
+ nYear = ImplGetNum( pBuf, bError );
+ }
+ break;
+ case ExtDateFieldFormat::ShortYYMMDD:
+ case ExtDateFieldFormat::ShortYYYYMMDD:
+ case ExtDateFieldFormat::ShortYYMMDD_DIN5008:
+ case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008:
+ {
+ if ( bYear )
+ nYear = ImplGetNum( pBuf, bError );
+ ImplSkipDelimiters( pBuf );
+ nMonth = ImplGetNum( pBuf, bError );
+ ImplSkipDelimiters( pBuf );
+ nDay = ImplGetNum( pBuf, bError );
+ }
+ break;
+
+ default:
+ {
+ OSL_FAIL( "DateOrder???" );
+ }
+ }
+ }
+
+ if ( bError || !nDay || !nMonth )
+ return false;
+
+ Date aNewDate( nDay, nMonth, nYear );
+ DateFormatter::ExpandCentury( aNewDate, officecfg::Office::Common::DateFormat::TwoDigitYear::get() );
+ if ( aNewDate.IsValidDate() )
+ {
+ rDate = aNewDate;
+ return true;
+ }
+ return false;
+}
+
+void DateFormatter::ImplDateReformat( const OUString& rStr, OUString& rOutStr )
+{
+ Date aDate( Date::EMPTY );
+ if (!TextToDate(rStr, aDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper()))
+ return;
+
+ Date aTempDate = aDate;
+ if ( aTempDate > GetMax() )
+ aTempDate = GetMax();
+ else if ( aTempDate < GetMin() )
+ aTempDate = GetMin();
+
+ rOutStr = ImplGetDateAsText( aTempDate );
+}
+
+namespace
+{
+ ExtDateFieldFormat ResolveSystemFormat(ExtDateFieldFormat eDateFormat, const LocaleDataWrapper& rLocaleData)
+ {
+ if (eDateFormat <= ExtDateFieldFormat::SystemShortYYYY)
+ {
+ bool bShowCentury = (eDateFormat == ExtDateFieldFormat::SystemShortYYYY);
+ switch (rLocaleData.getDateOrder())
+ {
+ case DateOrder::DMY:
+ eDateFormat = bShowCentury ? ExtDateFieldFormat::ShortDDMMYYYY : ExtDateFieldFormat::ShortDDMMYY;
+ break;
+ case DateOrder::MDY:
+ eDateFormat = bShowCentury ? ExtDateFieldFormat::ShortMMDDYYYY : ExtDateFieldFormat::ShortMMDDYY;
+ break;
+ default:
+ eDateFormat = bShowCentury ? ExtDateFieldFormat::ShortYYYYMMDD : ExtDateFieldFormat::ShortYYMMDD;
+ }
+ }
+ return eDateFormat;
+ }
+}
+
+OUString DateFormatter::FormatDate(const Date& rDate, ExtDateFieldFormat eExtFormat,
+ const LocaleDataWrapper& rLocaleData,
+ const Formatter::StaticFormatter& rStaticFormatter)
+{
+ bool bShowCentury = false;
+ switch (eExtFormat)
+ {
+ case ExtDateFieldFormat::SystemShortYYYY:
+ case ExtDateFieldFormat::SystemLong:
+ case ExtDateFieldFormat::ShortDDMMYYYY:
+ case ExtDateFieldFormat::ShortMMDDYYYY:
+ case ExtDateFieldFormat::ShortYYYYMMDD:
+ case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008:
+ {
+ bShowCentury = true;
+ }
+ break;
+ default:
+ {
+ bShowCentury = false;
+ }
+ }
+
+ if ( !bShowCentury )
+ {
+ // Check if I have to use force showing the century
+ sal_uInt16 nTwoDigitYearStart = officecfg::Office::Common::DateFormat::TwoDigitYear::get();
+ sal_uInt16 nYear = rDate.GetYearUnsigned();
+
+ // If year is not in double digit range
+ if ( (nYear < nTwoDigitYearStart) || (nYear >= nTwoDigitYearStart+100) )
+ bShowCentury = true;
+ }
+
+ sal_Unicode aBuf[128];
+ sal_Unicode* pBuf = aBuf;
+
+ eExtFormat = ResolveSystemFormat(eExtFormat, rLocaleData);
+
+ OUString aDateSep = ImplGetDateSep( rLocaleData, eExtFormat );
+ sal_uInt16 nDay = rDate.GetDay();
+ sal_uInt16 nMonth = rDate.GetMonth();
+ sal_Int16 nYear = rDate.GetYear();
+ sal_uInt16 nYearLen = bShowCentury ? 4 : 2;
+
+ if ( !bShowCentury )
+ nYear %= 100;
+
+ switch (eExtFormat)
+ {
+ case ExtDateFieldFormat::SystemLong:
+ {
+ SvNumberFormatter* pFormatter = rStaticFormatter;
+ const LanguageTag aFormatterLang( pFormatter->GetLanguageTag());
+ const sal_uInt32 nIndex = pFormatter->GetFormatIndex( NF_DATE_SYSTEM_LONG,
+ rLocaleData.getLanguageTag().getLanguageType(false));
+ OUString aStr;
+ const Color* pCol;
+ pFormatter->GetOutputString( rDate - pFormatter->GetNullDate(), nIndex, aStr, &pCol);
+ // Reset to what other uses may expect.
+ pFormatter->ChangeIntl( aFormatterLang.getLanguageType(false));
+ return aStr;
+ }
+ case ExtDateFieldFormat::ShortDDMMYY:
+ case ExtDateFieldFormat::ShortDDMMYYYY:
+ {
+ pBuf = ImplAddNum( pBuf, nDay, 2 );
+ pBuf = ImplAddString( pBuf, aDateSep );
+ pBuf = ImplAddNum( pBuf, nMonth, 2 );
+ pBuf = ImplAddString( pBuf, aDateSep );
+ pBuf = ImplAddSNum( pBuf, nYear, nYearLen );
+ }
+ break;
+ case ExtDateFieldFormat::ShortMMDDYY:
+ case ExtDateFieldFormat::ShortMMDDYYYY:
+ {
+ pBuf = ImplAddNum( pBuf, nMonth, 2 );
+ pBuf = ImplAddString( pBuf, aDateSep );
+ pBuf = ImplAddNum( pBuf, nDay, 2 );
+ pBuf = ImplAddString( pBuf, aDateSep );
+ pBuf = ImplAddSNum( pBuf, nYear, nYearLen );
+ }
+ break;
+ case ExtDateFieldFormat::ShortYYMMDD:
+ case ExtDateFieldFormat::ShortYYYYMMDD:
+ case ExtDateFieldFormat::ShortYYMMDD_DIN5008:
+ case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008:
+ {
+ pBuf = ImplAddSNum( pBuf, nYear, nYearLen );
+ pBuf = ImplAddString( pBuf, aDateSep );
+ pBuf = ImplAddNum( pBuf, nMonth, 2 );
+ pBuf = ImplAddString( pBuf, aDateSep );
+ pBuf = ImplAddNum( pBuf, nDay, 2 );
+ }
+ break;
+ default:
+ {
+ OSL_FAIL( "DateOrder???" );
+ }
+ }
+
+ return OUString(aBuf, pBuf-aBuf);
+}
+
+OUString DateFormatter::ImplGetDateAsText( const Date& rDate ) const
+{
+ return DateFormatter::FormatDate(rDate, GetExtDateFormat(), ImplGetLocaleDataWrapper(), maStaticFormatter);
+}
+
+static void ImplDateIncrementDay( Date& rDate, bool bUp )
+{
+ DateFormatter::ExpandCentury( rDate );
+ rDate.AddDays( bUp ? 1 : -1 );
+}
+
+static void ImplDateIncrementMonth( Date& rDate, bool bUp )
+{
+ DateFormatter::ExpandCentury( rDate );
+ rDate.AddMonths( bUp ? 1 : -1 );
+}
+
+static void ImplDateIncrementYear( Date& rDate, bool bUp )
+{
+ DateFormatter::ExpandCentury( rDate );
+ rDate.AddYears( bUp ? 1 : -1 );
+}
+
+bool DateFormatter::ImplAllowMalformedInput() const
+{
+ return !IsEnforceValidValue();
+}
+
+int DateFormatter::GetDateArea(ExtDateFieldFormat& eFormat, std::u16string_view rText, int nCursor, const LocaleDataWrapper& rLocaleDataWrapper)
+{
+ sal_Int8 nDateArea = 0;
+
+ if ( eFormat == ExtDateFieldFormat::SystemLong )
+ {
+ eFormat = ImplGetExtFormat(rLocaleDataWrapper.getLongDateOrder());
+ nDateArea = 1;
+ }
+ else
+ {
+ // search area
+ size_t nPos = 0;
+ OUString aDateSep = ImplGetDateSep(rLocaleDataWrapper, eFormat);
+ for ( sal_Int8 i = 1; i <= 3; i++ )
+ {
+ nPos = rText.find( aDateSep, nPos );
+ if (nPos == std::u16string_view::npos || static_cast<sal_Int32>(nPos) >= nCursor)
+ {
+ nDateArea = i;
+ break;
+ }
+ else
+ nPos++;
+ }
+ }
+
+ return nDateArea;
+}
+
+void DateField::ImplDateSpinArea( bool bUp )
+{
+ // increment days if all is selected
+ if ( !GetField() )
+ return;
+
+ Date aDate( GetDate() );
+ Selection aSelection = GetField()->GetSelection();
+ aSelection.Normalize();
+ OUString aText( GetText() );
+ if ( static_cast<sal_Int32>(aSelection.Len()) == aText.getLength() )
+ ImplDateIncrementDay( aDate, bUp );
+ else
+ {
+ ExtDateFieldFormat eFormat = GetExtDateFormat( true );
+ sal_Int8 nDateArea = GetDateArea(eFormat, aText, aSelection.Max(), ImplGetLocaleDataWrapper());
+
+ switch( eFormat )
+ {
+ case ExtDateFieldFormat::ShortMMDDYY:
+ case ExtDateFieldFormat::ShortMMDDYYYY:
+ switch( nDateArea )
+ {
+ case 1: ImplDateIncrementMonth( aDate, bUp );
+ break;
+ case 2: ImplDateIncrementDay( aDate, bUp );
+ break;
+ case 3: ImplDateIncrementYear( aDate, bUp );
+ break;
+ }
+ break;
+ case ExtDateFieldFormat::ShortDDMMYY:
+ case ExtDateFieldFormat::ShortDDMMYYYY:
+ switch( nDateArea )
+ {
+ case 1: ImplDateIncrementDay( aDate, bUp );
+ break;
+ case 2: ImplDateIncrementMonth( aDate, bUp );
+ break;
+ case 3: ImplDateIncrementYear( aDate, bUp );
+ break;
+ }
+ break;
+ case ExtDateFieldFormat::ShortYYMMDD:
+ case ExtDateFieldFormat::ShortYYYYMMDD:
+ case ExtDateFieldFormat::ShortYYMMDD_DIN5008:
+ case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008:
+ switch( nDateArea )
+ {
+ case 1: ImplDateIncrementYear( aDate, bUp );
+ break;
+ case 2: ImplDateIncrementMonth( aDate, bUp );
+ break;
+ case 3: ImplDateIncrementDay( aDate, bUp );
+ break;
+ }
+ break;
+ default:
+ OSL_FAIL( "invalid conversion" );
+ break;
+ }
+ }
+
+ ImplNewFieldValue( aDate );
+}
+
+DateFormatter::DateFormatter(Edit* pEdit)
+ : FormatterBase(pEdit)
+ , maFieldDate(0)
+ , maLastDate(0)
+ , maMin(1, 1, 1900)
+ , maMax(31, 12, 2200)
+ , mbLongFormat(false)
+ , mbShowDateCentury(true)
+ , mnExtDateFormat(ExtDateFieldFormat::SystemShort)
+ , mbEnforceValidValue(true)
+{
+}
+
+DateFormatter::~DateFormatter()
+{
+}
+
+CalendarWrapper& DateFormatter::GetCalendarWrapper() const
+{
+ if (!mxCalendarWrapper)
+ {
+ const_cast<DateFormatter*>(this)->mxCalendarWrapper.reset( new CalendarWrapper( comphelper::getProcessComponentContext() ) );
+ mxCalendarWrapper->loadDefaultCalendar( GetLocale() );
+ }
+
+ return *mxCalendarWrapper;
+}
+
+void DateFormatter::SetExtDateFormat( ExtDateFieldFormat eFormat )
+{
+ mnExtDateFormat = eFormat;
+ ReformatAll();
+}
+
+ExtDateFieldFormat DateFormatter::GetExtDateFormat( bool bResolveSystemFormat ) const
+{
+ ExtDateFieldFormat eDateFormat = mnExtDateFormat;
+
+ if (bResolveSystemFormat)
+ eDateFormat = ResolveSystemFormat(eDateFormat, ImplGetLocaleDataWrapper());
+
+ return eDateFormat;
+}
+
+void DateFormatter::ReformatAll()
+{
+ Reformat();
+}
+
+void DateFormatter::SetMin( const Date& rNewMin )
+{
+ maMin = rNewMin;
+ if ( !IsEmptyFieldValue() )
+ ReformatAll();
+}
+
+void DateFormatter::SetMax( const Date& rNewMax )
+{
+ maMax = rNewMax;
+ if ( !IsEmptyFieldValue() )
+ ReformatAll();
+}
+
+void DateFormatter::SetLongFormat( bool bLong )
+{
+ mbLongFormat = bLong;
+
+ // #91913# Remove LongFormat and DateShowCentury - redundant
+ if ( bLong )
+ {
+ SetExtDateFormat( ExtDateFieldFormat::SystemLong );
+ }
+ else
+ {
+ if( mnExtDateFormat == ExtDateFieldFormat::SystemLong )
+ SetExtDateFormat( ExtDateFieldFormat::SystemShort );
+ }
+
+ ReformatAll();
+}
+
+namespace
+{
+ ExtDateFieldFormat ChangeDateCentury(ExtDateFieldFormat eExtDateFormat, bool bShowDateCentury)
+ {
+ // #91913# Remove LongFormat and DateShowCentury - redundant
+ if (bShowDateCentury)
+ {
+ switch (eExtDateFormat)
+ {
+ case ExtDateFieldFormat::SystemShort:
+ case ExtDateFieldFormat::SystemShortYY:
+ eExtDateFormat = ExtDateFieldFormat::SystemShortYYYY; break;
+ case ExtDateFieldFormat::ShortDDMMYY:
+ eExtDateFormat = ExtDateFieldFormat::ShortDDMMYYYY; break;
+ case ExtDateFieldFormat::ShortMMDDYY:
+ eExtDateFormat = ExtDateFieldFormat::ShortMMDDYYYY; break;
+ case ExtDateFieldFormat::ShortYYMMDD:
+ eExtDateFormat = ExtDateFieldFormat::ShortYYYYMMDD; break;
+ case ExtDateFieldFormat::ShortYYMMDD_DIN5008:
+ eExtDateFormat = ExtDateFieldFormat::ShortYYYYMMDD_DIN5008; break;
+ default:
+ ;
+ }
+ }
+ else
+ {
+ switch (eExtDateFormat)
+ {
+ case ExtDateFieldFormat::SystemShort:
+ case ExtDateFieldFormat::SystemShortYYYY:
+ eExtDateFormat = ExtDateFieldFormat::SystemShortYY; break;
+ case ExtDateFieldFormat::ShortDDMMYYYY:
+ eExtDateFormat = ExtDateFieldFormat::ShortDDMMYY; break;
+ case ExtDateFieldFormat::ShortMMDDYYYY:
+ eExtDateFormat = ExtDateFieldFormat::ShortMMDDYY; break;
+ case ExtDateFieldFormat::ShortYYYYMMDD:
+ eExtDateFormat = ExtDateFieldFormat::ShortYYMMDD; break;
+ case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008:
+ eExtDateFormat = ExtDateFieldFormat::ShortYYMMDD_DIN5008; break;
+ default:
+ ;
+ }
+ }
+
+ return eExtDateFormat;
+ }
+}
+
+void DateFormatter::SetShowDateCentury( bool bShowDateCentury )
+{
+ mbShowDateCentury = bShowDateCentury;
+
+ SetExtDateFormat(ChangeDateCentury(GetExtDateFormat(), bShowDateCentury));
+
+ ReformatAll();
+}
+
+void DateFormatter::SetDate( const Date& rNewDate )
+{
+ ImplSetUserDate( rNewDate );
+ maFieldDate = maLastDate;
+ maLastDate = GetDate();
+}
+
+void DateFormatter::ImplSetUserDate( const Date& rNewDate, Selection const * pNewSelection )
+{
+ Date aNewDate = rNewDate;
+ if ( aNewDate > maMax )
+ aNewDate = maMax;
+ else if ( aNewDate < maMin )
+ aNewDate = maMin;
+ maLastDate = aNewDate;
+
+ if ( GetField() )
+ ImplSetText( ImplGetDateAsText( aNewDate ), pNewSelection );
+}
+
+void DateFormatter::ImplNewFieldValue( const Date& rDate )
+{
+ if ( !GetField() )
+ return;
+
+ Selection aSelection = GetField()->GetSelection();
+ aSelection.Normalize();
+ OUString aText = GetField()->GetText();
+
+ // If selected until the end then keep it that way
+ if ( static_cast<sal_Int32>(aSelection.Max()) == aText.getLength() )
+ {
+ if ( !aSelection.Len() )
+ aSelection.Min() = SELECTION_MAX;
+ aSelection.Max() = SELECTION_MAX;
+ }
+
+ Date aOldLastDate = maLastDate;
+ ImplSetUserDate( rDate, &aSelection );
+ maLastDate = aOldLastDate;
+
+ // Modify at Edit is only set at KeyInput
+ if ( GetField()->GetText() != aText )
+ {
+ GetField()->SetModifyFlag();
+ GetField()->Modify();
+ }
+}
+
+Date DateFormatter::GetDate() const
+{
+ Date aDate( Date::EMPTY );
+
+ if ( GetField() )
+ {
+ if (TextToDate(GetField()->GetText(), aDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper()))
+ {
+ if ( aDate > maMax )
+ aDate = maMax;
+ else if ( aDate < maMin )
+ aDate = maMin;
+ }
+ else
+ {
+ // !!! We should find out why dates are treated differently than other fields (see
+ // also bug: 52384)
+
+ if ( !ImplAllowMalformedInput() )
+ {
+ if ( maLastDate.GetDate() )
+ aDate = maLastDate;
+ else if ( !IsEmptyFieldValueEnabled() )
+ aDate = Date( Date::SYSTEM );
+ }
+ else
+ aDate = Date( Date::EMPTY ); // set invalid date
+ }
+ }
+
+ return aDate;
+}
+
+void DateFormatter::SetEmptyDate()
+{
+ FormatterBase::SetEmptyFieldValue();
+}
+
+bool DateFormatter::IsEmptyDate() const
+{
+ bool bEmpty = FormatterBase::IsEmptyFieldValue();
+
+ if ( GetField() && MustBeReformatted() && IsEmptyFieldValueEnabled() )
+ {
+ if ( GetField()->GetText().isEmpty() )
+ {
+ bEmpty = true;
+ }
+ else if ( !maLastDate.GetDate() )
+ {
+ Date aDate( Date::EMPTY );
+ bEmpty = !TextToDate(GetField()->GetText(), aDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper());
+ }
+ }
+ return bEmpty;
+}
+
+void DateFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() )
+ return;
+
+ OUString aStr;
+ ImplDateReformat( GetField()->GetText(), aStr );
+
+ if ( !aStr.isEmpty() )
+ {
+ ImplSetText( aStr );
+ (void)TextToDate(aStr, maLastDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper());
+ }
+ else
+ {
+ if ( maLastDate.GetDate() )
+ SetDate( maLastDate );
+ else if ( !IsEmptyFieldValueEnabled() )
+ SetDate( Date( Date::SYSTEM ) );
+ else
+ {
+ ImplSetText( OUString() );
+ SetEmptyFieldValueData( true );
+ }
+ }
+}
+
+void DateFormatter::ExpandCentury( Date& rDate )
+{
+ ExpandCentury(rDate, officecfg::Office::Common::DateFormat::TwoDigitYear::get());
+}
+
+void DateFormatter::ExpandCentury( Date& rDate, sal_uInt16 nTwoDigitYearStart )
+{
+ sal_Int16 nDateYear = rDate.GetYear();
+ if ( 0 <= nDateYear && nDateYear < 100 )
+ {
+ sal_uInt16 nCentury = nTwoDigitYearStart / 100;
+ if ( nDateYear < (nTwoDigitYearStart % 100) )
+ nCentury++;
+ rDate.SetYear( nDateYear + (nCentury*100) );
+ }
+}
+
+DateField::DateField( vcl::Window* pParent, WinBits nWinStyle ) :
+ SpinField( pParent, nWinStyle ),
+ DateFormatter(this),
+ maFirst( GetMin() ),
+ maLast( GetMax() )
+{
+ SetText( ImplGetLocaleDataWrapper().getDate( ImplGetFieldDate() ) );
+ Reformat();
+ ResetLastDate();
+}
+
+void DateField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+bool DateField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && IsStrictFormat() &&
+ ( GetExtDateFormat() != ExtDateFieldFormat::SystemLong ) &&
+ !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplDateProcessKeyInput( *rNEvt.GetKeyEvent(), GetExtDateFormat( true ), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool DateField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() )
+ {
+ // !!! We should find out why dates are treated differently than other fields (see
+ // also bug: 52384)
+
+ bool bTextLen = !GetText().isEmpty();
+ if ( bTextLen || !IsEmptyFieldValueEnabled() )
+ {
+ if ( !ImplAllowMalformedInput() )
+ Reformat();
+ else
+ {
+ Date aDate( 0, 0, 0 );
+ if (TextToDate(GetText(), aDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper()))
+ // even with strict text analysis, our text is a valid date -> do a complete
+ // reformat
+ Reformat();
+ }
+ }
+ else
+ {
+ ResetLastDate();
+ SetEmptyFieldValueData( true );
+ }
+ }
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void DateField::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SpinField::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & (AllSettingsFlags::LOCALE|AllSettingsFlags::MISC)) )
+ {
+ if (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE)
+ ImplResetLocaleDataWrapper();
+ ReformatAll();
+ }
+}
+
+void DateField::Modify()
+{
+ MarkToBeReformatted( true );
+ SpinField::Modify();
+}
+
+void DateField::Up()
+{
+ ImplDateSpinArea( true );
+ SpinField::Up();
+}
+
+void DateField::Down()
+{
+ ImplDateSpinArea( false );
+ SpinField::Down();
+}
+
+void DateField::First()
+{
+ ImplNewFieldValue( maFirst );
+ SpinField::First();
+}
+
+void DateField::Last()
+{
+ ImplNewFieldValue( maLast );
+ SpinField::Last();
+}
+
+DateBox::DateBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox( pParent, nWinStyle )
+ , DateFormatter(this)
+{
+ SetText( ImplGetLocaleDataWrapper().getDate( ImplGetFieldDate() ) );
+ Reformat();
+}
+
+void DateBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+bool DateBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && IsStrictFormat() &&
+ ( GetExtDateFormat() != ExtDateFieldFormat::SystemLong ) &&
+ !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplDateProcessKeyInput( *rNEvt.GetKeyEvent(), GetExtDateFormat( true ), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+void DateBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ ImplResetLocaleDataWrapper();
+ ReformatAll();
+ }
+}
+
+bool DateBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() )
+ {
+ bool bTextLen = !GetText().isEmpty();
+ if ( bTextLen || !IsEmptyFieldValueEnabled() )
+ Reformat();
+ else
+ {
+ ResetLastDate();
+ SetEmptyFieldValueData( true );
+ }
+ }
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void DateBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void DateBox::ReformatAll()
+{
+ OUString aStr;
+ SetUpdateMode( false );
+ const sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; ++i )
+ {
+ ImplDateReformat( GetEntry( i ), aStr );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ DateFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+namespace weld
+{
+ CalendarWrapper& DateFormatter::GetCalendarWrapper() const
+ {
+ if (!m_xCalendarWrapper)
+ {
+ m_xCalendarWrapper.reset(new CalendarWrapper(comphelper::getProcessComponentContext()));
+ m_xCalendarWrapper->loadDefaultCalendar(Application::GetSettings().GetLanguageTag().getLocale());
+ }
+ return *m_xCalendarWrapper;
+ }
+
+ void DateFormatter::SetShowDateCentury(bool bShowDateCentury)
+ {
+ m_eFormat = ChangeDateCentury(m_eFormat, bShowDateCentury);
+
+ ReFormat();
+ }
+
+ void DateFormatter::SetDate(const Date& rDate)
+ {
+ auto nDate = rDate.GetDate();
+ bool bForceOutput = GetEntryText().isEmpty() && rDate == GetDate();
+ if (bForceOutput)
+ {
+ ImplSetValue(nDate, true);
+ return;
+ }
+ SetValue(nDate);
+ }
+
+ Date DateFormatter::GetDate()
+ {
+ return Date(GetValue());
+ }
+
+ void DateFormatter::SetMin(const Date& rNewMin)
+ {
+ SetMinValue(rNewMin.GetDate());
+ }
+
+ void DateFormatter::SetMax(const Date& rNewMax)
+ {
+ SetMaxValue(rNewMax.GetDate());
+ }
+
+ OUString DateFormatter::FormatNumber(int nValue) const
+ {
+ const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
+ return ::DateFormatter::FormatDate(Date(nValue), m_eFormat, rLocaleData, m_aStaticFormatter);
+ }
+
+ IMPL_LINK_NOARG(DateFormatter, FormatOutputHdl, LinkParamNone*, bool)
+ {
+ OUString sText = FormatNumber(GetValue());
+ ImplSetTextImpl(sText, nullptr);
+ return true;
+ }
+
+ IMPL_LINK(DateFormatter, ParseInputHdl, sal_Int64*, result, TriState)
+ {
+ const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
+
+ Date aResult(Date::EMPTY);
+ bool bRet = ::DateFormatter::TextToDate(GetEntryText(), aResult, ResolveSystemFormat(m_eFormat, rLocaleDataWrapper),
+ rLocaleDataWrapper, GetCalendarWrapper());
+ if (bRet)
+ *result = aResult.GetDate();
+
+ return bRet ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+}
+
+static bool ImplTimeProcessKeyInput( const KeyEvent& rKEvt,
+ bool bStrictFormat, bool bDuration,
+ TimeFieldFormat eFormat,
+ const LocaleDataWrapper& rLocaleDataWrapper )
+{
+ sal_Unicode cChar = rKEvt.GetCharCode();
+
+ if ( !bStrictFormat )
+ return false;
+ else
+ {
+ sal_uInt16 nGroup = rKEvt.GetKeyCode().GetGroup();
+ if ( (nGroup == KEYGROUP_FKEYS) || (nGroup == KEYGROUP_CURSOR) ||
+ (nGroup == KEYGROUP_MISC) ||
+ ((cChar >= '0') && (cChar <= '9')) ||
+ rLocaleDataWrapper.getTimeSep() == OUStringChar(cChar) ||
+ (rLocaleDataWrapper.getTimeAM().indexOf(cChar) != -1) ||
+ (rLocaleDataWrapper.getTimePM().indexOf(cChar) != -1) ||
+ // Accept AM/PM:
+ (cChar == 'a') || (cChar == 'A') || (cChar == 'm') || (cChar == 'M') || (cChar == 'p') || (cChar == 'P') ||
+ ((eFormat == TimeFieldFormat::F_SEC_CS) && rLocaleDataWrapper.getTime100SecSep() == OUStringChar(cChar)) ||
+ (bDuration && (cChar == '-')) )
+ return false;
+ else
+ return true;
+ }
+}
+
+static bool ImplIsOnlyDigits( const OUString& _rStr )
+{
+ const sal_Unicode* _pChr = _rStr.getStr();
+ for ( sal_Int32 i = 0; i < _rStr.getLength(); ++i, ++_pChr )
+ {
+ if ( *_pChr < '0' || *_pChr > '9' )
+ return false;
+ }
+ return true;
+}
+
+static bool ImplIsValidTimePortion( bool _bSkipInvalidCharacters, const OUString& _rStr )
+{
+ if ( !_bSkipInvalidCharacters )
+ {
+ if ( ( _rStr.getLength() > 2 ) || _rStr.isEmpty() || !ImplIsOnlyDigits( _rStr ) )
+ return false;
+ }
+ return true;
+}
+
+static bool ImplCutTimePortion( OUStringBuffer& _rStr, sal_Int32 _nSepPos, bool _bSkipInvalidCharacters, short* _pPortion )
+{
+ OUString sPortion(_rStr.subView(0, _nSepPos));
+
+ if (_nSepPos < _rStr.getLength())
+ _rStr.remove(0, _nSepPos + 1);
+ else
+ _rStr.truncate();
+
+ if ( !ImplIsValidTimePortion( _bSkipInvalidCharacters, sPortion ) )
+ return false;
+ *_pPortion = static_cast<short>(sPortion.toInt32());
+ return true;
+}
+
+bool TimeFormatter::TextToTime(std::u16string_view rStr, tools::Time& rTime,
+ TimeFieldFormat eFormat,
+ bool bDuration, const LocaleDataWrapper& rLocaleDataWrapper, bool _bSkipInvalidCharacters)
+{
+ OUStringBuffer aStr(rStr);
+ short nHour = 0;
+ short nMinute = 0;
+ short nSecond = 0;
+ sal_Int64 nNanoSec = 0;
+ tools::Time aTime( 0, 0, 0 );
+
+ if ( rStr.empty() )
+ return false;
+
+ // Search for separators
+ if (!rLocaleDataWrapper.getTimeSep().isEmpty())
+ {
+ OUStringBuffer aSepStr(",.;:/");
+ if ( !bDuration )
+ aSepStr.append('-');
+
+ // Replace characters above by the separator character
+ for (sal_Int32 i = 0; i < aSepStr.getLength(); ++i)
+ {
+ if (rLocaleDataWrapper.getTimeSep() == OUStringChar(aSepStr[i]))
+ continue;
+ for ( sal_Int32 j = 0; j < aStr.getLength(); j++ )
+ {
+ if (aStr[j] == aSepStr[i])
+ aStr[j] = rLocaleDataWrapper.getTimeSep()[0];
+ }
+ }
+ }
+
+ bool bNegative = false;
+ sal_Int32 nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() );
+ if ( aStr[0] == '-' )
+ bNegative = true;
+ if ( eFormat != TimeFieldFormat::F_SEC_CS )
+ {
+ if ( nSepPos < 0 )
+ nSepPos = aStr.getLength();
+ if ( !ImplCutTimePortion( aStr, nSepPos, _bSkipInvalidCharacters, &nHour ) )
+ return false;
+
+ nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() );
+ if ( !aStr.isEmpty() && aStr[0] == '-' )
+ bNegative = true;
+ if ( nSepPos >= 0 )
+ {
+ if ( !ImplCutTimePortion( aStr, nSepPos, _bSkipInvalidCharacters, &nMinute ) )
+ return false;
+
+ nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() );
+ if ( !aStr.isEmpty() && aStr[0] == '-' )
+ bNegative = true;
+ if ( nSepPos >= 0 )
+ {
+ if ( !ImplCutTimePortion( aStr, nSepPos, _bSkipInvalidCharacters, &nSecond ) )
+ return false;
+ if ( !aStr.isEmpty() && aStr[0] == '-' )
+ bNegative = true;
+ nNanoSec = o3tl::toInt64(aStr);
+ }
+ else
+ nSecond = static_cast<short>(o3tl::toInt32(aStr));
+ }
+ else
+ nMinute = static_cast<short>(o3tl::toInt32(aStr));
+ }
+ else if ( nSepPos < 0 )
+ {
+ nSecond = static_cast<short>(o3tl::toInt32(aStr));
+ nMinute += nSecond / 60;
+ nSecond %= 60;
+ nHour += nMinute / 60;
+ nMinute %= 60;
+ }
+ else
+ {
+ nSecond = static_cast<short>(o3tl::toInt32(aStr.subView( 0, nSepPos )));
+ aStr.remove( 0, nSepPos+1 );
+
+ nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() );
+ if ( !aStr.isEmpty() && aStr[0] == '-' )
+ bNegative = true;
+ if ( nSepPos >= 0 )
+ {
+ nMinute = nSecond;
+ nSecond = static_cast<short>(o3tl::toInt32(aStr.subView( 0, nSepPos )));
+ aStr.remove( 0, nSepPos+1 );
+
+ nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() );
+ if ( !aStr.isEmpty() && aStr[0] == '-' )
+ bNegative = true;
+ if ( nSepPos >= 0 )
+ {
+ nHour = nMinute;
+ nMinute = nSecond;
+ nSecond = static_cast<short>(o3tl::toInt32(aStr.subView( 0, nSepPos )));
+ aStr.remove( 0, nSepPos+1 );
+ }
+ else
+ {
+ nHour += nMinute / 60;
+ nMinute %= 60;
+ }
+ }
+ else
+ {
+ nMinute += nSecond / 60;
+ nSecond %= 60;
+ nHour += nMinute / 60;
+ nMinute %= 60;
+ }
+ nNanoSec = o3tl::toInt64(aStr);
+ }
+
+ if ( nNanoSec )
+ {
+ assert(aStr.getLength() >= 1);
+
+ sal_Int32 nLen = 1; // at least one digit, otherwise nNanoSec==0
+
+ while ( aStr.getLength() > nLen && aStr[nLen] >= '0' && aStr[nLen] <= '9' )
+ nLen++;
+
+ while ( nLen < 9)
+ {
+ nNanoSec *= 10;
+ ++nLen;
+ }
+ while ( nLen > 9 )
+ {
+ // round if negative?
+ nNanoSec = (nNanoSec + 5) / 10;
+ --nLen;
+ }
+ }
+
+ assert(nNanoSec > -1000000000 && nNanoSec < 1000000000);
+ if ( (nMinute > 59) || (nSecond > 59) || (nNanoSec > 1000000000) )
+ return false;
+
+ if ( eFormat == TimeFieldFormat::F_NONE )
+ nSecond = nNanoSec = 0;
+ else if ( eFormat == TimeFieldFormat::F_SEC )
+ nNanoSec = 0;
+
+ if ( !bDuration )
+ {
+ if ( bNegative || (nHour < 0) || (nMinute < 0) ||
+ (nSecond < 0) || (nNanoSec < 0) )
+ return false;
+
+ OUString aUpperCaseStr = aStr.toString().toAsciiUpperCase();
+ OUString aAMlocalised(rLocaleDataWrapper.getTimeAM().toAsciiUpperCase());
+ OUString aPMlocalised(rLocaleDataWrapper.getTimePM().toAsciiUpperCase());
+
+ if ( (nHour < 12) && ( ( aUpperCaseStr.indexOf( "PM" ) >= 0 ) || ( aUpperCaseStr.indexOf( aPMlocalised ) >= 0 ) ) )
+ nHour += 12;
+
+ if ( (nHour == 12) && ( ( aUpperCaseStr.indexOf( "AM" ) >= 0 ) || ( aUpperCaseStr.indexOf( aAMlocalised ) >= 0 ) ) )
+ nHour = 0;
+
+ aTime = tools::Time( static_cast<sal_uInt16>(nHour), static_cast<sal_uInt16>(nMinute), static_cast<sal_uInt16>(nSecond),
+ static_cast<sal_uInt32>(nNanoSec) );
+ }
+ else
+ {
+ assert( !bNegative || (nHour < 0) || (nMinute < 0) ||
+ (nSecond < 0) || (nNanoSec < 0) );
+ if ( bNegative || (nHour < 0) || (nMinute < 0) ||
+ (nSecond < 0) || (nNanoSec < 0) )
+ {
+ // LEM TODO: this looks weird... I think buggy when parsing "05:-02:18"
+ bNegative = true;
+ nHour = nHour < 0 ? -nHour : nHour;
+ nMinute = nMinute < 0 ? -nMinute : nMinute;
+ nSecond = nSecond < 0 ? -nSecond : nSecond;
+ nNanoSec = nNanoSec < 0 ? -nNanoSec : nNanoSec;
+ }
+
+ aTime = tools::Time( static_cast<sal_uInt16>(nHour), static_cast<sal_uInt16>(nMinute), static_cast<sal_uInt16>(nSecond),
+ static_cast<sal_uInt32>(nNanoSec) );
+ if ( bNegative )
+ aTime = -aTime;
+ }
+
+ rTime = aTime;
+
+ return true;
+}
+
+void TimeFormatter::ImplTimeReformat( std::u16string_view rStr, OUString& rOutStr )
+{
+ tools::Time aTime( 0, 0, 0 );
+ if ( !TextToTime( rStr, aTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper() ) )
+ return;
+
+ tools::Time aTempTime = aTime;
+ if ( aTempTime > GetMax() )
+ aTempTime = GetMax() ;
+ else if ( aTempTime < GetMin() )
+ aTempTime = GetMin();
+
+ bool bSecond = false;
+ bool b100Sec = false;
+ if ( meFormat != TimeFieldFormat::F_NONE )
+ bSecond = true;
+
+ if ( meFormat == TimeFieldFormat::F_SEC_CS )
+ {
+ sal_uLong n = aTempTime.GetHour() * 3600L;
+ n += aTempTime.GetMin() * 60L;
+ n += aTempTime.GetSec();
+ rOutStr = OUString::number( n );
+ rOutStr += ImplGetLocaleDataWrapper().getTime100SecSep();
+ std::ostringstream ostr;
+ ostr.fill('0');
+ ostr.width(9);
+ ostr << aTempTime.GetNanoSec();
+ rOutStr += OUString::createFromAscii(ostr.str());
+ }
+ else if ( mbDuration )
+ {
+ tools::Duration aDuration( 0, aTempTime);
+ rOutStr = ImplGetLocaleDataWrapper().getDuration( aDuration, bSecond, b100Sec );
+ }
+ else
+ {
+ rOutStr = ImplGetLocaleDataWrapper().getTime( aTempTime, bSecond, b100Sec );
+ if ( GetTimeFormat() == TimeFormat::Hour12 )
+ {
+ if ( aTempTime.GetHour() > 12 )
+ {
+ tools::Time aT( aTempTime );
+ aT.SetHour( aT.GetHour() % 12 );
+ rOutStr = ImplGetLocaleDataWrapper().getTime( aT, bSecond, b100Sec );
+ }
+ // Don't use LocaleDataWrapper, we want AM/PM
+ if ( aTempTime.GetHour() < 12 )
+ rOutStr += "AM"; // ImplGetLocaleDataWrapper().getTimeAM();
+ else
+ rOutStr += "PM"; // ImplGetLocaleDataWrapper().getTimePM();
+ }
+ }
+}
+
+bool TimeFormatter::ImplAllowMalformedInput() const
+{
+ return !IsEnforceValidValue();
+}
+
+int TimeFormatter::GetTimeArea(TimeFieldFormat eFormat, std::u16string_view rText, int nCursor,
+ const LocaleDataWrapper& rLocaleDataWrapper)
+{
+ int nTimeArea = 0;
+
+ // Area search
+ if (eFormat != TimeFieldFormat::F_SEC_CS)
+ {
+ //Which area is the cursor in of HH:MM:SS.TT
+ for ( size_t i = 1, nPos = 0; i <= 4; i++ )
+ {
+ size_t nPos1 = rText.find(rLocaleDataWrapper.getTimeSep(), nPos);
+ size_t nPos2 = rText.find(rLocaleDataWrapper.getTime100SecSep(), nPos);
+ //which ever comes first, bearing in mind that one might not be there
+ if (nPos1 != std::u16string_view::npos && nPos2 != std::u16string_view::npos)
+ nPos = std::min(nPos1, nPos2);
+ else if (nPos1 != std::u16string_view::npos)
+ nPos = nPos1;
+ else
+ nPos = nPos2;
+ if (nPos == std::u16string_view::npos || static_cast<sal_Int32>(nPos) >= nCursor)
+ {
+ nTimeArea = i;
+ break;
+ }
+ else
+ nPos++;
+ }
+ }
+ else
+ {
+ size_t nPos = rText.find(rLocaleDataWrapper.getTime100SecSep());
+ if (nPos == std::u16string_view::npos || static_cast<sal_Int32>(nPos) >= nCursor)
+ nTimeArea = 3;
+ else
+ nTimeArea = 4;
+ }
+
+ return nTimeArea;
+}
+
+tools::Time TimeFormatter::SpinTime(bool bUp, const tools::Time& rTime, TimeFieldFormat eFormat,
+ bool bDuration, std::u16string_view rText, int nCursor,
+ const LocaleDataWrapper& rLocaleDataWrapper)
+{
+ tools::Time aTime(rTime);
+
+ int nTimeArea = GetTimeArea(eFormat, rText, nCursor, rLocaleDataWrapper);
+
+ if ( nTimeArea )
+ {
+ tools::Time aAddTime( 0, 0, 0 );
+ if ( nTimeArea == 1 )
+ aAddTime = tools::Time( 1, 0 );
+ else if ( nTimeArea == 2 )
+ aAddTime = tools::Time( 0, 1 );
+ else if ( nTimeArea == 3 )
+ aAddTime = tools::Time( 0, 0, 1 );
+ else if ( nTimeArea == 4 )
+ aAddTime = tools::Time( 0, 0, 0, 1 );
+
+ if ( !bUp )
+ aAddTime = -aAddTime;
+
+ aTime += aAddTime;
+ if (!bDuration)
+ {
+ tools::Time aAbsMaxTime( 23, 59, 59, 999999999 );
+ if ( aTime > aAbsMaxTime )
+ aTime = aAbsMaxTime;
+ tools::Time aAbsMinTime( 0, 0 );
+ if ( aTime < aAbsMinTime )
+ aTime = aAbsMinTime;
+ }
+ }
+
+ return aTime;
+}
+
+void TimeField::ImplTimeSpinArea( bool bUp )
+{
+ if ( GetField() )
+ {
+ tools::Time aTime( GetTime() );
+ OUString aText( GetText() );
+ Selection aSelection( GetField()->GetSelection() );
+
+ aTime = TimeFormatter::SpinTime(bUp, aTime, GetFormat(), IsDuration(), aText, aSelection.Max(), ImplGetLocaleDataWrapper());
+
+ ImplNewFieldValue( aTime );
+ }
+}
+
+TimeFormatter::TimeFormatter(Edit* pEdit)
+ : FormatterBase(pEdit)
+ , maLastTime(0, 0)
+ , maMin(0, 0)
+ , maMax(23, 59, 59, 999999999)
+ , meFormat(TimeFieldFormat::F_NONE)
+ , mnTimeFormat(TimeFormat::Hour24) // Should become an ExtTimeFieldFormat in next implementation, merge with mbDuration and meFormat
+ , mbDuration(false)
+ , mbEnforceValidValue(true)
+ , maFieldTime(0, 0)
+{
+}
+
+TimeFormatter::~TimeFormatter()
+{
+}
+
+void TimeFormatter::ReformatAll()
+{
+ Reformat();
+}
+
+void TimeFormatter::SetMin( const tools::Time& rNewMin )
+{
+ maMin = rNewMin;
+ if ( !IsEmptyFieldValue() )
+ ReformatAll();
+}
+
+void TimeFormatter::SetMax( const tools::Time& rNewMax )
+{
+ maMax = rNewMax;
+ if ( !IsEmptyFieldValue() )
+ ReformatAll();
+}
+
+void TimeFormatter::SetTimeFormat( TimeFormat eNewFormat )
+{
+ mnTimeFormat = eNewFormat;
+}
+
+
+void TimeFormatter::SetFormat( TimeFieldFormat eNewFormat )
+{
+ meFormat = eNewFormat;
+ ReformatAll();
+}
+
+void TimeFormatter::SetDuration( bool bNewDuration )
+{
+ mbDuration = bNewDuration;
+ ReformatAll();
+}
+
+void TimeFormatter::SetTime( const tools::Time& rNewTime )
+{
+ SetUserTime( rNewTime );
+ maFieldTime = maLastTime;
+ SetEmptyFieldValueData( false );
+}
+
+void TimeFormatter::ImplNewFieldValue( const tools::Time& rTime )
+{
+ if ( !GetField() )
+ return;
+
+ Selection aSelection = GetField()->GetSelection();
+ aSelection.Normalize();
+ OUString aText = GetField()->GetText();
+
+ // If selected until the end then keep it that way
+ if ( static_cast<sal_Int32>(aSelection.Max()) == aText.getLength() )
+ {
+ if ( !aSelection.Len() )
+ aSelection.Min() = SELECTION_MAX;
+ aSelection.Max() = SELECTION_MAX;
+ }
+
+ tools::Time aOldLastTime = maLastTime;
+ ImplSetUserTime( rTime, &aSelection );
+ maLastTime = aOldLastTime;
+
+ // Modify at Edit is only set at KeyInput
+ if ( GetField()->GetText() != aText )
+ {
+ GetField()->SetModifyFlag();
+ GetField()->Modify();
+ }
+}
+
+OUString TimeFormatter::FormatTime(const tools::Time& rNewTime, TimeFieldFormat eFormat, TimeFormat eHourFormat, bool bDuration, const LocaleDataWrapper& rLocaleData)
+{
+ OUString aStr;
+ bool bSec = false;
+ bool b100Sec = false;
+ if ( eFormat != TimeFieldFormat::F_NONE )
+ bSec = true;
+ if ( eFormat == TimeFieldFormat::F_SEC_CS )
+ b100Sec = true;
+ if ( eFormat == TimeFieldFormat::F_SEC_CS )
+ {
+ sal_uLong n = rNewTime.GetHour() * 3600L;
+ n += rNewTime.GetMin() * 60L;
+ n += rNewTime.GetSec();
+ aStr = OUString::number( n ) + rLocaleData.getTime100SecSep();
+ std::ostringstream ostr;
+ ostr.fill('0');
+ ostr.width(9);
+ ostr << rNewTime.GetNanoSec();
+ aStr += OUString::createFromAscii(ostr.str());
+ }
+ else if ( bDuration )
+ {
+ tools::Duration aDuration( 0, rNewTime);
+ aStr = rLocaleData.getDuration( aDuration, bSec, b100Sec );
+ }
+ else
+ {
+ aStr = rLocaleData.getTime( rNewTime, bSec, b100Sec );
+ if ( eHourFormat == TimeFormat::Hour12 )
+ {
+ if ( rNewTime.GetHour() > 12 )
+ {
+ tools::Time aT( rNewTime );
+ aT.SetHour( aT.GetHour() % 12 );
+ aStr = rLocaleData.getTime( aT, bSec, b100Sec );
+ }
+ // Don't use LocaleDataWrapper, we want AM/PM
+ if ( rNewTime.GetHour() < 12 )
+ aStr += "AM"; // rLocaleData.getTimeAM();
+ else
+ aStr += "PM"; // rLocaleData.getTimePM();
+ }
+ }
+
+ return aStr;
+}
+
+void TimeFormatter::ImplSetUserTime( const tools::Time& rNewTime, Selection const * pNewSelection )
+{
+ tools::Time aNewTime = rNewTime;
+ if ( aNewTime > GetMax() )
+ aNewTime = GetMax();
+ else if ( aNewTime < GetMin() )
+ aNewTime = GetMin();
+ maLastTime = aNewTime;
+
+ if ( GetField() )
+ {
+ OUString aStr = TimeFormatter::FormatTime(aNewTime, meFormat, GetTimeFormat(), mbDuration, ImplGetLocaleDataWrapper());
+ ImplSetText( aStr, pNewSelection );
+ }
+}
+
+void TimeFormatter::SetUserTime( const tools::Time& rNewTime )
+{
+ ImplSetUserTime( rNewTime );
+}
+
+tools::Time TimeFormatter::GetTime() const
+{
+ tools::Time aTime( 0, 0, 0 );
+
+ if ( GetField() )
+ {
+ bool bAllowMalformed = ImplAllowMalformedInput();
+ if ( TextToTime( GetField()->GetText(), aTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper(), !bAllowMalformed ) )
+ {
+ if ( aTime > GetMax() )
+ aTime = GetMax();
+ else if ( aTime < GetMin() )
+ aTime = GetMin();
+ }
+ else
+ {
+ if ( bAllowMalformed )
+ aTime = tools::Time( 99, 99, 99 ); // set invalid time
+ else
+ aTime = maLastTime;
+ }
+ }
+
+ return aTime;
+}
+
+void TimeFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() )
+ return;
+
+ OUString aStr;
+ ImplTimeReformat( GetField()->GetText(), aStr );
+
+ if ( !aStr.isEmpty() )
+ {
+ ImplSetText( aStr );
+ (void)TextToTime(aStr, maLastTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper());
+ }
+ else
+ SetTime( maLastTime );
+}
+
+TimeField::TimeField( vcl::Window* pParent, WinBits nWinStyle ) :
+ SpinField( pParent, nWinStyle ),
+ TimeFormatter(this),
+ maFirst( GetMin() ),
+ maLast( GetMax() )
+{
+ SetText( ImplGetLocaleDataWrapper().getTime( maFieldTime, false ) );
+ Reformat();
+}
+
+void TimeField::dispose()
+{
+ ClearField();
+ SpinField::dispose();
+}
+
+bool TimeField::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplTimeProcessKeyInput( *rNEvt.GetKeyEvent(), IsStrictFormat(), IsDuration(), GetFormat(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return SpinField::PreNotify( rNEvt );
+}
+
+bool TimeField::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ {
+ if ( !ImplAllowMalformedInput() )
+ Reformat();
+ else
+ {
+ tools::Time aTime( 0, 0, 0 );
+ if ( TextToTime( GetText(), aTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper(), false ) )
+ // even with strict text analysis, our text is a valid time -> do a complete
+ // reformat
+ Reformat();
+ }
+ }
+ }
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+void TimeField::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ SpinField::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ ImplResetLocaleDataWrapper();
+ ReformatAll();
+ }
+}
+
+void TimeField::Modify()
+{
+ MarkToBeReformatted( true );
+ SpinField::Modify();
+}
+
+void TimeField::Up()
+{
+ ImplTimeSpinArea( true );
+ SpinField::Up();
+}
+
+void TimeField::Down()
+{
+ ImplTimeSpinArea( false );
+ SpinField::Down();
+}
+
+void TimeField::First()
+{
+ ImplNewFieldValue( maFirst );
+ SpinField::First();
+}
+
+void TimeField::Last()
+{
+ ImplNewFieldValue( maLast );
+ SpinField::Last();
+}
+
+void TimeField::SetExtFormat( ExtTimeFieldFormat eFormat )
+{
+ switch ( eFormat )
+ {
+ case ExtTimeFieldFormat::Short24H:
+ {
+ SetTimeFormat( TimeFormat::Hour24 );
+ SetDuration( false );
+ SetFormat( TimeFieldFormat::F_NONE );
+ }
+ break;
+ case ExtTimeFieldFormat::Long24H:
+ {
+ SetTimeFormat( TimeFormat::Hour24 );
+ SetDuration( false );
+ SetFormat( TimeFieldFormat::F_SEC );
+ }
+ break;
+ case ExtTimeFieldFormat::Short12H:
+ {
+ SetTimeFormat( TimeFormat::Hour12 );
+ SetDuration( false );
+ SetFormat( TimeFieldFormat::F_NONE );
+ }
+ break;
+ case ExtTimeFieldFormat::Long12H:
+ {
+ SetTimeFormat( TimeFormat::Hour12 );
+ SetDuration( false );
+ SetFormat( TimeFieldFormat::F_SEC );
+ }
+ break;
+ case ExtTimeFieldFormat::ShortDuration:
+ {
+ SetDuration( true );
+ SetFormat( TimeFieldFormat::F_NONE );
+ }
+ break;
+ case ExtTimeFieldFormat::LongDuration:
+ {
+ SetDuration( true );
+ SetFormat( TimeFieldFormat::F_SEC );
+ }
+ break;
+ default: OSL_FAIL( "ExtTimeFieldFormat unknown!" );
+ }
+
+ if ( GetField() && !GetField()->GetText().isEmpty() )
+ SetUserTime( GetTime() );
+ ReformatAll();
+}
+
+TimeBox::TimeBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , TimeFormatter(this)
+{
+ SetText( ImplGetLocaleDataWrapper().getTime( maFieldTime, false ) );
+ Reformat();
+}
+
+void TimeBox::dispose()
+{
+ ClearField();
+ ComboBox::dispose();
+}
+
+bool TimeBox::PreNotify( NotifyEvent& rNEvt )
+{
+ if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() )
+ {
+ if ( ImplTimeProcessKeyInput( *rNEvt.GetKeyEvent(), IsStrictFormat(), IsDuration(), GetFormat(), ImplGetLocaleDataWrapper() ) )
+ return true;
+ }
+
+ return ComboBox::PreNotify( rNEvt );
+}
+
+bool TimeBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if ( rNEvt.GetType() == NotifyEventType::GETFOCUS )
+ MarkToBeReformatted( false );
+ else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) )
+ Reformat();
+ }
+
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void TimeBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ ComboBox::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) )
+ {
+ ImplResetLocaleDataWrapper();
+ ReformatAll();
+ }
+}
+
+void TimeBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void TimeBox::ReformatAll()
+{
+ OUString aStr;
+ SetUpdateMode( false );
+ const sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; ++i )
+ {
+ ImplTimeReformat( GetEntry( i ), aStr );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ TimeFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+namespace weld
+{
+ tools::Time TimeFormatter::ConvertValue(int nValue)
+ {
+ tools::Time aTime(0);
+ aTime.MakeTimeFromMS(nValue);
+ return aTime;
+ }
+
+ int TimeFormatter::ConvertValue(const tools::Time& rTime)
+ {
+ return rTime.GetMSFromTime();
+ }
+
+ void TimeFormatter::SetTime(const tools::Time& rTime)
+ {
+ auto nTime = ConvertValue(rTime);
+ bool bForceOutput = GetEntryText().isEmpty() && rTime == GetTime();
+ if (bForceOutput)
+ {
+ ImplSetValue(nTime, true);
+ return;
+ }
+ SetValue(nTime);
+ }
+
+ tools::Time TimeFormatter::GetTime()
+ {
+ return ConvertValue(GetValue());
+ }
+
+ void TimeFormatter::SetMin(const tools::Time& rNewMin)
+ {
+ SetMinValue(ConvertValue(rNewMin));
+ }
+
+ void TimeFormatter::SetMax(const tools::Time& rNewMax)
+ {
+ SetMaxValue(ConvertValue(rNewMax));
+ }
+
+ OUString TimeFormatter::FormatNumber(int nValue) const
+ {
+ const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
+ return ::TimeFormatter::FormatTime(ConvertValue(nValue), m_eFormat, m_eTimeFormat, m_bDuration, rLocaleData);
+ }
+
+ IMPL_LINK_NOARG(TimeFormatter, FormatOutputHdl, LinkParamNone*, bool)
+ {
+ OUString sText = FormatNumber(GetValue());
+ ImplSetTextImpl(sText, nullptr);
+ return true;
+ }
+
+ IMPL_LINK(TimeFormatter, ParseInputHdl, sal_Int64*, result, TriState)
+ {
+ const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
+
+ tools::Time aResult(0);
+ bool bRet = ::TimeFormatter::TextToTime(GetEntryText(), aResult, m_eFormat, m_bDuration, rLocaleDataWrapper);
+ if (bRet)
+ *result = ConvertValue(aResult);
+
+ return bRet ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+
+ IMPL_LINK(TimeFormatter, CursorChangedHdl, weld::Entry&, rEntry, void)
+ {
+ int nStartPos, nEndPos;
+ rEntry.get_selection_bounds(nStartPos, nEndPos);
+
+ const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper();
+ const int nTimeArea = ::TimeFormatter::GetTimeArea(m_eFormat, GetEntryText(), nEndPos, rLocaleData);
+
+ int nIncrements = 1;
+
+ if (nTimeArea == 1)
+ nIncrements = 1000 * 60 * 60;
+ else if (nTimeArea == 2)
+ nIncrements = 1000 * 60;
+ else if (nTimeArea == 3)
+ nIncrements = 1000;
+
+ SetSpinSize(nIncrements);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/fixed.cxx b/vcl/source/control/fixed.cxx
new file mode 100644
index 0000000000..068332e5b6
--- /dev/null
+++ b/vcl/source/control/fixed.cxx
@@ -0,0 +1,995 @@
+/* -*- 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/cvtgrf.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/fixed.hxx>
+#include <vcl/settings.hxx>
+
+#include <comphelper/base64.hxx>
+#include <comphelper/string.hxx>
+#include <sal/log.hxx>
+#include <tools/json_writer.hxx>
+#include <tools/stream.hxx>
+
+#define FIXEDLINE_TEXT_BORDER 4
+
+constexpr auto FIXEDTEXT_VIEW_STYLE = WB_3DLOOK |
+ WB_LEFT | WB_CENTER | WB_RIGHT |
+ WB_TOP | WB_VCENTER | WB_BOTTOM |
+ WB_WORDBREAK | WB_NOLABEL |
+ WB_PATHELLIPSIS;
+constexpr auto FIXEDLINE_VIEW_STYLE = WB_3DLOOK | WB_NOLABEL;
+constexpr auto FIXEDBITMAP_VIEW_STYLE = WB_3DLOOK |
+ WB_LEFT | WB_CENTER | WB_RIGHT |
+ WB_TOP | WB_VCENTER | WB_BOTTOM |
+ WB_SCALE;
+constexpr auto FIXEDIMAGE_VIEW_STYLE = WB_3DLOOK |
+ WB_LEFT | WB_CENTER | WB_RIGHT |
+ WB_TOP | WB_VCENTER | WB_BOTTOM |
+ WB_SCALE;
+
+static Point ImplCalcPos( WinBits nStyle, const Point& rPos,
+ const Size& rObjSize, const Size& rWinSize )
+{
+ tools::Long nX;
+ tools::Long nY;
+
+ if ( nStyle & WB_LEFT )
+ nX = 0;
+ else if ( nStyle & WB_RIGHT )
+ nX = rWinSize.Width()-rObjSize.Width();
+ else
+ nX = (rWinSize.Width()-rObjSize.Width())/2;
+
+ if ( nStyle & WB_TOP )
+ nY = 0;
+ else if ( nStyle & WB_BOTTOM )
+ nY = rWinSize.Height()-rObjSize.Height();
+ else
+ nY = (rWinSize.Height()-rObjSize.Height())/2;
+
+ Point aPos( nX+rPos.X(), nY+rPos.Y() );
+ return aPos;
+}
+
+void FixedText::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle( nStyle );
+ Control::ImplInit( pParent, nStyle, nullptr );
+ ApplySettings(*GetOutDev());
+}
+
+WinBits FixedText::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+const vcl::Font& FixedText::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetLabelFont();
+}
+
+const Color& FixedText::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetLabelTextColor();
+}
+
+FixedText::FixedText( vcl::Window* pParent, WinBits nStyle )
+ : Control(WindowType::FIXEDTEXT)
+ , m_nMaxWidthChars(-1)
+ , m_nMinWidthChars(-1)
+ , m_pMnemonicWindow(nullptr)
+{
+ ImplInit( pParent, nStyle );
+}
+
+DrawTextFlags FixedText::ImplGetTextStyle( WinBits nWinStyle )
+{
+ DrawTextFlags nTextStyle = DrawTextFlags::Mnemonic | DrawTextFlags::EndEllipsis;
+
+ if( ! (nWinStyle & WB_NOMULTILINE) )
+ nTextStyle |= DrawTextFlags::MultiLine;
+
+ if ( nWinStyle & WB_RIGHT )
+ nTextStyle |= DrawTextFlags::Right;
+ else if ( nWinStyle & WB_CENTER )
+ nTextStyle |= DrawTextFlags::Center;
+ else
+ nTextStyle |= DrawTextFlags::Left;
+ if ( nWinStyle & WB_BOTTOM )
+ nTextStyle |= DrawTextFlags::Bottom;
+ else if ( nWinStyle & WB_VCENTER )
+ nTextStyle |= DrawTextFlags::VCenter;
+ else
+ nTextStyle |= DrawTextFlags::Top;
+ if ( nWinStyle & WB_WORDBREAK )
+ nTextStyle |= DrawTextFlags::WordBreak;
+ if ( nWinStyle & WB_NOLABEL )
+ nTextStyle &= ~DrawTextFlags::Mnemonic;
+
+ return nTextStyle;
+}
+
+void FixedText::ImplDraw(OutputDevice* pDev, SystemTextColorFlags nSystemTextColorFlags,
+ const Point& rPos, const Size& rSize,
+ bool bFillLayout) const
+{
+ const StyleSettings& rStyleSettings = pDev->GetSettings().GetStyleSettings();
+ WinBits nWinStyle = GetStyle();
+ OUString aText(GetText());
+ DrawTextFlags nTextStyle = FixedText::ImplGetTextStyle( nWinStyle );
+ Point aPos = rPos;
+
+ if ( nWinStyle & WB_EXTRAOFFSET )
+ aPos.AdjustX(2 );
+
+ if ( nWinStyle & WB_PATHELLIPSIS )
+ {
+ nTextStyle &= ~DrawTextFlags(DrawTextFlags::EndEllipsis | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak);
+ nTextStyle |= DrawTextFlags::PathEllipsis;
+ }
+ if ( !IsEnabled() )
+ nTextStyle |= DrawTextFlags::Disable;
+ if ( (nSystemTextColorFlags & SystemTextColorFlags::Mono) ||
+ (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono) )
+ nTextStyle |= DrawTextFlags::Mono;
+
+ if( bFillLayout )
+ mxLayoutData->m_aDisplayText.clear();
+
+ const tools::Rectangle aRect(aPos, rSize);
+ DrawControlText(*pDev, aRect, aText, nTextStyle,
+ bFillLayout ? &mxLayoutData->m_aUnicodeBoundRects : nullptr,
+ bFillLayout ? &mxLayoutData->m_aDisplayText : nullptr);
+}
+
+void FixedText::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ Control::ApplySettings(rRenderContext);
+
+ vcl::Window* pParent = GetParent();
+ bool bEnableTransparent = true;
+ if (!pParent->IsChildTransparentModeEnabled() || IsControlBackground())
+ {
+ EnableChildTransparentMode(false);
+ SetParentClipMode();
+ SetPaintTransparent(false);
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else
+ rRenderContext.SetBackground(pParent->GetBackground());
+
+ if (rRenderContext.IsBackground())
+ bEnableTransparent = false;
+ }
+
+ if (bEnableTransparent)
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode(ParentClipMode::NoClip);
+ SetPaintTransparent(true);
+ rRenderContext.SetBackground();
+ }
+}
+
+void FixedText::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ ImplDraw(&rRenderContext, SystemTextColorFlags::NONE, Point(), GetOutputSizePixel());
+}
+
+void FixedText::Draw( OutputDevice* pDev, const Point& rPos,
+ SystemTextColorFlags nFlags )
+{
+ ApplySettings(*pDev);
+
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ vcl::Font aFont = GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ if ( nFlags & SystemTextColorFlags::Mono )
+ pDev->SetTextColor( COL_BLACK );
+ else
+ pDev->SetTextColor( GetTextColor() );
+ pDev->SetTextFillColor();
+
+ bool bBorder = (GetStyle() & WB_BORDER);
+ bool bBackground = IsControlBackground();
+ if ( bBorder || bBackground )
+ {
+ tools::Rectangle aRect( aPos, aSize );
+ if ( bBorder )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ if ( bBackground )
+ {
+ pDev->SetFillColor( GetControlBackground() );
+ pDev->DrawRect( aRect );
+ }
+ }
+
+ ImplDraw( pDev, nFlags, aPos, aSize );
+ pDev->Pop();
+}
+
+void FixedText::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void FixedText::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ if ( (GetPrevStyle() & FIXEDTEXT_VIEW_STYLE) !=
+ (GetStyle() & FIXEDTEXT_VIEW_STYLE) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+void FixedText::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+Size FixedText::getTextDimensions(Control const *pControl, const OUString &rTxt, tools::Long nMaxWidth)
+{
+ DrawTextFlags nStyle = ImplGetTextStyle( pControl->GetStyle() );
+ if ( !( pControl->GetStyle() & WB_NOLABEL ) )
+ nStyle |= DrawTextFlags::Mnemonic;
+
+ return pControl->GetTextRect(tools::Rectangle( Point(), Size(nMaxWidth, 0x7fffffff)),
+ rTxt, nStyle).GetSize();
+}
+
+Size FixedText::CalcMinimumTextSize( Control const *pControl, tools::Long nMaxWidth )
+{
+ Size aSize = getTextDimensions(pControl, pControl->GetText(), nMaxWidth);
+
+ if ( pControl->GetStyle() & WB_EXTRAOFFSET )
+ aSize.AdjustWidth(2 );
+
+ // GetTextRect cannot take an empty string
+ if ( aSize.Width() < 0 )
+ aSize.setWidth( 0 );
+ if ( aSize.Height() <= 0 )
+ aSize.setHeight( pControl->GetTextHeight() );
+
+ return aSize;
+}
+
+Size FixedText::CalcMinimumSize( tools::Long nMaxWidth ) const
+{
+ return CalcWindowSize( CalcMinimumTextSize ( this, nMaxWidth ) );
+}
+
+Size FixedText::GetOptimalSize() const
+{
+ sal_Int32 nMaxAvailWidth = 0x7fffffff;
+ if (m_nMaxWidthChars != -1)
+ {
+ OUStringBuffer aBuf(m_nMaxWidthChars);
+ comphelper::string::padToLength(aBuf, m_nMaxWidthChars, 'x');
+ nMaxAvailWidth = getTextDimensions(this,
+ aBuf.makeStringAndClear(), 0x7fffffff).Width();
+ }
+ Size aRet = CalcMinimumSize(nMaxAvailWidth);
+ if (m_nMinWidthChars != -1)
+ {
+ OUStringBuffer aBuf(m_nMinWidthChars);
+ comphelper::string::padToLength(aBuf, m_nMinWidthChars, 'x');
+ Size aMinAllowed = getTextDimensions(this,
+ aBuf.makeStringAndClear(), 0x7fffffff);
+ aRet.setWidth(std::max(aMinAllowed.Width(), aRet.Width()));
+ }
+ return aRet;
+}
+
+void FixedText::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ ImplDraw(const_cast<FixedText*>(this)->GetOutDev(), SystemTextColorFlags::NONE, Point(), GetOutputSizePixel(), true);
+ //const_cast<FixedText*>(this)->Invalidate();
+}
+
+void FixedText::setMaxWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != m_nMaxWidthChars)
+ {
+ m_nMaxWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+void FixedText::setMinWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != m_nMinWidthChars)
+ {
+ m_nMinWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+bool FixedText::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "max-width-chars")
+ setMaxWidthChars(rValue.toInt32());
+ else if (rKey == "width-chars")
+ setMinWidthChars(rValue.toInt32());
+ else if (rKey == "ellipsize")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~WB_PATHELLIPSIS;
+ if (rValue != "none")
+ {
+ SAL_WARN_IF(rValue != "end", "vcl.layout", "Only endellipsis support for now");
+ nBits |= WB_PATHELLIPSIS;
+ }
+ SetStyle(nBits);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+vcl::Window* FixedText::getAccessibleRelationLabelFor() const
+{
+ vcl::Window *pWindow = Control::getAccessibleRelationLabelFor();
+ if (pWindow)
+ return pWindow;
+ return get_mnemonic_widget();
+}
+
+void FixedText::set_mnemonic_widget(vcl::Window *pWindow)
+{
+ if (pWindow == m_pMnemonicWindow)
+ return;
+ if (m_pMnemonicWindow)
+ {
+ vcl::Window *pTempReEntryGuard = m_pMnemonicWindow;
+ m_pMnemonicWindow = nullptr;
+ pTempReEntryGuard->remove_mnemonic_label(this);
+ }
+ m_pMnemonicWindow = pWindow;
+ if (m_pMnemonicWindow)
+ m_pMnemonicWindow->add_mnemonic_label(this);
+}
+
+FixedText::~FixedText()
+{
+ disposeOnce();
+}
+
+void FixedText::dispose()
+{
+ set_mnemonic_widget(nullptr);
+ m_pMnemonicWindow.clear();
+ Control::dispose();
+}
+
+SelectableFixedText::SelectableFixedText(vcl::Window* pParent, WinBits nStyle)
+ : Edit(pParent, nStyle)
+{
+ // no border
+ SetBorderStyle( WindowBorderStyle::NOBORDER );
+ // read-only
+ SetReadOnly();
+ // make it transparent
+ SetPaintTransparent(true);
+ SetControlBackground();
+}
+
+void SelectableFixedText::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetBackground();
+}
+
+void SelectableFixedText::LoseFocus()
+{
+ Edit::LoseFocus();
+ // clear cursor
+ Invalidate();
+}
+
+void SelectableFixedText::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Edit::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("type", "fixedtext");
+ rJsonWriter.put("selectable", true);
+}
+
+void FixedLine::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle( nStyle );
+ Control::ImplInit( pParent, nStyle, nullptr );
+ ApplySettings(*GetOutDev());
+}
+
+WinBits FixedLine::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+const vcl::Font& FixedLine::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetGroupFont();
+}
+
+const Color& FixedLine::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetGroupTextColor();
+}
+
+void FixedLine::ImplDraw(vcl::RenderContext& rRenderContext)
+{
+ // we need to measure according to the window, not according to the
+ // RenderContext we paint to
+ Size aOutSize = GetOutputSizePixel();
+
+ OUString aText = GetText();
+ WinBits nWinStyle = GetStyle();
+
+ DecorationView aDecoView(&rRenderContext);
+ if (aText.isEmpty())
+ {
+ if (nWinStyle & WB_VERT)
+ {
+ tools::Long nX = (aOutSize.Width() - 1) / 2;
+ aDecoView.DrawSeparator(Point(nX, 0), Point(nX, aOutSize.Height() - 1));
+ }
+ else
+ {
+ tools::Long nY = (aOutSize.Height() - 1) / 2;
+ aDecoView.DrawSeparator(Point(0, nY), Point(aOutSize.Width() - 1, nY), false);
+ }
+ }
+ else if (nWinStyle & WB_VERT)
+ {
+ tools::Long nWidth = rRenderContext.GetTextWidth(aText);
+ rRenderContext.Push(vcl::PushFlags::FONT);
+ vcl::Font aFont(rRenderContext.GetFont());
+ aFont.SetOrientation(900_deg10);
+ SetFont(aFont);
+ Point aStartPt(aOutSize.Width() / 2, aOutSize.Height() - 1);
+ if (nWinStyle & WB_VCENTER)
+ aStartPt.AdjustY( -((aOutSize.Height() - nWidth) / 2) );
+ Point aTextPt(aStartPt);
+ aTextPt.AdjustX( -(GetTextHeight() / 2) );
+ rRenderContext.DrawText(aTextPt, aText, 0, aText.getLength());
+ rRenderContext.Pop();
+ if (aOutSize.Height() - aStartPt.Y() > FIXEDLINE_TEXT_BORDER)
+ aDecoView.DrawSeparator(Point(aStartPt.X(), aStartPt.Y() + FIXEDLINE_TEXT_BORDER),
+ Point(aStartPt.X(), aOutSize.Height() - 1));
+ if (aStartPt.Y() - nWidth - FIXEDLINE_TEXT_BORDER > 0)
+ aDecoView.DrawSeparator(Point(aStartPt.X(), 0),
+ Point(aStartPt.X(), aStartPt.Y() - nWidth - FIXEDLINE_TEXT_BORDER));
+ }
+ else
+ {
+ DrawTextFlags nStyle = DrawTextFlags::Mnemonic | DrawTextFlags::Left | DrawTextFlags::VCenter | DrawTextFlags::EndEllipsis;
+ tools::Rectangle aRect(0, 0, aOutSize.Width(), aOutSize.Height());
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ if (nWinStyle & WB_CENTER)
+ nStyle |= DrawTextFlags::Center;
+
+ if (!IsEnabled())
+ nStyle |= DrawTextFlags::Disable;
+ if (GetStyle() & WB_NOLABEL)
+ nStyle &= ~DrawTextFlags::Mnemonic;
+ if (rStyleSettings.GetOptions() & StyleSettingsOptions::Mono)
+ nStyle |= DrawTextFlags::Mono;
+
+ aRect = DrawControlText(*GetOutDev(), aRect, aText, nStyle, nullptr, nullptr);
+
+ tools::Long nTop = aRect.Top() + ((aRect.GetHeight() - 1) / 2);
+ aDecoView.DrawSeparator(Point(aRect.Right() + FIXEDLINE_TEXT_BORDER, nTop), Point(aOutSize.Width() - 1, nTop), false);
+ if (aRect.Left() > FIXEDLINE_TEXT_BORDER)
+ aDecoView.DrawSeparator(Point(0, nTop), Point(aRect.Left() - FIXEDLINE_TEXT_BORDER, nTop), false);
+ }
+}
+
+FixedLine::FixedLine( vcl::Window* pParent, WinBits nStyle ) :
+ Control( WindowType::FIXEDLINE )
+{
+ ImplInit( pParent, nStyle );
+ SetSizePixel( Size( 2, 2 ) );
+}
+
+void FixedLine::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const_cast<FixedLine*>(this)->Invalidate();
+}
+
+void FixedLine::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ Control::ApplySettings(rRenderContext);
+
+ vcl::Window* pParent = GetParent();
+ if (pParent->IsChildTransparentModeEnabled() && !IsControlBackground())
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode(ParentClipMode::NoClip);
+ SetPaintTransparent(true);
+ rRenderContext.SetBackground();
+ }
+ else
+ {
+ EnableChildTransparentMode(false);
+ SetParentClipMode();
+ SetPaintTransparent(false);
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else
+ rRenderContext.SetBackground(pParent->GetBackground());
+ }
+}
+
+void FixedLine::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImplDraw(rRenderContext);
+}
+
+void FixedLine::Draw( OutputDevice*, const Point&, SystemTextColorFlags )
+{
+}
+
+void FixedLine::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void FixedLine::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Text) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ if ( (GetPrevStyle() & FIXEDLINE_VIEW_STYLE) !=
+ (GetStyle() & FIXEDLINE_VIEW_STYLE) )
+ Invalidate();
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::Style) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+void FixedLine::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+Size FixedLine::GetOptimalSize() const
+{
+ return CalcWindowSize( FixedText::CalcMinimumTextSize ( this ) );
+}
+
+void FixedLine::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("type", "separator");
+ rJsonWriter.put("orientation", (GetStyle() & WB_VERT) ? "vertical" : "horizontal");
+}
+
+void FixedBitmap::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle( nStyle );
+ Control::ImplInit( pParent, nStyle, nullptr );
+ ApplySettings(*GetOutDev());
+}
+
+WinBits FixedBitmap::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+FixedBitmap::FixedBitmap( vcl::Window* pParent, WinBits nStyle ) :
+ Control( WindowType::FIXEDBITMAP )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void FixedBitmap::ImplDraw( OutputDevice* pDev, const Point& rPos, const Size& rSize )
+{
+ // do we have a Bitmap?
+ if ( !maBitmap.IsEmpty() )
+ {
+ if ( GetStyle() & WB_SCALE )
+ pDev->DrawBitmapEx( rPos, rSize, maBitmap );
+ else
+ {
+ Point aPos = ImplCalcPos( GetStyle(), rPos, maBitmap.GetSizePixel(), rSize );
+ pDev->DrawBitmapEx( aPos, maBitmap );
+ }
+ }
+}
+
+void FixedBitmap::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ vcl::Window* pParent = GetParent();
+ if (pParent->IsChildTransparentModeEnabled() && !IsControlBackground())
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode(ParentClipMode::NoClip);
+ SetPaintTransparent(true);
+ rRenderContext.SetBackground();
+ }
+ else
+ {
+ EnableChildTransparentMode(false);
+ SetParentClipMode();
+ SetPaintTransparent(false);
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else
+ rRenderContext.SetBackground(pParent->GetBackground());
+ }
+}
+
+void FixedBitmap::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImplDraw(&rRenderContext, Point(), GetOutputSizePixel());
+}
+
+void FixedBitmap::Draw( OutputDevice* pDev, const Point& rPos,
+ SystemTextColorFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ tools::Rectangle aRect( aPos, aSize );
+
+ pDev->Push();
+ pDev->SetMapMode();
+
+ // Border
+ if ( GetStyle() & WB_BORDER )
+ {
+ DecorationView aDecoView( pDev );
+ aRect = aDecoView.DrawFrame( aRect, DrawFrameStyle::DoubleIn );
+ }
+ pDev->IntersectClipRegion( aRect );
+ ImplDraw( pDev, aRect.TopLeft(), aRect.GetSize() );
+
+ pDev->Pop();
+}
+
+void FixedBitmap::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void FixedBitmap::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( (nType == StateChangedType::Data) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ if ( (GetPrevStyle() & FIXEDBITMAP_VIEW_STYLE) !=
+ (GetStyle() & FIXEDBITMAP_VIEW_STYLE) )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+void FixedBitmap::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+void FixedBitmap::SetBitmap( const BitmapEx& rBitmap )
+{
+ maBitmap = rBitmap;
+ CompatStateChanged( StateChangedType::Data );
+ queue_resize();
+}
+
+void FixedImage::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle( nStyle );
+ Control::ImplInit( pParent, nStyle, nullptr );
+ ApplySettings(*GetOutDev());
+}
+
+WinBits FixedImage::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+FixedImage::FixedImage( vcl::Window* pParent, WinBits nStyle ) :
+ Control( WindowType::FIXEDIMAGE )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void FixedImage::ImplDraw( OutputDevice* pDev,
+ const Point& rPos, const Size& rSize )
+{
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+ if ( !IsEnabled() )
+ nStyle |= DrawImageFlags::Disable;
+
+ Image *pImage = &maImage;
+
+ // do we have an image?
+ if ( !(!(*pImage)) )
+ {
+ if ( GetStyle() & WB_SCALE )
+ pDev->DrawImage( rPos, rSize, *pImage, nStyle );
+ else
+ {
+ Point aPos = ImplCalcPos( GetStyle(), rPos, pImage->GetSizePixel(), rSize );
+ pDev->DrawImage( aPos, *pImage, nStyle );
+ }
+ }
+}
+
+void FixedImage::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ vcl::Window* pParent = GetParent();
+ if (pParent && pParent->IsChildTransparentModeEnabled() && !IsControlBackground())
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode(ParentClipMode::NoClip);
+ SetPaintTransparent(true);
+ rRenderContext.SetBackground();
+ }
+ else
+ {
+ EnableChildTransparentMode(false);
+ SetParentClipMode();
+ SetPaintTransparent(false);
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else if (pParent)
+ rRenderContext.SetBackground(pParent->GetBackground());
+ }
+}
+
+
+void FixedImage::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImplDraw(&rRenderContext, Point(), GetOutputSizePixel());
+}
+
+Size FixedImage::GetOptimalSize() const
+{
+ return maImage.GetSizePixel();
+}
+
+void FixedImage::Draw( OutputDevice* pDev, const Point& rPos,
+ SystemTextColorFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ tools::Rectangle aRect( aPos, aSize );
+
+ pDev->Push();
+ pDev->SetMapMode();
+
+ // Border
+ if ( GetStyle() & WB_BORDER )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ pDev->IntersectClipRegion( aRect );
+ ImplDraw( pDev, aRect.TopLeft(), aRect.GetSize() );
+
+ pDev->Pop();
+}
+
+void FixedImage::Resize()
+{
+ Control::Resize();
+ Invalidate();
+}
+
+void FixedImage::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( (nType == StateChangedType::Enable) ||
+ (nType == StateChangedType::Data) ||
+ (nType == StateChangedType::UpdateMode) )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ if ( (GetPrevStyle() & FIXEDIMAGE_VIEW_STYLE) !=
+ (GetStyle() & FIXEDIMAGE_VIEW_STYLE) )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+void FixedImage::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+}
+
+void FixedImage::SetImage( const Image& rImage )
+{
+ if ( rImage != maImage )
+ {
+ maImage = rImage;
+ CompatStateChanged( StateChangedType::Data );
+ queue_resize();
+ }
+}
+
+Image FixedImage::loadThemeImage(const OUString &rFileName)
+{
+ return Image(StockImage::Yes, rFileName);
+}
+
+bool FixedImage::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "icon-size")
+ {
+ WinBits nBits = GetStyle();
+ nBits &= ~WB_SMALLSTYLE;
+ if (rValue == "2")
+ nBits |= WB_SMALLSTYLE;
+ SetStyle(nBits);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+void FixedImage::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+ rJsonWriter.put("id", get_id());
+ rJsonWriter.put("type", "image");
+ if (!!maImage)
+ {
+ SvMemoryStream aOStm(6535, 6535);
+ if(GraphicConverter::Export(aOStm, maImage.GetBitmapEx(), ConvertDataFormat::PNG) == ERRCODE_NONE)
+ {
+ css::uno::Sequence<sal_Int8> aSeq( static_cast<sal_Int8 const *>(aOStm.GetData()), aOStm.Tell());
+ OStringBuffer aBuffer("data:image/png;base64,");
+ ::comphelper::Base64::encode(aBuffer, aSeq);
+ rJsonWriter.put("image", aBuffer);
+ }
+ }
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/fixedhyper.cxx b/vcl/source/control/fixedhyper.cxx
new file mode 100644
index 0000000000..5b4960d92f
--- /dev/null
+++ b/vcl/source/control/fixedhyper.cxx
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/event.hxx>
+#include <vcl/toolkit/fixedhyper.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <comphelper/anytostring.hxx>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+
+#include <com/sun/star/system/XSystemShellExecute.hpp>
+#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
+#include <com/sun/star/system/SystemShellExecute.hpp>
+
+using namespace css;
+
+FixedHyperlink::FixedHyperlink(vcl::Window* pParent, WinBits nWinStyle)
+ : FixedText(pParent, nWinStyle)
+ , m_nTextLen(0)
+ , m_aOldPointer(PointerStyle::Arrow)
+{
+ Initialize();
+}
+
+void FixedHyperlink::Initialize()
+{
+ // saves the old pointer
+ m_aOldPointer = GetPointer();
+ // changes the font
+ vcl::Font aFont = GetControlFont( );
+ // to underline
+ aFont.SetUnderline( LINESTYLE_SINGLE );
+ SetControlFont( aFont );
+ // changes the color to link color
+ SetControlForeground( Application::GetSettings().GetStyleSettings().GetLinkColor() );
+ // calculates text len
+ m_nTextLen = GetOutDev()->GetCtrlTextWidth( GetText() );
+
+ SetClickHdl(LINK(this, FixedHyperlink, HandleClick));
+}
+
+bool FixedHyperlink::ImplIsOverText(Point aPosition) const
+{
+ Size aSize = GetOutputSizePixel();
+
+ bool bIsOver = false;
+
+ if (GetStyle() & WB_RIGHT)
+ {
+ return aPosition.X() > (aSize.Width() - m_nTextLen);
+ }
+ else if (GetStyle() & WB_CENTER)
+ {
+ bIsOver = aPosition.X() > (aSize.Width() / 2 - m_nTextLen / 2) &&
+ aPosition.X() < (aSize.Width() / 2 + m_nTextLen / 2);
+ }
+ else
+ {
+ bIsOver = aPosition.X() < m_nTextLen;
+ }
+
+ return bIsOver;
+}
+
+void FixedHyperlink::MouseMove( const MouseEvent& rMEvt )
+{
+ // changes the pointer if the control is enabled and the mouse is over the text.
+ if ( !rMEvt.IsLeaveWindow() && IsEnabled() && ImplIsOverText(GetPointerPosPixel()) )
+ SetPointer( PointerStyle::RefHand );
+ else
+ SetPointer( m_aOldPointer );
+}
+
+void FixedHyperlink::MouseButtonUp( const MouseEvent& )
+{
+ // calls the link if the control is enabled and the mouse is over the text.
+ if ( IsEnabled() && ImplIsOverText(GetPointerPosPixel()) )
+ ImplCallEventListenersAndHandler( VclEventId::ButtonClick, [this] () { m_aClickHdl.Call(*this); } );
+}
+
+void FixedHyperlink::RequestHelp( const HelpEvent& rHEvt )
+{
+ if ( IsEnabled() && ImplIsOverText(GetPointerPosPixel()) )
+ FixedText::RequestHelp( rHEvt );
+}
+
+void FixedHyperlink::GetFocus()
+{
+ Size aSize = GetSizePixel();
+ tools::Rectangle aFocusRect(Point(1, 1), Size(m_nTextLen + 4, aSize.Height() - 2));
+ if (GetStyle() & WB_RIGHT)
+ aFocusRect.Move(aSize.Width() - aFocusRect.getOpenWidth(), 0);
+ else if (GetStyle() & WB_CENTER)
+ aFocusRect.Move((aSize.Width() - aFocusRect.getOpenWidth()) / 2, 0);
+
+ Invalidate(aFocusRect);
+ ShowFocus(aFocusRect);
+}
+
+void FixedHyperlink::LoseFocus()
+{
+ SetTextColor( GetControlForeground() );
+ Invalidate(tools::Rectangle(Point(), GetSizePixel()));
+ HideFocus();
+}
+
+void FixedHyperlink::KeyInput( const KeyEvent& rKEvt )
+{
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_SPACE:
+ case KEY_RETURN:
+ m_aClickHdl.Call( *this );
+ break;
+
+ default:
+ FixedText::KeyInput( rKEvt );
+ }
+}
+
+void FixedHyperlink::SetURL( const OUString& rNewURL )
+{
+ m_sURL = rNewURL;
+ SetQuickHelpText( m_sURL );
+}
+
+
+void FixedHyperlink::SetText(const OUString& rNewDescription)
+{
+ FixedText::SetText(rNewDescription);
+ m_nTextLen = GetOutDev()->GetCtrlTextWidth(GetText());
+}
+
+bool FixedHyperlink::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "uri")
+ SetURL(rValue);
+ else
+ return FixedText::set_property(rKey, rValue);
+ return true;
+}
+
+IMPL_LINK(FixedHyperlink, HandleClick, FixedHyperlink&, rHyperlink, void)
+{
+ if ( rHyperlink.m_sURL.isEmpty() ) // Nothing to do, when the URL is empty
+ return;
+
+ try
+ {
+ uno::Reference< system::XSystemShellExecute > xSystemShellExecute(
+ system::SystemShellExecute::create(comphelper::getProcessComponentContext()));
+ //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException
+ xSystemShellExecute->execute( rHyperlink.m_sURL, OUString(), system::SystemShellExecuteFlags::URIS_ONLY );
+ }
+ catch ( const uno::Exception& )
+ {
+ uno::Any exc(cppu::getCaughtException());
+ OUString msg(comphelper::anyToString(exc));
+ SolarMutexGuard g;
+ std::shared_ptr<weld::MessageDialog> xErrorBox(
+ Application::CreateMessageDialog(GetFrameWeld(), VclMessageType::Error, VclButtonsType::Ok, msg));
+ xErrorBox->set_title(rHyperlink.GetText());
+ xErrorBox->runAsync(xErrorBox, [](sal_Int32){});
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/fmtfield.cxx b/vcl/source/control/fmtfield.cxx
new file mode 100644
index 0000000000..fc7bdfee6f
--- /dev/null
+++ b/vcl/source/control/fmtfield.cxx
@@ -0,0 +1,1366 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/debug.hxx>
+#include <boost/property_tree/json_parser.hpp>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/commandevent.hxx>
+#include <svl/zformat.hxx>
+#include <vcl/toolkit/fmtfield.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/uitest/formattedfielduiobject.hxx>
+#include <vcl/weld.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <unotools/syslocale.hxx>
+#include <limits>
+#include <map>
+#include <rtl/math.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <svl/numformat.hxx>
+#include <osl/diagnose.h>
+#include <tools/json_writer.hxx>
+
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::util;
+
+// hmm. No support for regular expression. Well, I always (not really :) wanted to write a finite automat
+// so here comes a finite automat ...
+
+namespace validation
+{
+ static void lcl_insertStopTransition( StateTransitions& _rRow )
+ {
+ _rRow.insert( Transition( '_', END ) );
+ }
+
+ static void lcl_insertStartExponentTransition( StateTransitions& _rRow )
+ {
+ _rRow.insert( Transition( 'e', EXPONENT_START ) );
+ }
+
+ static void lcl_insertSignTransitions( StateTransitions& _rRow, const State eNextState )
+ {
+ _rRow.insert( Transition( '-', eNextState ) );
+ _rRow.insert( Transition( '+', eNextState ) );
+ }
+
+ static void lcl_insertDigitTransitions( StateTransitions& _rRow, const State eNextState )
+ {
+ for ( sal_Unicode aChar = '0'; aChar <= '9'; ++aChar )
+ _rRow.insert( Transition( aChar, eNextState ) );
+ }
+
+ static void lcl_insertCommonPreCommaTransitions( StateTransitions& _rRow, const sal_Unicode _cThSep, const sal_Unicode _cDecSep )
+ {
+ // digits are allowed
+ lcl_insertDigitTransitions( _rRow, DIGIT_PRE_COMMA );
+
+ // the thousand separator is allowed
+ _rRow.insert( Transition( _cThSep, DIGIT_PRE_COMMA ) );
+
+ // a comma is allowed
+ _rRow.insert( Transition( _cDecSep, DIGIT_POST_COMMA ) );
+ }
+
+ NumberValidator::NumberValidator( const sal_Unicode _cThSep, const sal_Unicode _cDecSep )
+ {
+ // build up our transition table
+
+ // how to proceed from START
+ {
+ StateTransitions& rRow = m_aTransitions[ START ];
+ rRow.insert( Transition( '_', NUM_START ) );
+ // if we encounter the normalizing character, we want to proceed with the number
+ }
+
+ // how to proceed from NUM_START
+ {
+ StateTransitions& rRow = m_aTransitions[ NUM_START ];
+
+ // a sign is allowed
+ lcl_insertSignTransitions( rRow, DIGIT_PRE_COMMA );
+
+ // common transitions for the two pre-comma states
+ lcl_insertCommonPreCommaTransitions( rRow, _cThSep, _cDecSep );
+
+ // the exponent may start here
+ // (this would mean string like "_+e10_", but this is a valid fragment, though no valid number)
+ lcl_insertStartExponentTransition( rRow );
+ }
+
+ // how to proceed from DIGIT_PRE_COMMA
+ {
+ StateTransitions& rRow = m_aTransitions[ DIGIT_PRE_COMMA ];
+
+ // common transitions for the two pre-comma states
+ lcl_insertCommonPreCommaTransitions( rRow, _cThSep, _cDecSep );
+
+ // the exponent may start here
+ lcl_insertStartExponentTransition( rRow );
+
+ // the final transition indicating the end of the string
+ // (if there is no comma and no post-comma, then the string may end here)
+ lcl_insertStopTransition( rRow );
+ }
+
+ // how to proceed from DIGIT_POST_COMMA
+ {
+ StateTransitions& rRow = m_aTransitions[ DIGIT_POST_COMMA ];
+
+ // there might be digits, which would keep the state at DIGIT_POST_COMMA
+ lcl_insertDigitTransitions( rRow, DIGIT_POST_COMMA );
+
+ // the exponent may start here
+ lcl_insertStartExponentTransition( rRow );
+
+ // the string may end here
+ lcl_insertStopTransition( rRow );
+ }
+
+ // how to proceed from EXPONENT_START
+ {
+ StateTransitions& rRow = m_aTransitions[ EXPONENT_START ];
+
+ // there may be a sign
+ lcl_insertSignTransitions( rRow, EXPONENT_DIGIT );
+
+ // there may be digits
+ lcl_insertDigitTransitions( rRow, EXPONENT_DIGIT );
+
+ // the string may end here
+ lcl_insertStopTransition( rRow );
+ }
+
+ // how to proceed from EXPONENT_DIGIT
+ {
+ StateTransitions& rRow = m_aTransitions[ EXPONENT_DIGIT ];
+
+ // there may be digits
+ lcl_insertDigitTransitions( rRow, EXPONENT_DIGIT );
+
+ // the string may end here
+ lcl_insertStopTransition( rRow );
+ }
+
+ // how to proceed from END
+ {
+ /*StateTransitions& rRow =*/ m_aTransitions[ EXPONENT_DIGIT ];
+ // no valid transition to leave this state
+ // (note that we, for consistency, nevertheless want to have a row in the table)
+ }
+ }
+
+ bool NumberValidator::implValidateNormalized( const OUString& _rText )
+ {
+ const sal_Unicode* pCheckPos = _rText.getStr();
+ State eCurrentState = START;
+
+ while ( END != eCurrentState )
+ {
+ // look up the transition row for the current state
+ TransitionTable::const_iterator aRow = m_aTransitions.find( eCurrentState );
+ DBG_ASSERT( m_aTransitions.end() != aRow,
+ "NumberValidator::implValidateNormalized: invalid transition table (row not found)!" );
+
+ if ( m_aTransitions.end() != aRow )
+ {
+ // look up the current character in this row
+ StateTransitions::const_iterator aTransition = aRow->second.find( *pCheckPos );
+ if ( aRow->second.end() != aTransition )
+ {
+ // there is a valid transition for this character
+ eCurrentState = aTransition->second;
+ ++pCheckPos;
+ continue;
+ }
+ }
+
+ // if we're here, there is no valid transition
+ break;
+ }
+
+ DBG_ASSERT( ( END != eCurrentState ) || ( 0 == *pCheckPos ),
+ "NumberValidator::implValidateNormalized: inconsistency!" );
+ // if we're at END, then the string should be done, too - the string should be normalized, means ending
+ // a "_" and not containing any other "_" (except at the start), and "_" is the only possibility
+ // to reach the END state
+
+ // the string is valid if and only if we reached the final state
+ return ( END == eCurrentState );
+ }
+
+ bool NumberValidator::isValidNumericFragment( std::u16string_view _rText )
+ {
+ if ( _rText.empty() )
+ // empty strings are always allowed
+ return true;
+
+ // normalize the string
+ OUString sNormalized = OUString::Concat("_") + _rText + "_";
+
+ return implValidateNormalized( sNormalized );
+ }
+}
+
+SvNumberFormatter* Formatter::StaticFormatter::s_cFormatter = nullptr;
+sal_uLong Formatter::StaticFormatter::s_nReferences = 0;
+
+SvNumberFormatter* Formatter::StaticFormatter::GetFormatter()
+{
+ if (!s_cFormatter)
+ {
+ // get the Office's locale and translate
+ LanguageType eSysLanguage = SvtSysLocale().GetLanguageTag().getLanguageType( false);
+ s_cFormatter = new SvNumberFormatter(
+ ::comphelper::getProcessComponentContext(),
+ eSysLanguage);
+ }
+ return s_cFormatter;
+}
+
+Formatter::StaticFormatter::StaticFormatter()
+{
+ ++s_nReferences;
+}
+
+Formatter::StaticFormatter::~StaticFormatter()
+{
+ if (--s_nReferences == 0)
+ {
+ delete s_cFormatter;
+ s_cFormatter = nullptr;
+ }
+}
+
+Formatter::Formatter()
+ :m_aLastSelection(0,0)
+ ,m_dMinValue(0)
+ ,m_dMaxValue(0)
+ ,m_bHasMin(false)
+ ,m_bHasMax(false)
+ ,m_bWrapOnLimits(false)
+ ,m_bStrictFormat(true)
+ ,m_bEnableEmptyField(true)
+ ,m_bAutoColor(false)
+ ,m_bEnableNaN(false)
+ ,m_bDisableRemainderFactor(false)
+ ,m_bDefaultValueSet(false)
+ ,m_ValueState(valueDirty)
+ ,m_dCurrentValue(0)
+ ,m_dDefaultValue(0)
+ ,m_nFormatKey(0)
+ ,m_pFormatter(nullptr)
+ ,m_dSpinSize(1)
+ ,m_dSpinFirst(-1000000)
+ ,m_dSpinLast(1000000)
+ ,m_bTreatAsNumber(true)
+ ,m_pLastOutputColor(nullptr)
+ ,m_bUseInputStringForFormatting(false)
+{
+}
+
+Formatter::~Formatter()
+{
+}
+
+void Formatter::SetFieldText(const OUString& rStr, const Selection& rNewSelection)
+{
+ SetEntryText(rStr, rNewSelection);
+ m_ValueState = valueDirty;
+}
+
+void Formatter::SetTextFormatted(const OUString& rStr)
+{
+ SAL_INFO_IF(GetOrCreateFormatter()->IsTextFormat(m_nFormatKey), "svtools",
+ "FormattedField::SetTextFormatted : valid only with text formats !");
+
+ m_sCurrentTextValue = rStr;
+
+ OUString sFormatted;
+ double dNumber = 0.0;
+ // IsNumberFormat changes the format key parameter
+ sal_uInt32 nTempFormatKey = static_cast< sal_uInt32 >( m_nFormatKey );
+ if( IsUsingInputStringForFormatting() &&
+ GetOrCreateFormatter()->IsNumberFormat(m_sCurrentTextValue, nTempFormatKey, dNumber) )
+ {
+ GetOrCreateFormatter()->GetInputLineString(dNumber, m_nFormatKey, sFormatted);
+ }
+ else
+ {
+ GetOrCreateFormatter()->GetOutputString(m_sCurrentTextValue,
+ m_nFormatKey,
+ sFormatted,
+ &m_pLastOutputColor);
+ }
+
+ // calculate the new selection
+ Selection aSel(GetEntrySelection());
+ Selection aNewSel(aSel);
+ aNewSel.Normalize();
+ sal_Int32 nNewLen = sFormatted.getLength();
+ sal_Int32 nCurrentLen = GetEntryText().getLength();
+ if ((nNewLen > nCurrentLen) && (aNewSel.Max() == nCurrentLen))
+ { // the new text is longer and the cursor was behind the last char (of the old text)
+ if (aNewSel.Min() == 0)
+ { // the whole text was selected -> select the new text on the whole, too
+ aNewSel.Max() = nNewLen;
+ if (!nCurrentLen)
+ { // there wasn't really a previous selection (as there was no previous text), we're setting a new one -> check the selection options
+ SelectionOptions nSelOptions = GetEntrySelectionOptions();
+ if (nSelOptions & SelectionOptions::ShowFirst)
+ { // selection should be from right to left -> swap min and max
+ aNewSel.Min() = aNewSel.Max();
+ aNewSel.Max() = 0;
+ }
+ }
+ }
+ else if (aNewSel.Max() == aNewSel.Min())
+ { // there was no selection -> set the cursor behind the new last char
+ aNewSel.Max() = nNewLen;
+ aNewSel.Min() = nNewLen;
+ }
+ }
+ else if (aNewSel.Max() > nNewLen)
+ aNewSel.Max() = nNewLen;
+ else
+ aNewSel = aSel; // don't use the justified version
+ SetEntryText(sFormatted, aNewSel);
+ m_ValueState = valueString;
+}
+
+OUString const & Formatter::GetTextValue() const
+{
+ if (m_ValueState != valueString )
+ {
+ const_cast<Formatter*>(this)->m_sCurrentTextValue = GetEntryText();
+ const_cast<Formatter*>(this)->m_ValueState = valueString;
+ }
+ return m_sCurrentTextValue;
+}
+
+void Formatter::EnableNotANumber(bool _bEnable)
+{
+ if ( m_bEnableNaN == _bEnable )
+ return;
+
+ m_bEnableNaN = _bEnable;
+}
+
+void Formatter::SetAutoColor(bool _bAutomatic)
+{
+ if (_bAutomatic == m_bAutoColor)
+ return;
+
+ m_bAutoColor = _bAutomatic;
+ if (m_bAutoColor)
+ {
+ // if auto color is switched on, adjust the current text color, too
+ SetEntryTextColor(m_pLastOutputColor);
+ }
+}
+
+void Formatter::Modify(bool makeValueDirty)
+{
+ if (!IsStrictFormat())
+ {
+ if(makeValueDirty)
+ m_ValueState = valueDirty;
+ FieldModified();
+ return;
+ }
+
+ OUString sCheck = GetEntryText();
+ if (CheckText(sCheck))
+ {
+ m_sLastValidText = sCheck;
+ m_aLastSelection = GetEntrySelection();
+ if(makeValueDirty)
+ m_ValueState = valueDirty;
+ }
+ else
+ {
+ ImplSetTextImpl(m_sLastValidText, &m_aLastSelection);
+ }
+
+ FieldModified();
+}
+
+void Formatter::ImplSetTextImpl(const OUString& rNew, Selection const * pNewSel)
+{
+ if (m_bAutoColor)
+ SetEntryTextColor(m_pLastOutputColor);
+
+ if (pNewSel)
+ SetEntryText(rNew, *pNewSel);
+ else
+ {
+ Selection aSel(GetEntrySelection());
+ aSel.Normalize();
+
+ sal_Int32 nNewLen = rNew.getLength();
+ sal_Int32 nCurrentLen = GetEntryText().getLength();
+
+ if ((nNewLen > nCurrentLen) && (aSel.Max() == nCurrentLen))
+ { // new text is longer and the cursor is behind the last char
+ if (aSel.Min() == 0)
+ {
+ if (!nCurrentLen)
+ { // there wasn't really a previous selection (as there was no previous text)
+ aSel.Max() = 0;
+ }
+ else
+ { // the whole text was selected -> select the new text on the whole, too
+ aSel.Max() = nNewLen;
+ }
+ }
+ else if (aSel.Max() == aSel.Min())
+ { // there was no selection -> set the cursor behind the new last char
+ aSel.Max() = nNewLen;
+ aSel.Min() = nNewLen;
+ }
+ }
+ else if (aSel.Max() > nNewLen)
+ aSel.Max() = nNewLen;
+ SetEntryText(rNew, aSel);
+ }
+
+ m_ValueState = valueDirty; // not always necessary, but better re-evaluate for safety reasons
+}
+
+void Formatter::ImplSetFormatKey(sal_uLong nFormatKey)
+{
+ m_nFormatKey = nFormatKey;
+ bool bNeedFormatter = (m_pFormatter == nullptr) && (nFormatKey != 0);
+ if (bNeedFormatter)
+ {
+ GetOrCreateFormatter(); // this creates a standard formatter
+
+ // It might happen that the standard formatter makes no sense here, but it takes a default
+ // format. Thus, it is possible to set one of the other standard keys (which are spanning
+ // across multiple formatters).
+ m_nFormatKey = nFormatKey;
+ // When calling SetFormatKey without a formatter, the key must be one of the standard values
+ // that is available for all formatters (and, thus, also in this new one).
+ DBG_ASSERT(m_pFormatter->GetEntry(nFormatKey) != nullptr, "FormattedField::ImplSetFormatKey : invalid format key !");
+ }
+}
+
+void Formatter::SetFormatKey(sal_uLong nFormatKey)
+{
+ bool bNoFormatter = (m_pFormatter == nullptr);
+ ImplSetFormatKey(nFormatKey);
+ FormatChanged((bNoFormatter && (m_pFormatter != nullptr)) ? FORMAT_CHANGE_TYPE::FORMATTER : FORMAT_CHANGE_TYPE::KEYONLY);
+}
+
+void Formatter::SetFormatter(SvNumberFormatter* pFormatter, bool bResetFormat)
+{
+
+ if (bResetFormat)
+ {
+ m_pFormatter = pFormatter;
+
+ // calc the default format key from the Office's UI locale
+ if ( m_pFormatter )
+ {
+ // get the Office's locale and translate
+ LanguageType eSysLanguage = SvtSysLocale().GetLanguageTag().getLanguageType( false);
+ // get the standard numeric format for this language
+ m_nFormatKey = m_pFormatter->GetStandardFormat( SvNumFormatType::NUMBER, eSysLanguage );
+ }
+ else
+ m_nFormatKey = 0;
+ }
+ else
+ {
+ LanguageType aOldLang;
+ OUString sOldFormat = GetFormat(aOldLang);
+
+ sal_uInt32 nDestKey = pFormatter->TestNewString(sOldFormat);
+ if (nDestKey == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ // language of the new formatter
+ const SvNumberformat* pDefaultEntry = pFormatter->GetEntry(0);
+ LanguageType aNewLang = pDefaultEntry ? pDefaultEntry->GetLanguage() : LANGUAGE_DONTKNOW;
+
+ // convert the old format string into the new language
+ sal_Int32 nCheckPos;
+ SvNumFormatType nType;
+ pFormatter->PutandConvertEntry(sOldFormat, nCheckPos, nType, nDestKey, aOldLang, aNewLang, true);
+ m_nFormatKey = nDestKey;
+ }
+ m_pFormatter = pFormatter;
+ }
+
+ FormatChanged(FORMAT_CHANGE_TYPE::FORMATTER);
+}
+
+OUString Formatter::GetFormat(LanguageType& eLang) const
+{
+ const SvNumberformat* pFormatEntry = GetOrCreateFormatter()->GetEntry(m_nFormatKey);
+ DBG_ASSERT(pFormatEntry != nullptr, "FormattedField::GetFormat: no number format for the given format key.");
+ OUString sFormatString = pFormatEntry ? pFormatEntry->GetFormatstring() : OUString();
+ eLang = pFormatEntry ? pFormatEntry->GetLanguage() : LANGUAGE_DONTKNOW;
+
+ return sFormatString;
+}
+
+bool Formatter::SetFormat(const OUString& rFormatString, LanguageType eLang)
+{
+ sal_uInt32 nNewKey = GetOrCreateFormatter()->TestNewString(rFormatString, eLang);
+ if (nNewKey == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ sal_Int32 nCheckPos;
+ SvNumFormatType nType;
+ OUString rFormat(rFormatString);
+ if (!GetOrCreateFormatter()->PutEntry(rFormat, nCheckPos, nType, nNewKey, eLang))
+ return false;
+ DBG_ASSERT(nNewKey != NUMBERFORMAT_ENTRY_NOT_FOUND, "FormattedField::SetFormatString : PutEntry returned an invalid key !");
+ }
+
+ if (nNewKey != m_nFormatKey)
+ SetFormatKey(nNewKey);
+ return true;
+}
+
+bool Formatter::GetThousandsSep() const
+{
+ DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey),
+ "FormattedField::GetThousandsSep : Are you sure what you are doing when setting the precision of a text format?");
+
+ bool bThousand, IsRed;
+ sal_uInt16 nPrecision, nLeadingCnt;
+ GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nLeadingCnt);
+
+ return bThousand;
+}
+
+void Formatter::SetThousandsSep(bool _bUseSeparator)
+{
+ DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey),
+ "FormattedField::SetThousandsSep : Are you sure what you are doing when setting the precision of a text format?");
+
+ // get the current settings
+ bool bThousand, IsRed;
+ sal_uInt16 nPrecision, nLeadingCnt;
+ GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nLeadingCnt);
+ if (bThousand == _bUseSeparator)
+ return;
+
+ // we need the language for the following
+ LanguageType eLang;
+ GetFormat(eLang);
+
+ // generate a new format ...
+ OUString sFmtDescription = GetOrCreateFormatter()->GenerateFormat(m_nFormatKey, eLang, _bUseSeparator, IsRed, nPrecision, nLeadingCnt);
+ // ... and introduce it to the formatter
+ sal_Int32 nCheckPos = 0;
+ sal_uInt32 nNewKey;
+ SvNumFormatType nType;
+ GetOrCreateFormatter()->PutEntry(sFmtDescription, nCheckPos, nType, nNewKey, eLang);
+
+ // set the new key
+ ImplSetFormatKey(nNewKey);
+ FormatChanged(FORMAT_CHANGE_TYPE::THOUSANDSSEP);
+}
+
+sal_uInt16 Formatter::GetDecimalDigits() const
+{
+ DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey),
+ "FormattedField::GetDecimalDigits : Are you sure what you are doing when setting the precision of a text format?");
+
+ bool bThousand, IsRed;
+ sal_uInt16 nPrecision, nLeadingCnt;
+ GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nLeadingCnt);
+
+ return nPrecision;
+}
+
+void Formatter::SetDecimalDigits(sal_uInt16 _nPrecision)
+{
+ DBG_ASSERT(!GetOrCreateFormatter()->IsTextFormat(m_nFormatKey),
+ "FormattedField::SetDecimalDigits : Are you sure what you are doing when setting the precision of a text format?");
+
+ // get the current settings
+ bool bThousand, IsRed;
+ sal_uInt16 nPrecision, nLeadingCnt;
+ GetOrCreateFormatter()->GetFormatSpecialInfo(m_nFormatKey, bThousand, IsRed, nPrecision, nLeadingCnt);
+ if (nPrecision == _nPrecision)
+ return;
+
+ // we need the language for the following
+ LanguageType eLang;
+ GetFormat(eLang);
+
+ // generate a new format ...
+ OUString sFmtDescription = GetOrCreateFormatter()->GenerateFormat(m_nFormatKey, eLang, bThousand, IsRed, _nPrecision, nLeadingCnt);
+ // ... and introduce it to the formatter
+ sal_Int32 nCheckPos = 0;
+ sal_uInt32 nNewKey;
+ SvNumFormatType nType;
+ GetOrCreateFormatter()->PutEntry(sFmtDescription, nCheckPos, nType, nNewKey, eLang);
+
+ // set the new key
+ ImplSetFormatKey(nNewKey);
+ FormatChanged(FORMAT_CHANGE_TYPE::PRECISION);
+}
+
+void Formatter::FormatChanged(FORMAT_CHANGE_TYPE _nWhat)
+{
+ m_pLastOutputColor = nullptr;
+
+ if ( (_nWhat == FORMAT_CHANGE_TYPE::FORMATTER) && m_pFormatter )
+ m_pFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL );
+
+ ReFormat();
+}
+
+void Formatter::EntryLostFocus()
+{
+ // special treatment for empty texts
+ if (GetEntryText().isEmpty())
+ {
+ if (!IsEmptyFieldEnabled())
+ {
+ if (TreatingAsNumber())
+ {
+ ImplSetValue(m_dCurrentValue, true);
+ Modify();
+ m_ValueState = valueDouble;
+ }
+ else
+ {
+ OUString sNew = GetTextValue();
+ if (!sNew.isEmpty())
+ SetTextFormatted(sNew);
+ else
+ SetTextFormatted(m_sDefaultText);
+ m_ValueState = valueString;
+ }
+ }
+ }
+ else
+ {
+ Commit();
+ }
+}
+
+void Formatter::Commit()
+{
+ // remember the old text
+ OUString sOld(GetEntryText());
+
+ // do the reformat
+ ReFormat();
+
+ // did the text change?
+ if (GetEntryText() != sOld)
+ { // consider the field as modified,
+ // but we already have the most recent value;
+ // don't reparse it from the text
+ // (can lead to data loss when the format is lossy,
+ // as is e.g. our default date format: 2-digit year!)
+ Modify(false);
+ }
+}
+
+void Formatter::ReFormat()
+{
+ if (!IsEmptyFieldEnabled() || !GetEntryText().isEmpty())
+ {
+ if (TreatingAsNumber())
+ {
+ double dValue = GetValue();
+ if ( m_bEnableNaN && std::isnan( dValue ) )
+ return;
+ ImplSetValue( dValue, true );
+ }
+ else
+ SetTextFormatted(GetTextValue());
+ }
+}
+
+void Formatter::SetMinValue(double dMin)
+{
+ DBG_ASSERT(m_bTreatAsNumber, "FormattedField::SetMinValue : only to be used in numeric mode !");
+
+ m_dMinValue = dMin;
+ m_bHasMin = true;
+ // for checking the current value at the new border -> ImplSetValue
+ ReFormat();
+}
+
+void Formatter::SetMaxValue(double dMax)
+{
+ DBG_ASSERT(m_bTreatAsNumber, "FormattedField::SetMaxValue : only to be used in numeric mode !");
+
+ m_dMaxValue = dMax;
+ m_bHasMax = true;
+ // for checking the current value at the new border -> ImplSetValue
+ ReFormat();
+}
+
+void Formatter::SetTextValue(const OUString& rText)
+{
+ SetFieldText(rText, Selection(0, 0));
+ ReFormat();
+}
+
+void Formatter::EnableEmptyField(bool bEnable)
+{
+ if (bEnable == m_bEnableEmptyField)
+ return;
+
+ m_bEnableEmptyField = bEnable;
+ if (!m_bEnableEmptyField && GetEntryText().isEmpty())
+ ImplSetValue(m_dCurrentValue, true);
+}
+
+void Formatter::ImplSetValue(double dVal, bool bForce)
+{
+ if (m_bHasMin && (dVal<m_dMinValue))
+ {
+ dVal = m_bWrapOnLimits ? fmod(dVal + m_dMaxValue + 1 - m_dMinValue, m_dMaxValue + 1) + m_dMinValue
+ : m_dMinValue;
+ }
+ if (m_bHasMax && (dVal>m_dMaxValue))
+ {
+ dVal = m_bWrapOnLimits ? fmod(dVal - m_dMinValue, m_dMaxValue + 1) + m_dMinValue
+ : m_dMaxValue;
+ }
+ if (!bForce && (dVal == GetValue()))
+ return;
+
+ DBG_ASSERT(GetOrCreateFormatter() != nullptr, "FormattedField::ImplSetValue : can't set a value without a formatter !");
+
+ m_ValueState = valueDouble;
+ UpdateCurrentValue(dVal);
+
+ if (!m_aOutputHdl.IsSet() || !m_aOutputHdl.Call(nullptr))
+ {
+ OUString sNewText;
+ if (GetOrCreateFormatter()->IsTextFormat(m_nFormatKey))
+ {
+ // first convert the number as string in standard format
+ OUString sTemp;
+ GetOrCreateFormatter()->GetOutputString(dVal, 0, sTemp, &m_pLastOutputColor);
+ // then encode the string in the corresponding text format
+ GetOrCreateFormatter()->GetOutputString(sTemp, m_nFormatKey, sNewText, &m_pLastOutputColor);
+ }
+ else
+ {
+ if( IsUsingInputStringForFormatting())
+ {
+ GetOrCreateFormatter()->GetInputLineString(dVal, m_nFormatKey, sNewText);
+ }
+ else
+ {
+ GetOrCreateFormatter()->GetOutputString(dVal, m_nFormatKey, sNewText, &m_pLastOutputColor);
+ }
+ }
+ ImplSetTextImpl(sNewText, nullptr);
+ DBG_ASSERT(CheckText(sNewText), "FormattedField::ImplSetValue : formatted string doesn't match the criteria !");
+ }
+
+ m_ValueState = valueDouble;
+}
+
+bool Formatter::ImplGetValue(double& dNewVal)
+{
+ dNewVal = m_dCurrentValue;
+ if (m_ValueState == valueDouble)
+ return true;
+
+ // tdf#155241 default to m_dDefaultValue only if explicitly set
+ // otherwise default to m_dCurrentValue
+ if (m_bDefaultValueSet)
+ dNewVal = m_dDefaultValue;
+
+ OUString sText(GetEntryText());
+ if (sText.isEmpty())
+ return true;
+
+ bool bUseExternalFormatterValue = false;
+ if (m_aInputHdl.IsSet())
+ {
+ sal_Int64 nResult;
+ auto eState = m_aInputHdl.Call(&nResult);
+ bUseExternalFormatterValue = eState != TRISTATE_INDET;
+ if (bUseExternalFormatterValue)
+ {
+ if (eState == TRISTATE_TRUE)
+ {
+ dNewVal = nResult;
+ dNewVal /= weld::SpinButton::Power10(GetDecimalDigits());
+ }
+ else
+ dNewVal = m_dCurrentValue;
+ }
+ }
+
+ if (!bUseExternalFormatterValue)
+ {
+ DBG_ASSERT(GetOrCreateFormatter() != nullptr, "FormattedField::ImplGetValue : can't give you a current value without a formatter !");
+
+ sal_uInt32 nFormatKey = m_nFormatKey; // IsNumberFormat changes the FormatKey!
+
+ if (GetOrCreateFormatter()->IsTextFormat(nFormatKey) && m_bTreatAsNumber)
+ // for detection of values like "1,1" in fields that are formatted as text
+ nFormatKey = 0;
+
+ // special treatment for percentage formatting
+ if (GetOrCreateFormatter()->GetType(m_nFormatKey) == SvNumFormatType::PERCENT)
+ {
+ // the language of our format
+ LanguageType eLanguage = m_pFormatter->GetEntry(m_nFormatKey)->GetLanguage();
+ // the default number format for this language
+ sal_uLong nStandardNumericFormat = m_pFormatter->GetStandardFormat(SvNumFormatType::NUMBER, eLanguage);
+
+ sal_uInt32 nTempFormat = nStandardNumericFormat;
+ double dTemp;
+ if (m_pFormatter->IsNumberFormat(sText, nTempFormat, dTemp) &&
+ SvNumFormatType::NUMBER == m_pFormatter->GetType(nTempFormat))
+ // the string is equivalent to a number formatted one (has no % sign) -> append it
+ sText += "%";
+ // (with this, an input of '3' becomes '3%', which then by the formatter is translated
+ // into 0.03. Without this, the formatter would give us the double 3 for an input '3',
+ // which equals 300 percent.
+ }
+ if (!GetOrCreateFormatter()->IsNumberFormat(sText, nFormatKey, dNewVal))
+ return false;
+ }
+
+ if (m_bHasMin && (dNewVal<m_dMinValue))
+ dNewVal = m_dMinValue;
+ if (m_bHasMax && (dNewVal>m_dMaxValue))
+ dNewVal = m_dMaxValue;
+ return true;
+}
+
+void Formatter::SetValue(double dVal)
+{
+ ImplSetValue(dVal, m_ValueState != valueDouble);
+}
+
+double Formatter::GetValue()
+{
+ if ( !ImplGetValue( m_dCurrentValue ) )
+ UpdateCurrentValue(m_bEnableNaN ? std::numeric_limits<double>::quiet_NaN() : m_dDefaultValue);
+
+ m_ValueState = valueDouble;
+ return m_dCurrentValue;
+}
+
+void Formatter::DisableRemainderFactor()
+{
+ m_bDisableRemainderFactor = true;
+}
+
+void Formatter::UseInputStringForFormatting()
+{
+ m_bUseInputStringForFormatting = true;
+}
+
+namespace
+{
+ class FieldFormatter : public Formatter
+ {
+ private:
+ FormattedField& m_rSpinButton;
+ public:
+ FieldFormatter(FormattedField& rSpinButton)
+ : m_rSpinButton(rSpinButton)
+ {
+ }
+
+ // Formatter overrides
+ virtual Selection GetEntrySelection() const override
+ {
+ return m_rSpinButton.GetSelection();
+ }
+
+ virtual OUString GetEntryText() const override
+ {
+ return m_rSpinButton.GetText();
+ }
+
+ void SetEntryText(const OUString& rText, const Selection& rSel) override
+ {
+ m_rSpinButton.SpinField::SetText(rText, rSel);
+ }
+
+ virtual void SetEntryTextColor(const ::Color* pColor) override
+ {
+ if (pColor)
+ m_rSpinButton.SetControlForeground(*pColor);
+ else
+ m_rSpinButton.SetControlForeground();
+ }
+
+ virtual SelectionOptions GetEntrySelectionOptions() const override
+ {
+ return m_rSpinButton.GetSettings().GetStyleSettings().GetSelectionOptions();
+ }
+
+ virtual void FieldModified() override
+ {
+ m_rSpinButton.SpinField::Modify();
+ }
+
+ virtual void UpdateCurrentValue(double dCurrentValue) override
+ {
+ Formatter::UpdateCurrentValue(dCurrentValue);
+ m_rSpinButton.SetUpperEnabled(!m_bHasMax || dCurrentValue < m_dMaxValue);
+ m_rSpinButton.SetLowerEnabled(!m_bHasMin || dCurrentValue > m_dMinValue);
+ }
+ };
+
+ class DoubleNumericFormatter : public FieldFormatter
+ {
+ private:
+ DoubleNumericField& m_rNumericSpinButton;
+ public:
+ DoubleNumericFormatter(DoubleNumericField& rNumericSpinButton)
+ : FieldFormatter(rNumericSpinButton)
+ , m_rNumericSpinButton(rNumericSpinButton)
+ {
+ }
+
+ virtual bool CheckText(const OUString& sText) const override
+ {
+ // We'd like to implement this using the NumberFormatter::IsNumberFormat, but unfortunately, this doesn't
+ // recognize fragments of numbers (like, for instance "1e", which happens during entering e.g. "1e10")
+ // Thus, the roundabout way via a regular expression
+ return m_rNumericSpinButton.GetNumberValidator().isValidNumericFragment(sText);
+ }
+
+ virtual void FormatChanged(FORMAT_CHANGE_TYPE nWhat) override
+ {
+ m_rNumericSpinButton.ResetConformanceTester();
+ FieldFormatter::FormatChanged(nWhat);
+ }
+ };
+
+ class DoubleCurrencyFormatter : public FieldFormatter
+ {
+ private:
+ DoubleCurrencyField& m_rCurrencySpinButton;
+ bool m_bChangingFormat;
+ public:
+ DoubleCurrencyFormatter(DoubleCurrencyField& rNumericSpinButton)
+ : FieldFormatter(rNumericSpinButton)
+ , m_rCurrencySpinButton(rNumericSpinButton)
+ , m_bChangingFormat(false)
+ {
+ }
+
+ virtual void FormatChanged(FORMAT_CHANGE_TYPE nWhat) override
+ {
+ if (m_bChangingFormat)
+ {
+ FieldFormatter::FormatChanged(nWhat);
+ return;
+ }
+
+ switch (nWhat)
+ {
+ case FORMAT_CHANGE_TYPE::FORMATTER:
+ case FORMAT_CHANGE_TYPE::PRECISION:
+ case FORMAT_CHANGE_TYPE::THOUSANDSSEP:
+ // the aspects which changed don't take our currency settings into account (in fact, they most probably
+ // destroyed them)
+ m_rCurrencySpinButton.UpdateCurrencyFormat();
+ break;
+ case FORMAT_CHANGE_TYPE::KEYONLY:
+ OSL_FAIL("DoubleCurrencyField::FormatChanged : somebody modified my key !");
+ // We always build our own format from the settings we get via special methods (setCurrencySymbol etc.).
+ // Nobody but ourself should modify the format key directly!
+ break;
+ default: break;
+ }
+
+ FieldFormatter::FormatChanged(nWhat);
+ }
+
+ void GuardSetFormat(const OUString& rString, LanguageType eLanguage)
+ {
+ // set this new basic format
+ m_bChangingFormat = true;
+ SetFormat(rString, eLanguage);
+ m_bChangingFormat = false;
+ }
+
+ };
+}
+
+DoubleNumericField::DoubleNumericField(vcl::Window* pParent, WinBits nStyle)
+ : FormattedField(pParent, nStyle)
+{
+ m_xOwnFormatter.reset(new DoubleNumericFormatter(*this));
+ m_pFormatter = m_xOwnFormatter.get();
+ ResetConformanceTester();
+}
+
+DoubleNumericField::~DoubleNumericField() = default;
+
+void DoubleNumericField::ResetConformanceTester()
+{
+ // the thousands and the decimal separator are language dependent
+ Formatter& rFormatter = GetFormatter();
+ const SvNumberformat* pFormatEntry = rFormatter.GetOrCreateFormatter()->GetEntry(rFormatter.GetFormatKey());
+
+ sal_Unicode cSeparatorThousand = ',';
+ sal_Unicode cSeparatorDecimal = '.';
+ if (pFormatEntry)
+ {
+ LocaleDataWrapper aLocaleInfo( LanguageTag( pFormatEntry->GetLanguage()) );
+
+ OUString sSeparator = aLocaleInfo.getNumThousandSep();
+ if (!sSeparator.isEmpty())
+ cSeparatorThousand = sSeparator[0];
+
+ sSeparator = aLocaleInfo.getNumDecimalSep();
+ if (!sSeparator.isEmpty())
+ cSeparatorDecimal = sSeparator[0];
+ }
+
+ m_pNumberValidator.reset(new validation::NumberValidator( cSeparatorThousand, cSeparatorDecimal ));
+}
+
+
+DoubleCurrencyField::DoubleCurrencyField(vcl::Window* pParent, WinBits nStyle)
+ :FormattedField(pParent, nStyle)
+{
+ m_xOwnFormatter.reset(new DoubleCurrencyFormatter(*this));
+ m_pFormatter = m_xOwnFormatter.get();
+
+ m_bPrependCurrSym = false;
+
+ // initialize with a system currency format
+ m_sCurrencySymbol = SvtSysLocale().GetLocaleData().getCurrSymbol();
+ UpdateCurrencyFormat();
+}
+
+void DoubleCurrencyField::setCurrencySymbol(const OUString& rSymbol)
+{
+ if (m_sCurrencySymbol == rSymbol)
+ return;
+
+ m_sCurrencySymbol = rSymbol;
+ UpdateCurrencyFormat();
+ m_pFormatter->FormatChanged(FORMAT_CHANGE_TYPE::CURRENCY_SYMBOL);
+}
+
+void DoubleCurrencyField::setPrependCurrSym(bool _bPrepend)
+{
+ if (m_bPrependCurrSym == _bPrepend)
+ return;
+
+ m_bPrependCurrSym = _bPrepend;
+ UpdateCurrencyFormat();
+ m_pFormatter->FormatChanged(FORMAT_CHANGE_TYPE::CURRSYM_POSITION);
+}
+
+void DoubleCurrencyField::UpdateCurrencyFormat()
+{
+ // the old settings
+ LanguageType eLanguage;
+ m_pFormatter->GetFormat(eLanguage);
+ bool bThSep = m_pFormatter->GetThousandsSep();
+ sal_uInt16 nDigits = m_pFormatter->GetDecimalDigits();
+
+ // build a new format string with the base class' and my own settings
+
+ /* Strangely with gcc 4.6.3 this needs a temporary LanguageTag, otherwise
+ * there's
+ * error: request for member 'getNumThousandSep' in 'aLocaleInfo', which is
+ * of non-class type 'LocaleDataWrapper(LanguageTag)' */
+ LocaleDataWrapper aLocaleInfo(( LanguageTag(eLanguage) ));
+
+ OUStringBuffer sNewFormat;
+ if (bThSep)
+ {
+ sNewFormat.append("#" + aLocaleInfo.getNumThousandSep() + "##0");
+ }
+ else
+ sNewFormat.append('0');
+
+ if (nDigits)
+ {
+ sNewFormat.append(aLocaleInfo.getNumDecimalSep());
+ comphelper::string::padToLength(sNewFormat, sNewFormat.getLength() + nDigits, '0');
+ }
+
+ if (getPrependCurrSym())
+ {
+ OUString sSymbol = getCurrencySymbol();
+ sSymbol = comphelper::string::strip(sSymbol, ' ');
+
+ OUString sTemp =
+ "[$" + sSymbol + "] "
+ + sNewFormat
+ // for negative values : $ -0.00, not -$ 0.00...
+ // (the real solution would be a possibility to choose a "positive currency format" and a "negative currency format"...
+ // But not now... (and hey, you could take a formatted field for this...))
+ // FS - 31.03.00 74642
+ + ";[$"
+ + sSymbol
+ + "] -"
+ + sNewFormat;
+
+ sNewFormat = sTemp;
+ }
+ else
+ {
+ OUString sTemp = getCurrencySymbol();
+ sTemp = comphelper::string::strip(sTemp, ' ');
+
+ sNewFormat.append(" [$" + sTemp + "]");
+ }
+
+ // set this new basic format
+ static_cast<DoubleCurrencyFormatter*>(m_pFormatter)->GuardSetFormat(sNewFormat.makeStringAndClear(), eLanguage);
+}
+
+FormattedField::FormattedField(vcl::Window* pParent, WinBits nStyle)
+ : SpinField(pParent, nStyle, WindowType::FORMATTEDFIELD)
+ , m_pFormatter(nullptr)
+{
+}
+
+void FormattedField::dispose()
+{
+ m_pFormatter = nullptr;
+ m_xOwnFormatter.reset();
+ SpinField::dispose();
+}
+
+void FormattedField::SetText(const OUString& rStr)
+{
+ GetFormatter().SetFieldText(rStr, Selection(0, 0));
+}
+
+void FormattedField::SetText(const OUString& rStr, const Selection& rNewSelection)
+{
+ GetFormatter().SetFieldText(rStr, rNewSelection);
+ SetSelection(rNewSelection);
+}
+
+bool FormattedField::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "digits")
+ GetFormatter().SetDecimalDigits(rValue.toInt32());
+ else if (rKey == "wrap")
+ GetFormatter().SetWrapOnLimits(toBool(rValue));
+ else
+ return SpinField::set_property(rKey, rValue);
+ return true;
+}
+
+void FormattedField::Up()
+{
+ Formatter& rFormatter = GetFormatter();
+ auto nScale = weld::SpinButton::Power10(rFormatter.GetDecimalDigits());
+
+ sal_Int64 nValue = std::round(rFormatter.GetValue() * nScale);
+ sal_Int64 nSpinSize = std::round(rFormatter.GetSpinSize() * nScale);
+ assert(nSpinSize != 0);
+ sal_Int64 nRemainder = rFormatter.GetDisableRemainderFactor() || nSpinSize == 0 ? 0 : nValue % nSpinSize;
+ if (nValue >= 0)
+ nValue = (nRemainder == 0) ? nValue + nSpinSize : nValue + nSpinSize - nRemainder;
+ else
+ nValue = (nRemainder == 0) ? nValue + nSpinSize : nValue - nRemainder;
+
+ // setValue handles under- and overflows (min/max) automatically
+ rFormatter.SetValue(static_cast<double>(nValue) / nScale);
+ SetModifyFlag();
+ Modify();
+
+ SpinField::Up();
+}
+
+void FormattedField::Down()
+{
+ Formatter& rFormatter = GetFormatter();
+ auto nScale = weld::SpinButton::Power10(rFormatter.GetDecimalDigits());
+
+ sal_Int64 nValue = std::round(rFormatter.GetValue() * nScale);
+ sal_Int64 nSpinSize = std::round(rFormatter.GetSpinSize() * nScale);
+ assert(nSpinSize != 0);
+ sal_Int64 nRemainder = rFormatter.GetDisableRemainderFactor() || nSpinSize == 0 ? 0 : nValue % nSpinSize;
+ if (nValue >= 0)
+ nValue = (nRemainder == 0) ? nValue - nSpinSize : nValue - nRemainder;
+ else
+ nValue = (nRemainder == 0) ? nValue - nSpinSize : nValue - nSpinSize - nRemainder;
+
+ // setValue handles under- and overflows (min/max) automatically
+ rFormatter.SetValue(static_cast<double>(nValue) / nScale);
+ SetModifyFlag();
+ Modify();
+
+ SpinField::Down();
+}
+
+void FormattedField::First()
+{
+ Formatter& rFormatter = GetFormatter();
+ if (rFormatter.HasMinValue())
+ {
+ rFormatter.SetValue(rFormatter.GetMinValue());
+ SetModifyFlag();
+ Modify();
+ }
+
+ SpinField::First();
+}
+
+void FormattedField::Last()
+{
+ Formatter& rFormatter = GetFormatter();
+ if (rFormatter.HasMaxValue())
+ {
+ rFormatter.SetValue(rFormatter.GetMaxValue());
+ SetModifyFlag();
+ Modify();
+ }
+
+ SpinField::Last();
+}
+
+void FormattedField::Modify()
+{
+ GetFormatter().Modify();
+}
+
+bool FormattedField::PreNotify(NotifyEvent& rNEvt)
+{
+ if (rNEvt.GetType() == NotifyEventType::KEYINPUT)
+ GetFormatter().SetLastSelection(GetSelection());
+ return SpinField::PreNotify(rNEvt);
+}
+
+bool FormattedField::EventNotify(NotifyEvent& rNEvt)
+{
+ if ((rNEvt.GetType() == NotifyEventType::KEYINPUT) && !IsReadOnly())
+ {
+ const KeyEvent& rKEvt = *rNEvt.GetKeyEvent();
+ sal_uInt16 nMod = rKEvt.GetKeyCode().GetModifier();
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ {
+ Formatter& rFormatter = GetFormatter();
+ if (!nMod && rFormatter.GetOrCreateFormatter()->IsTextFormat(rFormatter.GetFormatKey()))
+ {
+ // the base class would translate this into calls to Up/Down/First/Last,
+ // but we don't want this if we are text-formatted
+ return true;
+ }
+ }
+ }
+ }
+
+ if ((rNEvt.GetType() == NotifyEventType::COMMAND) && !IsReadOnly())
+ {
+ const CommandEvent* pCommand = rNEvt.GetCommandEvent();
+ if (pCommand->GetCommand() == CommandEventId::Wheel)
+ {
+ const CommandWheelData* pData = rNEvt.GetCommandEvent()->GetWheelData();
+ Formatter& rFormatter = GetFormatter();
+ if ((pData->GetMode() == CommandWheelMode::SCROLL) &&
+ rFormatter.GetOrCreateFormatter()->IsTextFormat(rFormatter.GetFormatKey()))
+ {
+ // same as above : prevent the base class from doing Up/Down-calls
+ // (normally I should put this test into the Up/Down methods itself, shouldn't I ?)
+ // FS - 71553 - 19.01.00
+ return true;
+ }
+ }
+ }
+
+ if (rNEvt.GetType() == NotifyEventType::LOSEFOCUS && m_pFormatter)
+ m_pFormatter->EntryLostFocus();
+
+ return SpinField::EventNotify( rNEvt );
+}
+
+Formatter& FormattedField::GetFormatter()
+{
+ if (!m_pFormatter)
+ {
+ m_xOwnFormatter.reset(new FieldFormatter(*this));
+ m_pFormatter = m_xOwnFormatter.get();
+ }
+ return *m_pFormatter;
+}
+
+void FormattedField::SetFormatter(Formatter* pFormatter)
+{
+ m_xOwnFormatter.reset();
+ m_pFormatter = pFormatter;
+}
+
+// currently used by online
+void FormattedField::SetValueFromString(const OUString& rStr)
+{
+ sal_Int32 nEnd;
+ rtl_math_ConversionStatus eStatus;
+ Formatter& rFormatter = GetFormatter();
+ double fValue = ::rtl::math::stringToDouble(rStr, '.', rFormatter.GetDecimalDigits(), &eStatus, &nEnd );
+
+ if (eStatus == rtl_math_ConversionStatus_Ok &&
+ nEnd == rStr.getLength())
+ {
+ rFormatter.SetValue(fValue);
+ SetModifyFlag();
+ Modify();
+
+ // Notify the value has changed
+ SpinField::Up();
+ }
+ else
+ {
+ SAL_WARN("vcl", "fail to convert the value: " << rStr);
+ }
+}
+
+void FormattedField::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ SpinField::DumpAsPropertyTree(rJsonWriter);
+ Formatter& rFormatter = GetFormatter();
+ rJsonWriter.put("min", rFormatter.GetMinValue());
+ rJsonWriter.put("max", rFormatter.GetMaxValue());
+ rJsonWriter.put("value", rFormatter.GetValue());
+ rJsonWriter.put("step", rFormatter.GetSpinSize());
+}
+
+FactoryFunction FormattedField::GetUITestFactory() const
+{
+ return FormattedFieldUIObject::create;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/hyperlabel.cxx b/vcl/source/control/hyperlabel.cxx
new file mode 100644
index 0000000000..34f10750ae
--- /dev/null
+++ b/vcl/source/control/hyperlabel.cxx
@@ -0,0 +1,176 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * 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/color.hxx>
+#include <vcl/event.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <hyperlabel.hxx>
+
+namespace vcl
+{
+ HyperLabel::HyperLabel( vcl::Window* _pParent, WinBits _nWinStyle )
+ :FixedText( _pParent, _nWinStyle )
+ , ID(0)
+ , Index(0)
+ , bInteractive(false)
+ , m_bHyperMode(false)
+ {
+ implInit();
+ }
+
+ Size const & HyperLabel::CalcMinimumSize( tools::Long nMaxWidth )
+ {
+ m_aMinSize = FixedText::CalcMinimumSize( nMaxWidth );
+ // the MinimumSize is used to size the FocusRectangle
+ // and for the MouseMove method
+ m_aMinSize.AdjustHeight(2 );
+ m_aMinSize.AdjustWidth(1 );
+ return m_aMinSize;
+ }
+
+ void HyperLabel::implInit()
+ {
+ ToggleBackgroundColor( COL_TRANSPARENT );
+
+ WinBits nWinStyle = GetStyle();
+ nWinStyle |= WB_EXTRAOFFSET;
+ SetStyle( nWinStyle );
+
+ Show();
+ }
+
+ void HyperLabel::ToggleBackgroundColor( const Color& _rGBColor )
+ {
+ SetControlBackground( _rGBColor );
+ }
+
+ void HyperLabel::MouseMove( const MouseEvent& rMEvt )
+ {
+ vcl::Font aFont = GetControlFont( );
+
+ bool bHyperMode = false;
+ if (!rMEvt.IsLeaveWindow() && IsEnabled() && bInteractive)
+ {
+ Point aPoint = GetPointerPosPixel();
+ if (aPoint.X() < m_aMinSize.Width())
+ bHyperMode = true;
+ }
+
+ m_bHyperMode = bHyperMode;
+ if (bHyperMode)
+ {
+ aFont.SetUnderline(LINESTYLE_SINGLE);
+ SetPointer(PointerStyle::RefHand);
+ }
+ else
+ {
+ aFont.SetUnderline(LINESTYLE_NONE);
+ SetPointer(PointerStyle::Arrow);
+ }
+ SetControlFont(aFont);
+ }
+
+ void HyperLabel::MouseButtonDown( const MouseEvent& )
+ {
+ if ( m_bHyperMode && bInteractive )
+ {
+ maClickHdl.Call( this );
+ }
+ }
+
+ void HyperLabel::GetFocus()
+ {
+ if ( IsEnabled() && bInteractive )
+ {
+ Point aPoint(0,0);
+ tools::Rectangle rRect(aPoint, Size( m_aMinSize.Width(), GetSizePixel().Height() ) );
+ ShowFocus( rRect );
+ }
+ }
+
+ void HyperLabel::LoseFocus()
+ {
+ HideFocus();
+ }
+
+ HyperLabel::~HyperLabel( )
+ {
+ disposeOnce();
+ }
+
+ void HyperLabel::SetInteractive( bool _bInteractive )
+ {
+ bInteractive = ( _bInteractive && IsEnabled() );
+ }
+
+ sal_Int16 HyperLabel::GetID() const
+ {
+ return ID;
+ }
+
+ sal_Int32 HyperLabel::GetIndex() const
+ {
+ return Index;
+ }
+
+ void HyperLabel::SetID( sal_Int16 newID )
+ {
+ this->ID = newID;
+ }
+
+ void HyperLabel::SetIndex( sal_Int32 newIndex )
+ {
+ Index = newIndex;
+ }
+
+ void HyperLabel::SetLabel( const OUString& _rText )
+ {
+ SetText(_rText);
+ }
+
+ void HyperLabel::ApplySettings(vcl::RenderContext& rRenderContext)
+ {
+ FixedText::ApplySettings(rRenderContext);
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ if (GetControlBackground() == COL_TRANSPARENT)
+ rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
+ else
+ rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
+ }
+
+ void HyperLabel::DataChanged( const DataChangedEvent& rDCEvt )
+ {
+ FixedText::DataChanged( rDCEvt );
+
+ if ((( rDCEvt.GetType() == DataChangedEventType::SETTINGS ) ||
+ ( rDCEvt.GetType() == DataChangedEventType::DISPLAY )) &&
+ ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE ))
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ if (GetControlBackground() != COL_TRANSPARENT)
+ SetControlBackground(rStyleSettings.GetHighlightColor());
+ Invalidate();
+ }
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imgctrl.cxx b/vcl/source/control/imgctrl.cxx
new file mode 100644
index 0000000000..414824b29d
--- /dev/null
+++ b/vcl/source/control/imgctrl.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/imgctrl.hxx>
+
+#include <com/sun/star/awt/ImageScaleMode.hpp>
+#include <osl/diagnose.h>
+
+namespace ImageScaleMode = css::awt::ImageScaleMode;
+
+ImageControl::ImageControl( vcl::Window* pParent, WinBits nStyle )
+ :FixedImage( pParent, nStyle )
+ ,mnScaleMode( ImageScaleMode::ANISOTROPIC )
+{
+}
+
+void ImageControl::SetScaleMode( const ::sal_Int16 _nMode )
+{
+ if ( _nMode != mnScaleMode )
+ {
+ mnScaleMode = _nMode;
+ Invalidate();
+ }
+}
+
+void ImageControl::Resize()
+{
+ Invalidate();
+}
+
+namespace
+{
+ Size lcl_calcPaintSize( const tools::Rectangle& _rPaintRect, const Size& _rBitmapSize )
+ {
+ const Size aPaintSize = _rPaintRect.GetSize();
+
+ const double nRatioX = 1.0 * aPaintSize.Width() / _rBitmapSize.Width();
+ const double nRatioY = 1.0 * aPaintSize.Height() / _rBitmapSize.Height();
+ const double nRatioMin = ::std::min( nRatioX, nRatioY );
+
+ return Size( tools::Long( _rBitmapSize.Width() * nRatioMin ), tools::Long( _rBitmapSize.Height() * nRatioMin ) );
+ }
+
+ Point lcl_centerWithin( const tools::Rectangle& _rArea, const Size& _rObjectSize )
+ {
+ Point aPos( _rArea.TopLeft() );
+ aPos.AdjustX(( _rArea.GetWidth() - _rObjectSize.Width() ) / 2 );
+ aPos.AdjustY(( _rArea.GetHeight() - _rObjectSize.Height() ) / 2 );
+ return aPos;
+ }
+}
+
+void ImageControl::ImplDraw(OutputDevice& rDev, const Point& rPos, const Size& rSize) const
+{
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+ if ( !IsEnabled() )
+ nStyle |= DrawImageFlags::Disable;
+
+ const Image& rImage( GetModeImage() );
+ const tools::Rectangle aDrawRect( rPos, rSize );
+ if (!rImage)
+ {
+ OUString sText( GetText() );
+ if ( sText.isEmpty() )
+ return;
+
+ WinBits nWinStyle = GetStyle();
+ DrawTextFlags nTextStyle = FixedText::ImplGetTextStyle( nWinStyle );
+ if ( !IsEnabled() )
+ nTextStyle |= DrawTextFlags::Disable;
+
+ rDev.DrawText( aDrawRect, sText, nTextStyle );
+ return;
+ }
+
+ const Size& rBitmapSize = rImage.GetSizePixel();
+
+ switch ( mnScaleMode )
+ {
+ case ImageScaleMode::NONE:
+ {
+ rDev.DrawImage(lcl_centerWithin( aDrawRect, rBitmapSize ), rImage, nStyle);
+ }
+ break;
+
+ case ImageScaleMode::ISOTROPIC:
+ {
+ const Size aPaintSize = lcl_calcPaintSize( aDrawRect, rBitmapSize );
+ rDev.DrawImage(lcl_centerWithin(aDrawRect, aPaintSize), aPaintSize, rImage, nStyle);
+ }
+ break;
+
+ case ImageScaleMode::ANISOTROPIC:
+ {
+ rDev.DrawImage(
+ aDrawRect.TopLeft(),
+ aDrawRect.GetSize(),
+ rImage, nStyle );
+ }
+ break;
+
+ default:
+ OSL_ENSURE( false, "ImageControl::ImplDraw: unhandled scale mode!" );
+ break;
+
+ } // switch ( mnScaleMode )
+}
+
+void ImageControl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ ImplDraw(rRenderContext, Point(), GetOutputSizePixel());
+
+ if (!HasFocus())
+ return;
+
+ vcl::Window* pBorderWindow = GetWindow(GetWindowType::Border);
+
+ bool bFlat = (GetBorderStyle() == WindowBorderStyle::MONO);
+ tools::Rectangle aRect(Point(0,0), pBorderWindow->GetOutputSizePixel());
+ Color oldLineCol = pBorderWindow->GetOutDev()->GetLineColor();
+ Color oldFillCol = pBorderWindow->GetOutDev()->GetFillColor();
+ pBorderWindow->GetOutDev()->SetFillColor();
+ pBorderWindow->GetOutDev()->SetLineColor(bFlat ? COL_WHITE : COL_BLACK);
+ pBorderWindow->GetOutDev()->DrawRect(aRect);
+ aRect.AdjustLeft( 1 );
+ aRect.AdjustRight( -1 );
+ aRect.AdjustTop( 1 );
+ aRect.AdjustBottom( -1 );
+ pBorderWindow->GetOutDev()->SetLineColor(bFlat ? COL_BLACK : COL_WHITE);
+ pBorderWindow->GetOutDev()->DrawRect(aRect);
+ pBorderWindow->GetOutDev()->SetLineColor(oldLineCol);
+ pBorderWindow->GetOutDev()->SetFillColor(oldFillCol);
+
+}
+
+void ImageControl::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags )
+{
+ const Point aPos = pDev->LogicToPixel( rPos );
+ const Size aSize = GetSizePixel();
+ tools::Rectangle aRect( aPos, aSize );
+
+ pDev->Push();
+ pDev->SetMapMode();
+
+ // Border
+ if ( GetStyle() & WB_BORDER )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ pDev->IntersectClipRegion( aRect );
+ ImplDraw( *pDev, aRect.TopLeft(), aRect.GetSize() );
+
+ pDev->Pop();
+}
+
+void ImageControl::GetFocus()
+{
+ FixedImage::GetFocus();
+ GetWindow( GetWindowType::Border )->Invalidate();
+}
+
+void ImageControl::LoseFocus()
+{
+ FixedImage::GetFocus();
+ GetWindow( GetWindowType::Border )->Invalidate();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imivctl.hxx b/vcl/source/control/imivctl.hxx
new file mode 100644
index 0000000000..5a052c083e
--- /dev/null
+++ b/vcl/source/control/imivctl.hxx
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <o3tl/safeint.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/vclptr.hxx>
+#include <tools/debug.hxx>
+#include <vcl/svtaccessiblefactory.hxx>
+#include <vcl/toolkit/ivctrl.hxx>
+#include <vcl/toolkit/scrbar.hxx>
+
+#include <limits.h>
+
+
+#include <memory>
+#include <map>
+
+class IcnCursor_Impl;
+class SvtIconChoiceCtrl;
+class SvxIconChoiceCtrlEntry;
+class IcnViewEdit_Impl;
+class IcnGridMap_Impl;
+
+
+// some defines
+
+#define PAINTFLAG_HOR_CENTERED 0x0001
+#define PAINTFLAG_VER_CENTERED 0x0002
+
+enum class IconChoiceFlags {
+ NONE = 0x0000,
+ AddMode = 0x0001,
+ SelectingRect = 0x0002,
+ DownCtrl = 0x0004,
+ DownDeselect = 0x0008,
+ EntryListPosValid = 0x0010,
+ ClearingSelection = 0x0020,
+ Arranging = 0x0040
+};
+namespace o3tl {
+ template<> struct typed_flags<IconChoiceFlags> : is_typed_flags<IconChoiceFlags, 0x007f> {};
+}
+
+// unit = pixels
+// distances from window borders
+#define LROFFS_WINBORDER 4
+#define TBOFFS_WINBORDER 4
+// for the bounding rectangle
+#define LROFFS_BOUND 2
+#define TBOFFS_BOUND 2
+// distance icon to text
+#define HOR_DIST_BMP_STRING 3
+#define VER_DIST_BMP_STRING 3
+// width offset of highlight rectangle for Text
+#define LROFFS_TEXT 2
+
+#define DEFAULT_MAX_VIRT_WIDTH 200
+#define DEFAULT_MAX_VIRT_HEIGHT 200
+
+#define VIEWMODE_MASK (WB_ICON | WB_SMALLICON | WB_DETAILS)
+
+
+enum class IcnViewFieldType
+{
+ Image,
+ Text
+};
+
+
+// Data about the focus of entries
+
+struct LocalFocus
+{
+ tools::Rectangle aRect;
+ Color aPenColor;
+};
+
+
+typedef sal_uLong GridId;
+
+#define GRID_NOT_FOUND (GridId(ULONG_MAX))
+
+// Implementation-class of IconChoiceCtrl
+
+
+typedef std::map<sal_uInt16, std::unique_ptr<SvxIconChoiceCtrlColumnInfo>> SvxIconChoiceCtrlColumnInfoMap;
+typedef std::vector<SvxIconChoiceCtrlEntry*> SvxIconChoiceCtrlEntryPtrVec;
+
+class SvxIconChoiceCtrl_Impl
+{
+ friend class IcnCursor_Impl;
+ friend class IcnGridMap_Impl;
+
+ std::vector< std::unique_ptr<SvxIconChoiceCtrlEntry> > maEntries;
+ VclPtr<ScrollBar> aVerSBar;
+ VclPtr<ScrollBar> aHorSBar;
+ VclPtr<ScrollBarBox> aScrBarBox;
+ tools::Rectangle aCurSelectionRect;
+ std::vector<tools::Rectangle> aSelectedRectList;
+ Idle aAutoArrangeIdle;
+ Idle aDocRectChangedIdle;
+ Idle aVisRectChangedIdle;
+ Idle aCallSelectHdlIdle;
+ Size aVirtOutputSize;
+ Size aImageSize;
+ Size aDefaultTextSize;
+ Size aOutputSize; // Pixel
+ VclPtr<SvtIconChoiceCtrl> pView;
+ std::unique_ptr<IcnCursor_Impl> pImpCursor;
+ std::unique_ptr<IcnGridMap_Impl> pGridMap;
+ tools::Long nMaxVirtWidth; // max. width aVirtOutputSize for ALIGN_TOP
+ tools::Long nMaxVirtHeight; // max. height aVirtOutputSize for ALIGN_LEFT
+ std::vector< SvxIconChoiceCtrlEntry* > maZOrderList;
+ std::unique_ptr<SvxIconChoiceCtrlColumnInfoMap> m_pColumns;
+ WinBits nWinBits;
+ tools::Long nMaxBoundHeight; // height of highest BoundRects
+ IconChoiceFlags nFlags;
+ DrawTextFlags nCurTextDrawFlags;
+ ImplSVEvent * nUserEventAdjustScrBars;
+ SvxIconChoiceCtrlEntry* pCurHighlightFrame;
+ bool bHighlightFramePressed;
+ SvxIconChoiceCtrlEntry* pHead = nullptr; // top left entry
+ SvxIconChoiceCtrlEntry* pCursor;
+ SvxIconChoiceCtrlEntry* pHdlEntry;
+ SvxIconChoiceCtrlEntry* pAnchor; // for selection
+ LocalFocus aFocus; // Data for focusrect
+ ::vcl::AccessibleFactoryAccess aAccFactory;
+
+ SvxIconChoiceCtrlTextMode eTextMode;
+ SelectionMode eSelectionMode;
+ sal_Int32 nSelectionCount;
+ SvxIconChoiceCtrlPositionMode ePositionMode;
+ bool bBoundRectsDirty;
+ bool bUpdateMode;
+
+ void ShowCursor( bool bShow );
+
+ void ImpArrange( bool bKeepPredecessors );
+ void AdjustVirtSize( const tools::Rectangle& );
+ void ResetVirtSize();
+ void CheckScrollBars();
+
+ DECL_LINK( ScrollUpDownHdl, ScrollBar*, void );
+ DECL_LINK( ScrollLeftRightHdl, ScrollBar*, void );
+ DECL_LINK( UserEventHdl, void*, void );
+ DECL_LINK( AutoArrangeHdl, Timer*, void );
+ DECL_LINK( DocRectChangedHdl, Timer*, void );
+ DECL_LINK( VisRectChangedHdl, Timer*, void );
+ DECL_LINK( CallSelectHdlHdl, Timer*, void );
+
+ void AdjustScrollBars();
+ void PositionScrollBars( tools::Long nRealWidth, tools::Long nRealHeight );
+ static tools::Long GetScrollBarPageSize( tools::Long nVisibleRange )
+ {
+ return ((nVisibleRange*75)/100);
+ }
+ tools::Long GetScrollBarLineSize() const
+ {
+ return nMaxBoundHeight / 2;
+ }
+ bool HandleScrollCommand( const CommandEvent& rCmd );
+ void ToDocPos( Point& rPosPixel )
+ {
+ rPosPixel -= pView->GetMapMode().GetOrigin();
+ }
+ void InitScrollBarBox();
+ void ToggleSelection( SvxIconChoiceCtrlEntry* );
+ void DeselectAllBut( SvxIconChoiceCtrlEntry const * );
+ void Center( SvxIconChoiceCtrlEntry* pEntry ) const;
+ void CallSelectHandler();
+ void SelectRect(
+ SvxIconChoiceCtrlEntry* pEntry1,
+ SvxIconChoiceCtrlEntry* pEntry2,
+ bool bAdd,
+ std::vector<tools::Rectangle>* pOtherRects
+ );
+
+ void SelectRange(
+ SvxIconChoiceCtrlEntry const * pStart,
+ SvxIconChoiceCtrlEntry const * pEnd,
+ bool bAdd
+ );
+
+ void AddSelectedRect( const tools::Rectangle& );
+ void AddSelectedRect(
+ SvxIconChoiceCtrlEntry* pEntry1,
+ SvxIconChoiceCtrlEntry* pEntry2
+ );
+
+ void ClearSelectedRectList();
+ tools::Rectangle CalcMaxTextRect( const SvxIconChoiceCtrlEntry* pEntry ) const;
+
+ void ClipAtVirtOutRect( tools::Rectangle& rRect ) const;
+ GridId GetPredecessorGrid( const Point& rDocPos) const;
+
+ void InitPredecessors();
+ void ClearPredecessors();
+
+ bool CheckVerScrollBar();
+ bool CheckHorScrollBar();
+ void CancelUserEvents();
+ void EntrySelected(
+ SvxIconChoiceCtrlEntry* pEntry,
+ bool bSelect
+ );
+ void RepaintSelectedEntries();
+ void SetListPositions();
+ void SetDefaultTextSize();
+ bool IsAutoArrange() const
+ {
+ return (ePositionMode == SvxIconChoiceCtrlPositionMode::AutoArrange);
+ }
+ void DocRectChanged() { aDocRectChangedIdle.Start(); }
+ void VisRectChanged() { aVisRectChangedIdle.Start(); }
+ void SetOrigin( const Point& );
+
+ void ShowFocus ( tools::Rectangle const & rRect );
+ void DrawFocusRect(vcl::RenderContext& rRenderContext);
+
+ bool IsMnemonicChar( sal_Unicode cChar, sal_uLong& rPos ) const;
+
+ // Copy assignment is forbidden and not implemented.
+ SvxIconChoiceCtrl_Impl (const SvxIconChoiceCtrl_Impl &) = delete;
+ SvxIconChoiceCtrl_Impl & operator= (const SvxIconChoiceCtrl_Impl &) = delete;
+
+public:
+
+ tools::Long nGridDX;
+ tools::Long nGridDY;
+ tools::Long nHorSBarHeight;
+ tools::Long nVerSBarWidth;
+
+ SvxIconChoiceCtrl_Impl( SvtIconChoiceCtrl* pView, WinBits nWinStyle );
+ ~SvxIconChoiceCtrl_Impl();
+
+ void SetSelectionMode(SelectionMode eMode)
+ {
+ eSelectionMode = eMode;
+ }
+
+ void Clear( bool bInCtor );
+ void SetStyle( WinBits nWinStyle );
+ WinBits GetStyle() const { return nWinBits; }
+ void InsertEntry( std::unique_ptr<SvxIconChoiceCtrlEntry>, size_t nPos );
+ void RemoveEntry( size_t nPos );
+ void FontModified();
+ void SelectAll();
+ void SelectEntry(
+ SvxIconChoiceCtrlEntry*,
+ bool bSelect,
+ bool bAddToSelection = false
+ );
+ void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect);
+ bool MouseButtonDown( const MouseEvent& );
+ bool MouseButtonUp( const MouseEvent& );
+ bool MouseMove( const MouseEvent&);
+ bool RequestHelp( const HelpEvent& rHEvt );
+ void SetCursor_Impl(
+ SvxIconChoiceCtrlEntry* pOldCursor,
+ SvxIconChoiceCtrlEntry* pNewCursor,
+ bool bMod1,
+ bool bShift
+ );
+ bool KeyInput( const KeyEvent& );
+ void Resize();
+ void GetFocus();
+ void LoseFocus();
+ void SetUpdateMode( bool bUpdate );
+ bool GetUpdateMode() const { return bUpdateMode; }
+ void PaintEntry(SvxIconChoiceCtrlEntry*, const Point&, vcl::RenderContext& rRenderContext);
+
+ void SetEntryPos(
+ SvxIconChoiceCtrlEntry* pEntry,
+ const Point& rPos
+ );
+
+ void InvalidateEntry( SvxIconChoiceCtrlEntry* );
+
+ void SetNoSelection();
+
+ SvxIconChoiceCtrlEntry* GetCurEntry() const { return pCursor; }
+ void SetCursor( SvxIconChoiceCtrlEntry* );
+
+ SvxIconChoiceCtrlEntry* GetEntry( const Point& rDocPos, bool bHit = false );
+
+ void MakeEntryVisible( SvxIconChoiceCtrlEntry* pEntry, bool bBound = true );
+
+ void Arrange(
+ bool bKeepPredecessors,
+ tools::Long nSetMaxVirtWidth,
+ tools::Long nSetMaxVirtHeight
+ );
+
+ tools::Rectangle CalcFocusRect( SvxIconChoiceCtrlEntry* );
+ tools::Rectangle CalcBmpRect( SvxIconChoiceCtrlEntry*, const Point* pPos = nullptr );
+ tools::Rectangle CalcTextRect(
+ SvxIconChoiceCtrlEntry*,
+ const Point* pPos = nullptr,
+ const OUString* pStr = nullptr
+ );
+
+ tools::Long CalcBoundingWidth() const;
+ tools::Long CalcBoundingHeight() const;
+ Size CalcBoundingSize() const;
+ void FindBoundingRect( SvxIconChoiceCtrlEntry* pEntry );
+ void SetBoundingRect_Impl(
+ SvxIconChoiceCtrlEntry* pEntry,
+ const Point& rPos,
+ const Size& rBoundingSize
+ );
+ // recalculates all invalid BoundRects
+ void RecalcAllBoundingRectsSmart();
+ const tools::Rectangle& GetEntryBoundRect( SvxIconChoiceCtrlEntry* );
+ void InvalidateBoundingRect( tools::Rectangle& rRect )
+ {
+ rRect.SetRight(LONG_MAX);
+ bBoundRectsDirty = true;
+ }
+ static bool IsBoundingRectValid( const tools::Rectangle& rRect ) { return ( rRect.Right() != LONG_MAX ); }
+
+ static void PaintEmphasis(const tools::Rectangle& rRect1, bool bSelected,
+ vcl::RenderContext& rRenderContext );
+
+ void PaintItem(const tools::Rectangle& rRect, IcnViewFieldType eItem, SvxIconChoiceCtrlEntry* pEntry,
+ sal_uInt16 nPaintFlags, vcl::RenderContext& rRenderContext);
+
+ // recalculates all BoundingRects if bMustRecalcBoundingRects == true
+ void CheckBoundingRects() { if (bBoundRectsDirty) RecalcAllBoundingRectsSmart(); }
+ void Command( const CommandEvent& rCEvt );
+ void ToTop( SvxIconChoiceCtrlEntry* );
+
+ sal_Int32 GetSelectionCount() const;
+ void SetGrid( const Size& );
+ Size GetMinGrid() const;
+ void Scroll( tools::Long nDeltaX, tools::Long nDeltaY );
+ const Size& GetItemSize( IcnViewFieldType ) const;
+
+ void HideDDIcon();
+
+ static bool IsOver(
+ std::vector<tools::Rectangle>* pSelectedRectList,
+ const tools::Rectangle& rEntryBoundRect
+ );
+
+ void SelectRect(
+ const tools::Rectangle&,
+ bool bAdd,
+ std::vector<tools::Rectangle>* pOtherRects
+ );
+
+ void MakeVisible(
+ const tools::Rectangle& rDocPos,
+ bool bInScrollBarEvent=false
+ );
+
+#ifdef DBG_UTIL
+ void SetEntryTextMode(
+ SvxIconChoiceCtrlTextMode,
+ SvxIconChoiceCtrlEntry* pEntry
+ );
+#endif
+ size_t GetEntryCount() const { return maEntries.size(); }
+ SvxIconChoiceCtrlEntry* GetEntry( size_t nPos )
+ {
+ return maEntries[ nPos ].get();
+ }
+ SvxIconChoiceCtrlEntry* GetEntry( size_t nPos ) const
+ {
+ return maEntries[ nPos ].get();
+ }
+ SvxIconChoiceCtrlEntry* GetFirstSelectedEntry() const;
+ sal_Int32 GetEntryListPos( SvxIconChoiceCtrlEntry const * ) const;
+ void InitSettings();
+ tools::Rectangle GetOutputRect() const;
+
+ void SetEntryPredecessor(SvxIconChoiceCtrlEntry* pEntry,SvxIconChoiceCtrlEntry* pPredecessor);
+ // only delivers valid results when in AutoArrange mode!
+ SvxIconChoiceCtrlEntry* FindEntryPredecessor( SvxIconChoiceCtrlEntry* pEntry, const Point& );
+
+ void SetPositionMode( SvxIconChoiceCtrlPositionMode );
+
+ void SetColumn( sal_uInt16 nIndex, const SvxIconChoiceCtrlColumnInfo& );
+ const SvxIconChoiceCtrlColumnInfo* GetColumn( sal_uInt16 nIndex ) const;
+
+ void SetEntryHighlightFrame(
+ SvxIconChoiceCtrlEntry* pEntry,
+ bool bKeepHighlightFlags
+ );
+ void DrawHighlightFrame(vcl::RenderContext& rRenderContext, const tools::Rectangle& rBmpRect);
+
+ void CallEventListeners( VclEventId nEvent, void* pData );
+
+ ::vcl::IAccessibleFactory& GetAccessibleFactory()
+ {
+ return aAccFactory.getFactory();
+ }
+};
+
+typedef std::map<sal_uInt16, SvxIconChoiceCtrlEntryPtrVec> IconChoiceMap;
+
+class IcnCursor_Impl
+{
+ SvxIconChoiceCtrl_Impl* pView;
+ std::unique_ptr<IconChoiceMap> xColumns;
+ std::unique_ptr<IconChoiceMap> xRows;
+ tools::Long nCols;
+ tools::Long nRows;
+ short nDeltaWidth;
+ short nDeltaHeight;
+ SvxIconChoiceCtrlEntry* pCurEntry;
+ void SetDeltas();
+ void ImplCreate();
+ void Create() { if( !xColumns ) ImplCreate(); }
+
+ sal_uInt16 GetSortListPos(
+ SvxIconChoiceCtrlEntryPtrVec& rList,
+ tools::Long nValue,
+ bool bVertical);
+ SvxIconChoiceCtrlEntry* SearchCol(
+ sal_uInt16 nCol,
+ sal_uInt16 nTop,
+ sal_uInt16 nBottom,
+ bool bDown,
+ bool bSimple
+ );
+
+ SvxIconChoiceCtrlEntry* SearchRow(
+ sal_uInt16 nRow,
+ sal_uInt16 nLeft,
+ sal_uInt16 nRight,
+ bool bRight,
+ bool bSimple
+ );
+
+public:
+ explicit IcnCursor_Impl( SvxIconChoiceCtrl_Impl* pOwner );
+ ~IcnCursor_Impl();
+ void Clear();
+
+ // for Cursortravelling etc.
+ SvxIconChoiceCtrlEntry* GoLeftRight( SvxIconChoiceCtrlEntry*, bool bRight );
+ SvxIconChoiceCtrlEntry* GoUpDown( SvxIconChoiceCtrlEntry*, bool bDown );
+ SvxIconChoiceCtrlEntry* GoPageUpDown( SvxIconChoiceCtrlEntry*, bool bDown );
+};
+
+class IcnGridMap_Impl
+{
+ tools::Rectangle _aLastOccupiedGrid;
+ SvxIconChoiceCtrl_Impl* _pView;
+ std::unique_ptr<bool[]> _pGridMap;
+ sal_uInt16 _nGridCols, _nGridRows;
+
+ void Expand();
+ void Create_Impl();
+ void Create() { if(!_pGridMap) Create_Impl(); }
+
+ void GetMinMapSize( sal_uInt16& rDX, sal_uInt16& rDY ) const;
+
+public:
+ explicit IcnGridMap_Impl(SvxIconChoiceCtrl_Impl* pView);
+ ~IcnGridMap_Impl();
+
+ void Clear();
+
+ GridId GetGrid( const Point& rDocPos );
+ GridId GetGrid( sal_uInt16 nGridX, sal_uInt16 nGridY );
+ GridId GetUnoccupiedGrid();
+
+ void OccupyGrids( const SvxIconChoiceCtrlEntry* );
+ void OccupyGrid( GridId nId )
+ {
+ DBG_ASSERT(!_pGridMap || nId<o3tl::make_unsigned(_nGridCols*_nGridRows),"OccupyGrid: Bad GridId");
+ if(_pGridMap && nId < o3tl::make_unsigned(_nGridCols *_nGridRows) )
+ _pGridMap[ nId ] = true;
+ }
+
+ tools::Rectangle GetGridRect( GridId );
+ void GetGridCoord( GridId, sal_uInt16& rGridX, sal_uInt16& rGridY );
+ static sal_uLong GetGridCount(
+ const Size& rSizePixel,
+ sal_uInt16 nGridWidth,
+ sal_uInt16 nGridHeight
+ );
+
+ void OutputSizeChanged();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imivctl1.cxx b/vcl/source/control/imivctl1.cxx
new file mode 100644
index 0000000000..1364dc4f44
--- /dev/null
+++ b/vcl/source/control/imivctl1.cxx
@@ -0,0 +1,2927 @@
+/* -*- 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 <osl/diagnose.h>
+#include <tools/debug.hxx>
+#include <vcl/wall.hxx>
+#include <vcl/help.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <tools/poly.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/commandevent.hxx>
+
+#include <vcl/toolkit/ivctrl.hxx>
+#include "imivctl.hxx"
+
+#include <algorithm>
+#include <memory>
+#include <vcl/idle.hxx>
+
+constexpr auto DRAWTEXT_FLAGS_ICON =
+ DrawTextFlags::Center | DrawTextFlags::Top | DrawTextFlags::EndEllipsis |
+ DrawTextFlags::Clip | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak | DrawTextFlags::Mnemonic;
+
+#define DRAWTEXT_FLAGS_SMALLICON (DrawTextFlags::Left|DrawTextFlags::EndEllipsis|DrawTextFlags::Clip)
+
+#define EVENTID_SHOW_CURSOR (reinterpret_cast<void*>(1))
+#define EVENTID_ADJUST_SCROLLBARS (reinterpret_cast<void*>(2))
+
+SvxIconChoiceCtrl_Impl::SvxIconChoiceCtrl_Impl(
+ SvtIconChoiceCtrl* pCurView,
+ WinBits nWinStyle
+) :
+ aVerSBar( VclPtr<ScrollBar>::Create(pCurView, WB_DRAG | WB_VSCROLL) ),
+ aHorSBar( VclPtr<ScrollBar>::Create(pCurView, WB_DRAG | WB_HSCROLL) ),
+ aScrBarBox( VclPtr<ScrollBarBox>::Create(pCurView) ),
+ aAutoArrangeIdle( "svtools::SvxIconChoiceCtrl_Impl aAutoArrangeIdle" ),
+ aDocRectChangedIdle( "svtools::SvxIconChoiceCtrl_Impl aDocRectChangedIdle" ),
+ aVisRectChangedIdle( "svtools::SvxIconChoiceCtrl_Impl aVisRectChangedIdle" ),
+ aCallSelectHdlIdle( "svtools::SvxIconChoiceCtrl_Impl aCallSelectHdlIdle" ),
+ aImageSize( 32 * pCurView->GetDPIScaleFactor(), 32 * pCurView->GetDPIScaleFactor()),
+ pView(pCurView), nMaxVirtWidth(DEFAULT_MAX_VIRT_WIDTH), nMaxVirtHeight(DEFAULT_MAX_VIRT_HEIGHT),
+ nFlags(IconChoiceFlags::NONE), nUserEventAdjustScrBars(nullptr),
+ pCurHighlightFrame(nullptr), bHighlightFramePressed(false), pHead(nullptr), pCursor(nullptr),
+ pHdlEntry(nullptr),
+ pAnchor(nullptr), eTextMode(SvxIconChoiceCtrlTextMode::Short),
+ eSelectionMode(SelectionMode::Multiple), ePositionMode(SvxIconChoiceCtrlPositionMode::Free),
+ bUpdateMode(true)
+{
+ SetStyle( nWinStyle );
+ pImpCursor.reset( new IcnCursor_Impl( this ) );
+ pGridMap.reset( new IcnGridMap_Impl( this ) );
+
+ aVerSBar->SetScrollHdl( LINK( this, SvxIconChoiceCtrl_Impl, ScrollUpDownHdl ) );
+ aHorSBar->SetScrollHdl( LINK( this, SvxIconChoiceCtrl_Impl, ScrollLeftRightHdl ) );
+
+ nHorSBarHeight = aHorSBar->GetSizePixel().Height();
+ nVerSBarWidth = aVerSBar->GetSizePixel().Width();
+
+ aAutoArrangeIdle.SetPriority( TaskPriority::HIGH_IDLE );
+ aAutoArrangeIdle.SetInvokeHandler(LINK(this,SvxIconChoiceCtrl_Impl,AutoArrangeHdl));
+
+ aCallSelectHdlIdle.SetPriority( TaskPriority::LOWEST );
+ aCallSelectHdlIdle.SetInvokeHandler( LINK(this,SvxIconChoiceCtrl_Impl,CallSelectHdlHdl));
+
+ aDocRectChangedIdle.SetPriority( TaskPriority::HIGH_IDLE );
+ aDocRectChangedIdle.SetInvokeHandler(LINK(this,SvxIconChoiceCtrl_Impl,DocRectChangedHdl));
+
+ aVisRectChangedIdle.SetPriority( TaskPriority::HIGH_IDLE );
+ aVisRectChangedIdle.SetInvokeHandler(LINK(this,SvxIconChoiceCtrl_Impl,VisRectChangedHdl));
+
+ Clear( true );
+ Size gridSize(100,70);
+ if(pView->GetDPIScaleFactor() > 1)
+ {
+ gridSize.setHeight( gridSize.Height() * ( pView->GetDPIScaleFactor()) );
+ }
+ SetGrid(gridSize);
+}
+
+SvxIconChoiceCtrl_Impl::~SvxIconChoiceCtrl_Impl()
+{
+ Clear(false);
+ CancelUserEvents();
+ pImpCursor.reset();
+ pGridMap.reset();
+ ClearSelectedRectList();
+ m_pColumns.reset();
+ aVerSBar.disposeAndClear();
+ aHorSBar.disposeAndClear();
+ aScrBarBox.disposeAndClear();
+}
+
+void SvxIconChoiceCtrl_Impl::Clear( bool bInCtor )
+{
+ nSelectionCount = 0;
+ pCurHighlightFrame = nullptr;
+ CancelUserEvents();
+ ShowCursor( false );
+ bBoundRectsDirty = false;
+ nMaxBoundHeight = 0;
+
+ pCursor = nullptr;
+ if( !bInCtor )
+ {
+ pImpCursor->Clear();
+ pGridMap->Clear();
+ aVirtOutputSize.setWidth( 0 );
+ aVirtOutputSize.setHeight( 0 );
+ Size aSize( pView->GetOutputSizePixel() );
+ nMaxVirtWidth = aSize.Width() - nVerSBarWidth;
+ if( nMaxVirtWidth <= 0 )
+ nMaxVirtWidth = DEFAULT_MAX_VIRT_WIDTH;
+ nMaxVirtHeight = aSize.Height() - nHorSBarHeight;
+ if( nMaxVirtHeight <= 0 )
+ nMaxVirtHeight = DEFAULT_MAX_VIRT_HEIGHT;
+ maZOrderList.clear();
+ SetOrigin( Point() );
+ if( bUpdateMode )
+ pView->Invalidate(InvalidateFlags::NoChildren);
+ }
+ AdjustScrollBars();
+ maEntries.clear();
+ DocRectChanged();
+ VisRectChanged();
+}
+
+void SvxIconChoiceCtrl_Impl::SetStyle( WinBits nWinStyle )
+{
+ nWinBits = nWinStyle;
+ nCurTextDrawFlags = DRAWTEXT_FLAGS_ICON;
+ if( nWinBits & (WB_SMALLICON | WB_DETAILS) )
+ nCurTextDrawFlags = DRAWTEXT_FLAGS_SMALLICON;
+ if( nWinBits & WB_NOSELECTION )
+ eSelectionMode = SelectionMode::NONE;
+ if( !(nWinStyle & (WB_ALIGN_TOP | WB_ALIGN_LEFT)))
+ nWinBits |= WB_ALIGN_LEFT;
+ if( nWinStyle & WB_DETAILS )
+ {
+ if (!m_pColumns)
+ SetColumn( 0, SvxIconChoiceCtrlColumnInfo() );
+ }
+}
+
+IMPL_LINK( SvxIconChoiceCtrl_Impl, ScrollUpDownHdl, ScrollBar*, pScrollBar, void )
+{
+ // arrow up: delta=-1; arrow down: delta=+1
+ Scroll( 0, pScrollBar->GetDelta() );
+}
+
+IMPL_LINK( SvxIconChoiceCtrl_Impl, ScrollLeftRightHdl, ScrollBar*, pScrollBar, void )
+{
+ // arrow left: delta=-1; arrow right: delta=+1
+ Scroll( pScrollBar->GetDelta(), 0 );
+}
+
+void SvxIconChoiceCtrl_Impl::FontModified()
+{
+ SetDefaultTextSize();
+ ShowCursor( false );
+ ShowCursor( true );
+}
+
+void SvxIconChoiceCtrl_Impl::InsertEntry( std::unique_ptr<SvxIconChoiceCtrlEntry> pEntry1, size_t nPos)
+{
+ auto pEntry = pEntry1.get();
+
+ if ( nPos < maEntries.size() ) {
+ maEntries.insert( maEntries.begin() + nPos, std::move(pEntry1) );
+ } else {
+ maEntries.push_back( std::move(pEntry1) );
+ }
+
+ if( pHead )
+ pEntry->SetBacklink( pHead->pblink );
+
+ if( (nFlags & IconChoiceFlags::EntryListPosValid) && nPos >= maEntries.size() - 1 )
+ pEntry->nPos = maEntries.size() - 1;
+ else
+ nFlags &= ~IconChoiceFlags::EntryListPosValid;
+
+ maZOrderList.push_back( pEntry );
+ pImpCursor->Clear();
+
+ // If the UpdateMode is true, don't set all bounding rectangles to
+ // 'to be checked', but only the bounding rectangle of the new entry.
+ // Thus, don't call InvalidateBoundingRect!
+ pEntry->aRect.SetRight( LONG_MAX );
+ if( bUpdateMode )
+ {
+ FindBoundingRect( pEntry );
+ tools::Rectangle aOutputArea( GetOutputRect() );
+ pGridMap->OccupyGrids( pEntry );
+ if( !aOutputArea.Overlaps( pEntry->aRect ) )
+ return; // is invisible
+ pView->Invalidate( pEntry->aRect );
+ }
+ else
+ InvalidateBoundingRect( pEntry->aRect );
+}
+
+void SvxIconChoiceCtrl_Impl::RemoveEntry(size_t nPos)
+{
+ pImpCursor->Clear();
+ maEntries.erase(maEntries.begin() + nPos);
+ RecalcAllBoundingRectsSmart();
+}
+
+tools::Rectangle SvxIconChoiceCtrl_Impl::GetOutputRect() const
+{
+ Point aOrigin( pView->GetMapMode().GetOrigin() );
+ aOrigin *= -1;
+ return tools::Rectangle( aOrigin, aOutputSize );
+}
+
+void SvxIconChoiceCtrl_Impl::SetListPositions()
+{
+ if( nFlags & IconChoiceFlags::EntryListPosValid )
+ return;
+
+ size_t nCount = maEntries.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ maEntries[ nCur ]->nPos = nCur;
+ }
+ nFlags |= IconChoiceFlags::EntryListPosValid;
+}
+
+void SvxIconChoiceCtrl_Impl::SelectEntry( SvxIconChoiceCtrlEntry* pEntry, bool bSelect,
+ bool bAdd )
+{
+ if( eSelectionMode == SelectionMode::NONE )
+ return;
+
+ if( !bAdd )
+ {
+ if ( !( nFlags & IconChoiceFlags::ClearingSelection ) )
+ {
+ nFlags |= IconChoiceFlags::ClearingSelection;
+ DeselectAllBut( pEntry );
+ nFlags &= ~IconChoiceFlags::ClearingSelection;
+ }
+ }
+ if( pEntry->IsSelected() == bSelect )
+ return;
+
+ pHdlEntry = pEntry;
+ SvxIconViewFlags nEntryFlags = pEntry->GetFlags();
+ if( bSelect )
+ {
+ nEntryFlags |= SvxIconViewFlags::SELECTED;
+ pEntry->AssignFlags( nEntryFlags );
+ nSelectionCount++;
+ CallSelectHandler();
+ }
+ else
+ {
+ nEntryFlags &= ~SvxIconViewFlags::SELECTED;
+ pEntry->AssignFlags( nEntryFlags );
+ nSelectionCount--;
+ CallSelectHandler();
+ }
+ EntrySelected( pEntry, bSelect );
+}
+
+void SvxIconChoiceCtrl_Impl::EntrySelected(SvxIconChoiceCtrlEntry* pEntry, bool bSelect)
+{
+ // When using SingleSelection, make sure that the cursor is always placed
+ // over the (only) selected entry. (But only if a cursor exists.)
+ if (bSelect && pCursor &&
+ eSelectionMode == SelectionMode::Single &&
+ pEntry != pCursor)
+ {
+ SetCursor(pEntry);
+ }
+
+ // Not when dragging though, else the loop in SelectRect doesn't work
+ // correctly!
+ if (!(nFlags & IconChoiceFlags::SelectingRect))
+ ToTop(pEntry);
+ if (bUpdateMode)
+ {
+ if (pEntry == pCursor)
+ ShowCursor(false);
+ pView->Invalidate(CalcFocusRect(pEntry));
+ if (pEntry == pCursor)
+ ShowCursor(true);
+ }
+
+ // #i101012# emit vcl event LISTBOX_SELECT only in case that the given entry is selected.
+ if (bSelect)
+ {
+ CallEventListeners(VclEventId::ListboxSelect, pEntry);
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::ResetVirtSize()
+{
+ aVirtOutputSize.setWidth( 0 );
+ aVirtOutputSize.setHeight( 0 );
+ const size_t nCount = maEntries.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pCur = maEntries[ nCur ].get();
+ pCur->ClearFlags( SvxIconViewFlags::POS_MOVED );
+ if( pCur->IsPosLocked() )
+ {
+ // adapt (among others) VirtSize
+ if( !IsBoundingRectValid( pCur->aRect ) )
+ FindBoundingRect( pCur );
+ else
+ AdjustVirtSize( pCur->aRect );
+ }
+ else
+ InvalidateBoundingRect( pCur->aRect );
+ }
+
+ if( !(nWinBits & (WB_NOVSCROLL | WB_NOHSCROLL)) )
+ {
+ Size aRealOutputSize( pView->GetOutputSizePixel() );
+ if( aVirtOutputSize.Width() < aRealOutputSize.Width() ||
+ aVirtOutputSize.Height() < aRealOutputSize.Height() )
+ {
+ sal_uLong nGridCount = IcnGridMap_Impl::GetGridCount(
+ aRealOutputSize, static_cast<sal_uInt16>(nGridDX), static_cast<sal_uInt16>(nGridDY) );
+ if( nGridCount < nCount )
+ {
+ if( nWinBits & WB_ALIGN_TOP )
+ nMaxVirtWidth = aRealOutputSize.Width() - nVerSBarWidth;
+ else // WB_ALIGN_LEFT
+ nMaxVirtHeight = aRealOutputSize.Height() - nHorSBarHeight;
+ }
+ }
+ }
+
+ pImpCursor->Clear();
+ pGridMap->Clear();
+ VisRectChanged();
+}
+
+void SvxIconChoiceCtrl_Impl::AdjustVirtSize( const tools::Rectangle& rRect )
+{
+ tools::Long nHeightOffs = 0;
+ tools::Long nWidthOffs = 0;
+
+ if( aVirtOutputSize.Width() < (rRect.Right()+LROFFS_WINBORDER) )
+ nWidthOffs = (rRect.Right()+LROFFS_WINBORDER) - aVirtOutputSize.Width();
+
+ if( aVirtOutputSize.Height() < (rRect.Bottom()+TBOFFS_WINBORDER) )
+ nHeightOffs = (rRect.Bottom()+TBOFFS_WINBORDER) - aVirtOutputSize.Height();
+
+ if( !(nWidthOffs || nHeightOffs) )
+ return;
+
+ Range aRange;
+ aVirtOutputSize.AdjustWidth(nWidthOffs );
+ aRange.Max() = aVirtOutputSize.Width();
+ aHorSBar->SetRange( aRange );
+
+ aVirtOutputSize.AdjustHeight(nHeightOffs );
+ aRange.Max() = aVirtOutputSize.Height();
+ aVerSBar->SetRange( aRange );
+
+ pImpCursor->Clear();
+ pGridMap->OutputSizeChanged();
+ AdjustScrollBars();
+ DocRectChanged();
+}
+
+void SvxIconChoiceCtrl_Impl::InitPredecessors()
+{
+ DBG_ASSERT(!pHead,"SvxIconChoiceCtrl_Impl::InitPredecessors() >> Already initialized");
+ size_t nCount = maEntries.size();
+ if( nCount )
+ {
+ SvxIconChoiceCtrlEntry* pPrev = maEntries[ 0 ].get();
+ for( size_t nCur = 1; nCur <= nCount; nCur++ )
+ {
+ pPrev->ClearFlags( SvxIconViewFlags::POS_LOCKED | SvxIconViewFlags::POS_MOVED );
+
+ SvxIconChoiceCtrlEntry* pNext;
+ if( nCur == nCount )
+ pNext = maEntries[ 0 ].get();
+ else
+ pNext = maEntries[ nCur ].get();
+ pPrev->pflink = pNext;
+ pNext->pblink = pPrev;
+ pPrev = pNext;
+ }
+ pHead = maEntries[ 0 ].get();
+ }
+ else
+ pHead = nullptr;
+}
+
+void SvxIconChoiceCtrl_Impl::ClearPredecessors()
+{
+ if( pHead )
+ {
+ size_t nCount = maEntries.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pCur = maEntries[ nCur ].get();
+ pCur->pflink = nullptr;
+ pCur->pblink = nullptr;
+ }
+ pHead = nullptr;
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::Arrange( bool bKeepPredecessors, tools::Long nSetMaxVirtWidth, tools::Long nSetMaxVirtHeight )
+{
+ if ( nSetMaxVirtWidth != 0 )
+ nMaxVirtWidth = nSetMaxVirtWidth;
+ else
+ nMaxVirtWidth = aOutputSize.Width();
+
+ if ( nSetMaxVirtHeight != 0 )
+ nMaxVirtHeight = nSetMaxVirtHeight;
+ else
+ nMaxVirtHeight = aOutputSize.Height();
+
+ ImpArrange( bKeepPredecessors );
+}
+
+void SvxIconChoiceCtrl_Impl::ImpArrange( bool bKeepPredecessors )
+{
+ static const Point aEmptyPoint;
+
+ bool bOldUpdate = bUpdateMode;
+ tools::Rectangle aCurOutputArea( GetOutputRect() );
+ if( (nWinBits & WB_SMART_ARRANGE) && aCurOutputArea.TopLeft() != aEmptyPoint )
+ bUpdateMode = false;
+ aAutoArrangeIdle.Stop();
+ nFlags |= IconChoiceFlags::Arranging;
+ ShowCursor( false );
+ ResetVirtSize();
+ if( !bKeepPredecessors )
+ ClearPredecessors();
+ bBoundRectsDirty = false;
+ SetOrigin( Point() );
+ VisRectChanged();
+ RecalcAllBoundingRectsSmart();
+ // TODO: the invalidation in the detail view should be more intelligent
+ //if( !(nWinBits & WB_DETAILS ))
+ pView->Invalidate( InvalidateFlags::NoChildren );
+ nFlags &= ~IconChoiceFlags::Arranging;
+ if( (nWinBits & WB_SMART_ARRANGE) && aCurOutputArea.TopLeft() != aEmptyPoint )
+ {
+ MakeVisible( aCurOutputArea );
+ SetUpdateMode( bOldUpdate );
+ }
+ ShowCursor( true );
+}
+
+void SvxIconChoiceCtrl_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+#if defined(OV_DRAWGRID)
+ Color aOldColor (rRenderContext.GetLineColor());
+ Color aCOL_BLACK);
+ rRenderContext.SetLineColor( aColor );
+ Point aOffs(rRenderContext.GetMapMode().GetOrigin());
+ Size aXSize(GetOutputSizePixel());
+ {
+ Point aStart(LROFFS_WINBORDER, 0);
+ Point aEnd(LROFFS_WINBORDER, aXSize.Height());
+ aStart -= aOffs;
+ aEnd -= aOffs;
+ rRenderContext.DrawLine(aStart, aEnd);
+ }
+ {
+ Point aStart(0, TBOFFS_WINBORDER);
+ Point aEnd(aXSize.Width(), TBOFFS_WINBORDER);
+ aStart -= aOffs;
+ aEnd -= aOffs;
+ rRenderContext.DrawLine(aStart, aEnd);
+ }
+
+ for (tools::Long nDX = nGridDX; nDX <= aXSize.Width(); nDX += nGridDX)
+ {
+ Point aStart( nDX+LROFFS_WINBORDER, 0 );
+ Point aEnd( nDX+LROFFS_WINBORDER, aXSize.Height());
+ aStart -= aOffs;
+ aEnd -= aOffs;
+ rRenderContext.DrawLine(aStart, aEnd);
+ }
+ for (tools::Long nDY = nGridDY; nDY <= aXSize.Height(); nDY += nGridDY)
+ {
+ Point aStart(0, nDY + TBOFFS_WINBORDER);
+ Point aEnd(aXSize.Width(), nDY + TBOFFS_WINBORDER);
+ aStart -= aOffs;
+ aEnd -= aOffs;
+ rRenderContext.DrawLine(aStart, aEnd);
+ }
+ rRenderContext.SetLineColor(aOldColor);
+#endif
+
+ if (!maEntries.size())
+ return;
+ if (!pCursor)
+ {
+ // set cursor to item with focus-flag
+ bool bfound = false;
+ for (sal_Int32 i = 0; i < pView->GetEntryCount() && !bfound; i++)
+ {
+ SvxIconChoiceCtrlEntry* pEntry = pView->GetEntry(i);
+ if (pEntry->IsFocused())
+ {
+ pCursor = pEntry;
+ bfound = true;
+ }
+ }
+
+ if (!bfound)
+ pCursor = maEntries[ 0 ].get();
+ }
+
+ size_t nCount = maZOrderList.size();
+ if (!nCount)
+ return;
+
+ rRenderContext.Push(vcl::PushFlags::CLIPREGION);
+ rRenderContext.SetClipRegion(vcl::Region(rRect));
+
+ std::vector< SvxIconChoiceCtrlEntry* > aNewZOrderList;
+ std::vector< SvxIconChoiceCtrlEntry* > aPaintedEntries;
+
+ size_t nPos = 0;
+ while(nCount)
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maZOrderList[nPos];
+ const tools::Rectangle& rBoundRect = GetEntryBoundRect(pEntry);
+ if (rRect.Overlaps(rBoundRect))
+ {
+ PaintEntry(pEntry, rBoundRect.TopLeft(), rRenderContext);
+ // set entries to Top if they are being repainted
+ aPaintedEntries.push_back(pEntry);
+ }
+ else
+ aNewZOrderList.push_back(pEntry);
+
+ nCount--;
+ nPos++;
+ }
+ maZOrderList = std::move( aNewZOrderList );
+ maZOrderList.insert(maZOrderList.end(), aPaintedEntries.begin(), aPaintedEntries.end());
+
+ rRenderContext.Pop();
+}
+
+void SvxIconChoiceCtrl_Impl::RepaintSelectedEntries()
+{
+ const size_t nCount = maZOrderList.size();
+ if (!nCount)
+ return;
+
+ tools::Rectangle aOutRect(GetOutputRect());
+ for (size_t nCur = 0; nCur < nCount; nCur++)
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maZOrderList[nCur];
+ if (pEntry->GetFlags() & SvxIconViewFlags::SELECTED)
+ {
+ const tools::Rectangle& rBoundRect = GetEntryBoundRect(pEntry);
+ if (aOutRect.Overlaps(rBoundRect))
+ pView->Invalidate(rBoundRect);
+ }
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::InitScrollBarBox()
+{
+ aScrBarBox->SetSizePixel( Size(nVerSBarWidth-1, nHorSBarHeight-1) );
+ Size aSize( pView->GetOutputSizePixel() );
+ aScrBarBox->SetPosPixel( Point(aSize.Width()-nVerSBarWidth+1, aSize.Height()-nHorSBarHeight+1));
+}
+
+bool SvxIconChoiceCtrl_Impl::MouseButtonDown( const MouseEvent& rMEvt)
+{
+ bool bHandled = true;
+ bHighlightFramePressed = false;
+ bool bGotFocus = (!pView->HasFocus() && !(nWinBits & WB_NOPOINTERFOCUS));
+ if( !(nWinBits & WB_NOPOINTERFOCUS) )
+ pView->GrabFocus();
+
+ Point aDocPos( rMEvt.GetPosPixel() );
+ if(aDocPos.X()>=aOutputSize.Width() || aDocPos.Y()>=aOutputSize.Height())
+ return false;
+ ToDocPos( aDocPos );
+ SvxIconChoiceCtrlEntry* pEntry = GetEntry( aDocPos, true );
+ if( pEntry )
+ MakeEntryVisible( pEntry, false );
+
+ if( rMEvt.IsShift() && eSelectionMode != SelectionMode::Single )
+ {
+ if( pEntry )
+ SetCursor_Impl( pCursor, pEntry, rMEvt.IsMod1(), rMEvt.IsShift() );
+ return true;
+ }
+
+ if( pAnchor && (rMEvt.IsShift() || rMEvt.IsMod1())) // keyboard selection?
+ {
+ DBG_ASSERT(eSelectionMode != SelectionMode::Single,"Invalid selection mode");
+ if( rMEvt.IsMod1() )
+ nFlags |= IconChoiceFlags::AddMode;
+
+ if( rMEvt.IsShift() )
+ {
+ tools::Rectangle aRect( GetEntryBoundRect( pAnchor ));
+ if( pEntry )
+ aRect.Union( GetEntryBoundRect( pEntry ) );
+ else
+ {
+ tools::Rectangle aTempRect( aDocPos, Size(1,1));
+ aRect.Union( aTempRect );
+ }
+ aCurSelectionRect = aRect;
+ SelectRect( aRect, bool(nFlags & IconChoiceFlags::AddMode), &aSelectedRectList );
+ }
+ else if( rMEvt.IsMod1() )
+ {
+ AddSelectedRect( aCurSelectionRect );
+ pAnchor = nullptr;
+ aCurSelectionRect.SetPos( aDocPos );
+ }
+
+ if( !pEntry && !(nWinBits & WB_NODRAGSELECTION))
+ pView->StartTracking( StartTrackingFlags::ScrollRepeat );
+ return true;
+ }
+ else
+ {
+ if( !pEntry )
+ {
+ if( eSelectionMode == SelectionMode::Multiple )
+ {
+ if( !rMEvt.IsMod1() ) // Ctrl
+ {
+ if( !bGotFocus )
+ {
+ SetNoSelection();
+ ClearSelectedRectList();
+ }
+ }
+ else
+ nFlags |= IconChoiceFlags::AddMode;
+ aCurSelectionRect.SetPos( aDocPos );
+ pView->StartTracking( StartTrackingFlags::ScrollRepeat );
+ }
+ else
+ bHandled = false;
+ return bHandled;
+ }
+ }
+ bool bSelected = pEntry->IsSelected();
+
+ if( rMEvt.GetClicks() == 2 )
+ {
+ DeselectAllBut( pEntry );
+ SelectEntry( pEntry, true, false );
+ pHdlEntry = pEntry;
+ pView->ClickIcon();
+ }
+ else
+ {
+ // Inplace-Editing ?
+ if( rMEvt.IsMod2() ) // Alt?
+ {
+ }
+ else if( eSelectionMode == SelectionMode::Single )
+ {
+ DeselectAllBut( pEntry );
+ SetCursor( pEntry );
+ }
+ else if( eSelectionMode == SelectionMode::NONE )
+ {
+ if( rMEvt.IsLeft() && (nWinBits & WB_HIGHLIGHTFRAME) )
+ {
+ pCurHighlightFrame = nullptr; // force repaint of frame
+ bHighlightFramePressed = true;
+ SetEntryHighlightFrame( pEntry, true );
+ }
+ }
+ else
+ {
+ if( !rMEvt.GetModifier() && rMEvt.IsLeft() )
+ {
+ if( !bSelected )
+ {
+ DeselectAllBut( pEntry );
+ SetCursor( pEntry );
+ SelectEntry( pEntry, true, false );
+ }
+ else
+ {
+ // deselect only in the Up, if the Move happened via D&D!
+ nFlags |= IconChoiceFlags::DownDeselect;
+ }
+ }
+ else if( rMEvt.IsMod1() )
+ nFlags |= IconChoiceFlags::DownCtrl;
+ }
+ }
+ return bHandled;
+}
+
+bool SvxIconChoiceCtrl_Impl::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ bool bHandled = false;
+ if( rMEvt.IsRight() && (nFlags & (IconChoiceFlags::DownCtrl | IconChoiceFlags::DownDeselect) ))
+ {
+ nFlags &= ~IconChoiceFlags(IconChoiceFlags::DownCtrl | IconChoiceFlags::DownDeselect);
+ bHandled = true;
+ }
+
+ Point aDocPos( rMEvt.GetPosPixel() );
+ ToDocPos( aDocPos );
+ SvxIconChoiceCtrlEntry* pDocEntry = GetEntry( aDocPos );
+ if( pDocEntry )
+ {
+ if( nFlags & IconChoiceFlags::DownCtrl )
+ {
+ // Ctrl & MultiSelection
+ ToggleSelection( pDocEntry );
+ SetCursor( pDocEntry );
+ bHandled = true;
+ }
+ else if( nFlags & IconChoiceFlags::DownDeselect )
+ {
+ DeselectAllBut( pDocEntry );
+ SetCursor( pDocEntry );
+ SelectEntry( pDocEntry, true, false );
+ bHandled = true;
+ }
+ }
+
+ nFlags &= ~IconChoiceFlags(IconChoiceFlags::DownCtrl | IconChoiceFlags::DownDeselect);
+
+ if((nWinBits & WB_HIGHLIGHTFRAME) && bHighlightFramePressed && pCurHighlightFrame)
+ {
+ bHandled = true;
+ SvxIconChoiceCtrlEntry* pEntry = pCurHighlightFrame;
+ pCurHighlightFrame = nullptr; // force repaint of frame
+ bHighlightFramePressed = false;
+ SetEntryHighlightFrame( pEntry, true );
+
+ pHdlEntry = pCurHighlightFrame;
+ pView->ClickIcon();
+
+ // set focus on Icon
+ SvxIconChoiceCtrlEntry* pOldCursor = pCursor;
+ SetCursor_Impl( pOldCursor, pHdlEntry, false, false );
+
+ pHdlEntry = nullptr;
+ }
+ return bHandled;
+}
+
+bool SvxIconChoiceCtrl_Impl::MouseMove( const MouseEvent& rMEvt )
+{
+ const Point aDocPos( pView->PixelToLogic(rMEvt.GetPosPixel()) );
+
+ if( pView->IsTracking() )
+ return false;
+ else if( nWinBits & WB_HIGHLIGHTFRAME )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = GetEntry( aDocPos, true );
+ SetEntryHighlightFrame( pEntry, false );
+ }
+ else
+ return false;
+ return true;
+}
+
+void SvxIconChoiceCtrl_Impl::SetCursor_Impl( SvxIconChoiceCtrlEntry* pOldCursor,
+ SvxIconChoiceCtrlEntry* pNewCursor, bool bMod1, bool bShift )
+{
+ if( !pNewCursor )
+ return;
+
+ SvxIconChoiceCtrlEntry* pFilterEntry = nullptr;
+ bool bDeselectAll = false;
+ if( eSelectionMode != SelectionMode::Single )
+ {
+ if( !bMod1 && !bShift )
+ bDeselectAll = true;
+ else if( bShift && !bMod1 && !pAnchor )
+ {
+ bDeselectAll = true;
+ pFilterEntry = pOldCursor;
+ }
+ }
+ if( bDeselectAll )
+ DeselectAllBut( pFilterEntry );
+ ShowCursor( false );
+ MakeEntryVisible( pNewCursor );
+ SetCursor( pNewCursor );
+ if( bMod1 && !bShift )
+ {
+ if( pAnchor )
+ {
+ AddSelectedRect( pAnchor, pOldCursor );
+ pAnchor = nullptr;
+ }
+ }
+ else if( bShift )
+ {
+ if( !pAnchor )
+ pAnchor = pOldCursor;
+ if ( nWinBits & WB_ALIGN_LEFT )
+ SelectRange( pAnchor, pNewCursor, bool(nFlags & IconChoiceFlags::AddMode) );
+ else
+ SelectRect(pAnchor,pNewCursor, bool(nFlags & IconChoiceFlags::AddMode), &aSelectedRectList);
+ }
+ else
+ {
+ SelectEntry( pCursor, true, false );
+ aCurSelectionRect = GetEntryBoundRect( pCursor );
+ CallEventListeners( VclEventId::ListboxSelect, pCursor );
+ }
+}
+
+bool SvxIconChoiceCtrl_Impl::KeyInput( const KeyEvent& rKEvt )
+{
+ bool bMod2 = rKEvt.GetKeyCode().IsMod2();
+ sal_Unicode cChar = rKEvt.GetCharCode();
+ sal_uLong nPos = sal_uLong(-1);
+ if ( bMod2 && cChar && IsMnemonicChar( cChar, nPos ) )
+ {
+ // shortcut is clicked
+ SvxIconChoiceCtrlEntry* pNewCursor = GetEntry( nPos );
+ SvxIconChoiceCtrlEntry* pOldCursor = pCursor;
+ if ( pNewCursor != pOldCursor )
+ SetCursor_Impl( pOldCursor, pNewCursor, false, false );
+ return true;
+ }
+
+ if ( bMod2 )
+ // no actions with <ALT>
+ return false;
+
+ bool bKeyUsed = true;
+ bool bMod1 = rKEvt.GetKeyCode().IsMod1();
+ bool bShift = rKEvt.GetKeyCode().IsShift();
+
+ if( eSelectionMode == SelectionMode::Single || eSelectionMode == SelectionMode::NONE)
+ {
+ bShift = false;
+ bMod1 = false;
+ }
+
+ if( bMod1 )
+ nFlags |= IconChoiceFlags::AddMode;
+
+ SvxIconChoiceCtrlEntry* pNewCursor;
+ SvxIconChoiceCtrlEntry* pOldCursor = pCursor;
+
+ sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
+ switch( nCode )
+ {
+ case KEY_UP:
+ case KEY_PAGEUP:
+ if( pCursor )
+ {
+ MakeEntryVisible( pCursor );
+ if( nCode == KEY_UP )
+ pNewCursor = pImpCursor->GoUpDown(pCursor,false);
+ else
+ pNewCursor = pImpCursor->GoPageUpDown(pCursor,false);
+ SetCursor_Impl( pOldCursor, pNewCursor, bMod1, bShift );
+ if( !pNewCursor )
+ {
+ tools::Rectangle aRect( GetEntryBoundRect( pCursor ) );
+ if( aRect.Top())
+ {
+ aRect.AdjustBottom( -(aRect.Top()) );
+ aRect.SetTop( 0 );
+ MakeVisible( aRect );
+ }
+ }
+ }
+ break;
+
+ case KEY_DOWN:
+ case KEY_PAGEDOWN:
+ if( pCursor )
+ {
+ if( nCode == KEY_DOWN )
+ pNewCursor=pImpCursor->GoUpDown( pCursor,true );
+ else
+ pNewCursor=pImpCursor->GoPageUpDown( pCursor,true );
+ SetCursor_Impl( pOldCursor, pNewCursor, bMod1, bShift );
+ }
+ break;
+
+ case KEY_RIGHT:
+ if( pCursor )
+ {
+ pNewCursor=pImpCursor->GoLeftRight(pCursor,true );
+ SetCursor_Impl( pOldCursor, pNewCursor, bMod1, bShift );
+ }
+ break;
+
+ case KEY_LEFT:
+ if( pCursor )
+ {
+ MakeEntryVisible( pCursor );
+ pNewCursor = pImpCursor->GoLeftRight(pCursor,false );
+ SetCursor_Impl( pOldCursor, pNewCursor, bMod1, bShift );
+ if( !pNewCursor )
+ {
+ tools::Rectangle aRect( GetEntryBoundRect(pCursor));
+ if( aRect.Left() )
+ {
+ aRect.AdjustRight( -(aRect.Left()) );
+ aRect.SetLeft( 0 );
+ MakeVisible( aRect );
+ }
+ }
+ }
+ break;
+
+ case KEY_F2:
+ if( bMod1 || bShift )
+ bKeyUsed = false;
+ break;
+
+ case KEY_F8:
+ if( rKEvt.GetKeyCode().IsShift() )
+ {
+ if( nFlags & IconChoiceFlags::AddMode )
+ nFlags &= ~IconChoiceFlags::AddMode;
+ else
+ nFlags |= IconChoiceFlags::AddMode;
+ }
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_SPACE:
+ if( pCursor && eSelectionMode != SelectionMode::Single )
+ {
+ if( !bMod1 )
+ {
+ //SelectAll( false );
+ SetNoSelection();
+ ClearSelectedRectList();
+
+ // click Icon with spacebar
+ SetEntryHighlightFrame( GetCurEntry(), true );
+ pView->ClickIcon();
+ pHdlEntry = pCurHighlightFrame;
+ pCurHighlightFrame=nullptr;
+ }
+ else
+ ToggleSelection( pCursor );
+ }
+ break;
+
+#ifdef DBG_UTIL
+ case KEY_F10:
+ if( rKEvt.GetKeyCode().IsShift() )
+ {
+ if( pCursor )
+ pView->SetEntryTextMode( SvxIconChoiceCtrlTextMode::Full, pCursor );
+ }
+ if( rKEvt.GetKeyCode().IsMod1() )
+ {
+ if( pCursor )
+ pView->SetEntryTextMode( SvxIconChoiceCtrlTextMode::Short, pCursor );
+ }
+ break;
+#endif
+
+ case KEY_ADD:
+ case KEY_DIVIDE :
+ case KEY_A:
+ if( bMod1 && (eSelectionMode != SelectionMode::Single))
+ SelectAll();
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_SUBTRACT:
+ case KEY_COMMA :
+ if( bMod1 )
+ SetNoSelection();
+ else
+ bKeyUsed = false;
+ break;
+
+ case KEY_RETURN:
+ if( !bMod1 )
+ bKeyUsed = false;
+ break;
+
+ case KEY_END:
+ if( pCursor )
+ {
+ pNewCursor = maEntries.back().get();
+ SetCursor_Impl( pOldCursor, pNewCursor, bMod1, bShift );
+ }
+ break;
+
+ case KEY_HOME:
+ if( pCursor )
+ {
+ pNewCursor = maEntries[ 0 ].get();
+ SetCursor_Impl( pOldCursor, pNewCursor, bMod1, bShift );
+ }
+ break;
+
+ default:
+ bKeyUsed = false;
+
+ }
+ return bKeyUsed;
+}
+
+// recalculate TopLeft of scrollbars (but not their sizes!)
+void SvxIconChoiceCtrl_Impl::PositionScrollBars( tools::Long nRealWidth, tools::Long nRealHeight )
+{
+ // horizontal scrollbar
+ Point aPos( 0, nRealHeight );
+ aPos.AdjustY( -nHorSBarHeight );
+
+ if( aHorSBar->GetPosPixel() != aPos )
+ aHorSBar->SetPosPixel( aPos );
+
+ // vertical scrollbar
+ aPos.setX( nRealWidth ); aPos.setY( 0 );
+ aPos.AdjustX( -nVerSBarWidth );
+ aPos.AdjustX( 1 );
+ aPos.AdjustY( -1 );
+
+ if( aVerSBar->GetPosPixel() != aPos )
+ aVerSBar->SetPosPixel( aPos );
+}
+
+void SvxIconChoiceCtrl_Impl::AdjustScrollBars()
+{
+ tools::Long nVirtHeight = aVirtOutputSize.Height();
+ tools::Long nVirtWidth = aVirtOutputSize.Width();
+
+ Size aOSize( pView->Control::GetOutputSizePixel() );
+ tools::Long nRealHeight = aOSize.Height();
+ tools::Long nRealWidth = aOSize.Width();
+
+ PositionScrollBars( nRealWidth, nRealHeight );
+
+ const MapMode& rMapMode = pView->GetMapMode();
+ Point aOrigin( rMapMode.GetOrigin() );
+
+ tools::Long nVisibleWidth;
+ if( nRealWidth > nVirtWidth )
+ nVisibleWidth = nVirtWidth + aOrigin.X();
+ else
+ nVisibleWidth = nRealWidth;
+
+ tools::Long nVisibleHeight;
+ if( nRealHeight > nVirtHeight )
+ nVisibleHeight = nVirtHeight + aOrigin.Y();
+ else
+ nVisibleHeight = nRealHeight;
+
+ bool bVerSBar = ( nWinBits & WB_VSCROLL ) != 0;
+ bool bHorSBar = ( nWinBits & WB_HSCROLL ) != 0;
+ bool bNoVerSBar = ( nWinBits & WB_NOVSCROLL ) != 0;
+ bool bNoHorSBar = ( nWinBits & WB_NOHSCROLL ) != 0;
+
+ sal_uInt16 nResult = 0;
+ if( nVirtHeight )
+ {
+ // activate vertical scrollbar?
+ if( !bNoVerSBar && (bVerSBar || ( nVirtHeight > nVisibleHeight)) )
+ {
+ nResult = 0x0001;
+ nRealWidth -= nVerSBarWidth;
+
+ if( nRealWidth > nVirtWidth )
+ nVisibleWidth = nVirtWidth + aOrigin.X();
+ else
+ nVisibleWidth = nRealWidth;
+ }
+ // activate horizontal scrollbar?
+ if( !bNoHorSBar && (bHorSBar || (nVirtWidth > nVisibleWidth)) )
+ {
+ nResult |= 0x0002;
+ nRealHeight -= nHorSBarHeight;
+
+ if( nRealHeight > nVirtHeight )
+ nVisibleHeight = nVirtHeight + aOrigin.Y();
+ else
+ nVisibleHeight = nRealHeight;
+
+ // do we need a vertical scrollbar after all?
+ if( !(nResult & 0x0001) && // only if not already there
+ ( !bNoVerSBar && ((nVirtHeight > nVisibleHeight) || bVerSBar)) )
+ {
+ nResult = 3; // both turned on
+ nRealWidth -= nVerSBarWidth;
+
+ if( nRealWidth > nVirtWidth )
+ nVisibleWidth = nVirtWidth + aOrigin.X();
+ else
+ nVisibleWidth = nRealWidth;
+ }
+ }
+ }
+
+ // size vertical scrollbar
+ tools::Long nThumb = aVerSBar->GetThumbPos();
+ Size aSize( nVerSBarWidth, nRealHeight );
+ aSize.AdjustHeight(2 );
+ if( aSize != aVerSBar->GetSizePixel() )
+ aVerSBar->SetSizePixel( aSize );
+ aVerSBar->SetVisibleSize( nVisibleHeight );
+ aVerSBar->SetPageSize( GetScrollBarPageSize( nVisibleHeight ));
+
+ if( nResult & 0x0001 )
+ {
+ aVerSBar->SetThumbPos( nThumb );
+ aVerSBar->Show();
+ }
+ else
+ {
+ aVerSBar->SetThumbPos( 0 );
+ aVerSBar->Hide();
+ }
+
+ // size horizontal scrollbar
+ nThumb = aHorSBar->GetThumbPos();
+ aSize.setWidth( nRealWidth );
+ aSize.setHeight( nHorSBarHeight );
+ aSize.AdjustWidth( 1 );
+ if( nResult & 0x0001 ) // vertical scrollbar?
+ {
+ aSize.AdjustWidth( 1 );
+ nRealWidth++;
+ }
+ if( aSize != aHorSBar->GetSizePixel() )
+ aHorSBar->SetSizePixel( aSize );
+ aHorSBar->SetVisibleSize( nVisibleWidth );
+ aHorSBar->SetPageSize( GetScrollBarPageSize(nVisibleWidth ));
+ if( nResult & 0x0002 )
+ {
+ aHorSBar->SetThumbPos( nThumb );
+ aHorSBar->Show();
+ }
+ else
+ {
+ aHorSBar->SetThumbPos( 0 );
+ aHorSBar->Hide();
+ }
+
+ aOutputSize.setWidth( nRealWidth );
+ if( nResult & 0x0002 ) // horizontal scrollbar ?
+ nRealHeight++; // because lower border is clipped
+ aOutputSize.setHeight( nRealHeight );
+
+ if( (nResult & (0x0001|0x0002)) == (0x0001|0x0002) )
+ aScrBarBox->Show();
+ else
+ aScrBarBox->Hide();
+}
+
+void SvxIconChoiceCtrl_Impl::Resize()
+{
+ InitScrollBarBox();
+ aOutputSize = pView->GetOutputSizePixel();
+ pImpCursor->Clear();
+ pGridMap->OutputSizeChanged();
+
+ const Size& rSize = pView->Control::GetOutputSizePixel();
+ PositionScrollBars( rSize.Width(), rSize.Height() );
+ // The scrollbars are shown/hidden asynchronously, so derived classes can
+ // do an Arrange during Resize, without the scrollbars suddenly turning
+ // on and off again.
+ // If an event is already underway, we don't need to send a new one, at least
+ // as long as there is only one event type.
+ if ( ! nUserEventAdjustScrBars )
+ nUserEventAdjustScrBars =
+ Application::PostUserEvent( LINK( this, SvxIconChoiceCtrl_Impl, UserEventHdl),
+ EVENTID_ADJUST_SCROLLBARS);
+
+ VisRectChanged();
+}
+
+bool SvxIconChoiceCtrl_Impl::CheckHorScrollBar()
+{
+ if( maZOrderList.empty() || !aHorSBar->IsVisible() )
+ return false;
+ const MapMode& rMapMode = pView->GetMapMode();
+ Point aOrigin( rMapMode.GetOrigin() );
+ if(!( nWinBits & WB_HSCROLL) && !aOrigin.X() )
+ {
+ tools::Long nWidth = aOutputSize.Width();
+ const size_t nCount = maZOrderList.size();
+ tools::Long nMostRight = 0;
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maZOrderList[ nCur ];
+ tools::Long nRight = GetEntryBoundRect(pEntry).Right();
+ if( nRight > nWidth )
+ return false;
+ if( nRight > nMostRight )
+ nMostRight = nRight;
+ }
+ aHorSBar->Hide();
+ aOutputSize.AdjustHeight(nHorSBarHeight );
+ aVirtOutputSize.setWidth( nMostRight );
+ aHorSBar->SetThumbPos( 0 );
+ Range aRange;
+ aRange.Max() = nMostRight - 1;
+ aHorSBar->SetRange( aRange );
+ if( aVerSBar->IsVisible() )
+ {
+ Size aSize( aVerSBar->GetSizePixel());
+ aSize.AdjustHeight(nHorSBarHeight );
+ aVerSBar->SetSizePixel( aSize );
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SvxIconChoiceCtrl_Impl::CheckVerScrollBar()
+{
+ if( maZOrderList.empty() || !aVerSBar->IsVisible() )
+ return false;
+ const MapMode& rMapMode = pView->GetMapMode();
+ Point aOrigin( rMapMode.GetOrigin() );
+ if(!( nWinBits & WB_VSCROLL) && !aOrigin.Y() )
+ {
+ tools::Long nDeepest = 0;
+ tools::Long nHeight = aOutputSize.Height();
+ const size_t nCount = maZOrderList.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maZOrderList[ nCur ];
+ tools::Long nBottom = GetEntryBoundRect(pEntry).Bottom();
+ if( nBottom > nHeight )
+ return false;
+ if( nBottom > nDeepest )
+ nDeepest = nBottom;
+ }
+ aVerSBar->Hide();
+ aOutputSize.AdjustWidth(nVerSBarWidth );
+ aVirtOutputSize.setHeight( nDeepest );
+ aVerSBar->SetThumbPos( 0 );
+ Range aRange;
+ aRange.Max() = nDeepest - 1;
+ aVerSBar->SetRange( aRange );
+ if( aHorSBar->IsVisible() )
+ {
+ Size aSize( aHorSBar->GetSizePixel());
+ aSize.AdjustWidth(nVerSBarWidth );
+ aHorSBar->SetSizePixel( aSize );
+ }
+ return true;
+ }
+ return false;
+}
+
+
+// hides scrollbars if they're unnecessary
+void SvxIconChoiceCtrl_Impl::CheckScrollBars()
+{
+ CheckVerScrollBar();
+ if( CheckHorScrollBar() )
+ CheckVerScrollBar();
+ if( aVerSBar->IsVisible() && aHorSBar->IsVisible() )
+ aScrBarBox->Show();
+ else
+ aScrBarBox->Hide();
+}
+
+
+void SvxIconChoiceCtrl_Impl::GetFocus()
+{
+ RepaintSelectedEntries();
+ if( pCursor )
+ {
+ pCursor->SetFlags( SvxIconViewFlags::FOCUSED );
+ ShowCursor( true );
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::LoseFocus()
+{
+ if( pCursor )
+ pCursor->ClearFlags( SvxIconViewFlags::FOCUSED );
+ ShowCursor( false );
+
+// HideFocus ();
+// pView->Invalidate ( aFocus.aRect );
+
+ RepaintSelectedEntries();
+}
+
+void SvxIconChoiceCtrl_Impl::SetUpdateMode( bool bUpdate )
+{
+ if( bUpdate != bUpdateMode )
+ {
+ bUpdateMode = bUpdate;
+ if( bUpdate )
+ {
+ AdjustScrollBars();
+ pImpCursor->Clear();
+ pGridMap->Clear();
+ pView->Invalidate(InvalidateFlags::NoChildren);
+ }
+ }
+}
+
+// priorities of the emphasis: bSelected
+void SvxIconChoiceCtrl_Impl::PaintEmphasis(const tools::Rectangle& rTextRect, bool bSelected,
+ vcl::RenderContext& rRenderContext)
+{
+ Color aOldFillColor(rRenderContext.GetFillColor());
+
+ bool bSolidTextRect = false;
+
+ if (!bSelected)
+ {
+ const Color& rFillColor = rRenderContext.GetFont().GetFillColor();
+ rRenderContext.SetFillColor(rFillColor);
+ if (rFillColor != COL_TRANSPARENT)
+ bSolidTextRect = true;
+ }
+
+ // draw text rectangle
+ if (bSolidTextRect)
+ {
+ rRenderContext.DrawRect(rTextRect);
+ }
+
+ rRenderContext.SetFillColor(aOldFillColor);
+}
+
+
+void SvxIconChoiceCtrl_Impl::PaintItem(const tools::Rectangle& rRect,
+ IcnViewFieldType eItem, SvxIconChoiceCtrlEntry* pEntry, sal_uInt16 nPaintFlags,
+ vcl::RenderContext& rRenderContext )
+{
+ if (eItem == IcnViewFieldType::Text)
+ {
+ OUString aText = SvtIconChoiceCtrl::GetEntryText(pEntry);
+
+ rRenderContext.DrawText(rRect, aText, nCurTextDrawFlags);
+
+ if (pEntry->IsFocused())
+ {
+ tools::Rectangle aRect (CalcFocusRect(pEntry));
+ ShowFocus(aRect);
+ DrawFocusRect(rRenderContext);
+ }
+ }
+ else
+ {
+ Point aPos(rRect.TopLeft());
+ if (nPaintFlags & PAINTFLAG_HOR_CENTERED)
+ aPos.AdjustX((rRect.GetWidth() - aImageSize.Width()) / 2 );
+ if (nPaintFlags & PAINTFLAG_VER_CENTERED)
+ aPos.AdjustY((rRect.GetHeight() - aImageSize.Height()) / 2 );
+ SvtIconChoiceCtrl::DrawEntryImage(pEntry, aPos, rRenderContext);
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::PaintEntry(SvxIconChoiceCtrlEntry* pEntry, const Point& rPos, vcl::RenderContext& rRenderContext)
+{
+ bool bSelected = false;
+
+ if (eSelectionMode != SelectionMode::NONE)
+ bSelected = pEntry->IsSelected();
+
+ rRenderContext.Push(vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR);
+
+ OUString aEntryText(SvtIconChoiceCtrl::GetEntryText(pEntry));
+ tools::Rectangle aTextRect(CalcTextRect(pEntry, &rPos, &aEntryText));
+ tools::Rectangle aBmpRect(CalcBmpRect(pEntry, &rPos));
+
+ bool bShowSelection = (bSelected && (eSelectionMode != SelectionMode::NONE));
+
+ bool bActiveSelection = (0 != (nWinBits & WB_NOHIDESELECTION)) || pView->HasFocus();
+
+ if (bShowSelection)
+ {
+ const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
+ vcl::Font aNewFont(rRenderContext.GetFont());
+
+ // font fill colors that are attributed "hard" need corresponding "hard"
+ // attributed highlight colors
+ if ((nWinBits & WB_NOHIDESELECTION) || pView->HasFocus())
+ aNewFont.SetFillColor(rSettings.GetHighlightColor());
+ else
+ aNewFont.SetFillColor(rSettings.GetDeactiveColor());
+
+ Color aWinCol = rSettings.GetWindowTextColor();
+ if (!bActiveSelection && rSettings.GetFaceColor().IsBright() == aWinCol.IsBright())
+ aNewFont.SetColor(rSettings.GetWindowTextColor());
+ else
+ aNewFont.SetColor(rSettings.GetHighlightTextColor());
+
+ rRenderContext.SetFont(aNewFont);
+
+ rRenderContext.SetFillColor(rRenderContext.GetBackground().GetColor());
+ rRenderContext.DrawRect(CalcFocusRect(pEntry));
+ rRenderContext.SetFillColor();
+ }
+
+ bool bResetClipRegion = false;
+ if (!rRenderContext.IsClipRegion() && (aVerSBar->IsVisible() || aHorSBar->IsVisible()))
+ {
+ tools::Rectangle aOutputArea(GetOutputRect());
+ if (aOutputArea.Overlaps(aTextRect) || aOutputArea.Overlaps(aBmpRect))
+ {
+ rRenderContext.SetClipRegion(vcl::Region(aOutputArea));
+ bResetClipRegion = true;
+ }
+ }
+
+ bool bLargeIconMode = WB_ICON == ( nWinBits & VIEWMODE_MASK );
+ sal_uInt16 nBmpPaintFlags = PAINTFLAG_VER_CENTERED;
+ if (bLargeIconMode)
+ nBmpPaintFlags |= PAINTFLAG_HOR_CENTERED;
+ sal_uInt16 nTextPaintFlags = bLargeIconMode ? PAINTFLAG_HOR_CENTERED : PAINTFLAG_VER_CENTERED;
+
+ PaintEmphasis(aTextRect, bSelected, rRenderContext);
+
+ if ( bShowSelection )
+ vcl::RenderTools::DrawSelectionBackground(rRenderContext, *pView, CalcFocusRect(pEntry),
+ bActiveSelection ? 1 : 2, false, true, false);
+
+
+ PaintItem(aBmpRect, IcnViewFieldType::Image, pEntry, nBmpPaintFlags, rRenderContext);
+
+ PaintItem(aTextRect, IcnViewFieldType::Text, pEntry, nTextPaintFlags, rRenderContext);
+
+ // draw highlight frame
+ if (pEntry == pCurHighlightFrame)
+ DrawHighlightFrame(rRenderContext, CalcFocusRect(pEntry));
+
+ rRenderContext.Pop();
+ if (bResetClipRegion)
+ rRenderContext.SetClipRegion();
+}
+
+void SvxIconChoiceCtrl_Impl::SetEntryPos( SvxIconChoiceCtrlEntry* pEntry, const Point& rPos )
+{
+ ShowCursor( false );
+ tools::Rectangle aBoundRect( GetEntryBoundRect( pEntry ));
+ pView->Invalidate( aBoundRect );
+ ToTop( pEntry );
+ if( !IsAutoArrange() )
+ {
+ bool bAdjustVirtSize = false;
+ if( rPos != aBoundRect.TopLeft() )
+ {
+ Point aGridOffs(
+ pEntry->aGridRect.TopLeft() - pEntry->aRect.TopLeft() );
+ pImpCursor->Clear();
+ pGridMap->Clear();
+ aBoundRect.SetPos( rPos );
+ pEntry->aRect = aBoundRect;
+ pEntry->aGridRect.SetPos( rPos + aGridOffs );
+ bAdjustVirtSize = true;
+ }
+ if( bAdjustVirtSize )
+ AdjustVirtSize( pEntry->aRect );
+
+ pView->Invalidate( pEntry->aRect );
+ pGridMap->OccupyGrids( pEntry );
+ }
+ else
+ {
+ SvxIconChoiceCtrlEntry* pPrev = FindEntryPredecessor( pEntry, rPos );
+ SetEntryPredecessor( pEntry, pPrev );
+ aAutoArrangeIdle.Start();
+ }
+ ShowCursor( true );
+}
+
+void SvxIconChoiceCtrl_Impl::SetNoSelection()
+{
+ // block recursive calls via SelectEntry
+ if( !(nFlags & IconChoiceFlags::ClearingSelection ))
+ {
+ nFlags |= IconChoiceFlags::ClearingSelection;
+ DeselectAllBut( nullptr );
+ nFlags &= ~IconChoiceFlags::ClearingSelection;
+ }
+}
+
+SvxIconChoiceCtrlEntry* SvxIconChoiceCtrl_Impl::GetEntry( const Point& rDocPos, bool bHit )
+{
+ CheckBoundingRects();
+ // search through z-order list from the end
+ size_t nCount = maZOrderList.size();
+ while( nCount )
+ {
+ nCount--;
+ SvxIconChoiceCtrlEntry* pEntry = maZOrderList[ nCount ];
+ if( pEntry->aRect.Contains( rDocPos ) )
+ {
+ if( bHit )
+ {
+ tools::Rectangle aRect = CalcBmpRect( pEntry );
+ aRect.AdjustTop( -3 );
+ aRect.AdjustBottom(3 );
+ aRect.AdjustLeft( -3 );
+ aRect.AdjustRight(3 );
+ if( aRect.Contains( rDocPos ) )
+ return pEntry;
+ aRect = CalcTextRect( pEntry );
+ if( aRect.Contains( rDocPos ) )
+ return pEntry;
+ }
+ else
+ return pEntry;
+ }
+ }
+ return nullptr;
+}
+
+void SvxIconChoiceCtrl_Impl::MakeEntryVisible( SvxIconChoiceCtrlEntry* pEntry, bool bBound )
+{
+ if ( bBound )
+ {
+ const tools::Rectangle& rRect = GetEntryBoundRect( pEntry );
+ MakeVisible( rRect );
+ }
+ else
+ {
+ tools::Rectangle aRect = CalcBmpRect( pEntry );
+ aRect.Union( CalcTextRect( pEntry ) );
+ aRect.AdjustTop(TBOFFS_BOUND );
+ aRect.AdjustBottom(TBOFFS_BOUND );
+ aRect.AdjustLeft(LROFFS_BOUND );
+ aRect.AdjustRight(LROFFS_BOUND );
+ MakeVisible( aRect );
+ }
+}
+
+const tools::Rectangle& SvxIconChoiceCtrl_Impl::GetEntryBoundRect( SvxIconChoiceCtrlEntry* pEntry )
+{
+ if( !IsBoundingRectValid( pEntry->aRect ))
+ FindBoundingRect( pEntry );
+ return pEntry->aRect;
+}
+
+tools::Rectangle SvxIconChoiceCtrl_Impl::CalcBmpRect( SvxIconChoiceCtrlEntry* pEntry, const Point* pPos )
+{
+ tools::Rectangle aBound = GetEntryBoundRect( pEntry );
+ if( pPos )
+ aBound.SetPos( *pPos );
+ Point aPos( aBound.TopLeft() );
+
+ switch( nWinBits & VIEWMODE_MASK )
+ {
+ case WB_ICON:
+ {
+ aPos.AdjustX(( aBound.GetWidth() - aImageSize.Width() ) / 2 );
+ return tools::Rectangle( aPos, aImageSize );
+ }
+
+ case WB_SMALLICON:
+ case WB_DETAILS:
+ aPos.AdjustY(( aBound.GetHeight() - aImageSize.Height() ) / 2 );
+ //TODO: determine horizontal distance to bounding rectangle
+ return tools::Rectangle( aPos, aImageSize );
+
+ default:
+ OSL_FAIL("IconView: Viewmode not set");
+ return aBound;
+ }
+}
+
+tools::Rectangle SvxIconChoiceCtrl_Impl::CalcTextRect( SvxIconChoiceCtrlEntry* pEntry,
+ const Point* pEntryPos, const OUString* pStr )
+{
+ OUString aEntryText;
+ if( !pStr )
+ aEntryText = SvtIconChoiceCtrl::GetEntryText( pEntry );
+ else
+ aEntryText = *pStr;
+
+ const tools::Rectangle aMaxTextRect( CalcMaxTextRect( pEntry ) );
+ tools::Rectangle aBound( GetEntryBoundRect( pEntry ) );
+ if( pEntryPos )
+ aBound.SetPos( *pEntryPos );
+
+ tools::Rectangle aTextRect = pView->GetTextRect( aMaxTextRect, aEntryText, nCurTextDrawFlags );
+
+ Size aTextSize( aTextRect.GetSize() );
+
+ Point aPos( aBound.TopLeft() );
+ tools::Long nBoundWidth = aBound.GetWidth();
+ tools::Long nBoundHeight = aBound.GetHeight();
+
+ switch( nWinBits & VIEWMODE_MASK )
+ {
+ case WB_ICON:
+ aPos.AdjustY(aImageSize.Height() );
+ aPos.AdjustY(VER_DIST_BMP_STRING );
+ aPos.AdjustX((nBoundWidth - aTextSize.Width()) / 2 );
+ break;
+
+ case WB_SMALLICON:
+ case WB_DETAILS:
+ aPos.AdjustX(aImageSize.Width() );
+ aPos.AdjustX(HOR_DIST_BMP_STRING );
+ aPos.AdjustY((nBoundHeight - aTextSize.Height()) / 2 );
+ break;
+ }
+ return tools::Rectangle( aPos, aTextSize );
+}
+
+
+tools::Long SvxIconChoiceCtrl_Impl::CalcBoundingWidth() const
+{
+ tools::Long nStringWidth = GetItemSize( IcnViewFieldType::Text ).Width();
+ tools::Long nWidth = 0;
+
+ switch( nWinBits & VIEWMODE_MASK )
+ {
+ case WB_ICON:
+ nWidth = std::max( nStringWidth, aImageSize.Width() );
+ break;
+
+ case WB_SMALLICON:
+ case WB_DETAILS:
+ nWidth = aImageSize.Width();
+ nWidth += HOR_DIST_BMP_STRING;
+ nWidth += nStringWidth;
+ break;
+ }
+ return nWidth;
+}
+
+tools::Long SvxIconChoiceCtrl_Impl::CalcBoundingHeight() const
+{
+ tools::Long nStringHeight = GetItemSize(IcnViewFieldType::Text).Height();
+ tools::Long nHeight = 0;
+
+ switch( nWinBits & VIEWMODE_MASK )
+ {
+ case WB_ICON:
+ nHeight = aImageSize.Height();
+ nHeight += VER_DIST_BMP_STRING;
+ nHeight += nStringHeight;
+ break;
+
+ case WB_SMALLICON:
+ case WB_DETAILS:
+ nHeight = std::max( aImageSize.Height(), nStringHeight );
+ break;
+ }
+ if( nHeight > nMaxBoundHeight )
+ {
+ const_cast<SvxIconChoiceCtrl_Impl*>(this)->nMaxBoundHeight = nHeight;
+ const_cast<SvxIconChoiceCtrl_Impl*>(this)->aHorSBar->SetLineSize( GetScrollBarLineSize() );
+ const_cast<SvxIconChoiceCtrl_Impl*>(this)->aVerSBar->SetLineSize( GetScrollBarLineSize() );
+ }
+ return nHeight;
+}
+
+Size SvxIconChoiceCtrl_Impl::CalcBoundingSize() const
+{
+ return Size( CalcBoundingWidth(), CalcBoundingHeight() );
+}
+
+void SvxIconChoiceCtrl_Impl::RecalcAllBoundingRectsSmart()
+{
+ nMaxBoundHeight = 0;
+ maZOrderList.clear();
+ size_t nCur;
+ SvxIconChoiceCtrlEntry* pEntry;
+ const size_t nCount = maEntries.size();
+
+ if( !IsAutoArrange() || !pHead )
+ {
+ for( nCur = 0; nCur < nCount; nCur++ )
+ {
+ pEntry = maEntries[ nCur ].get();
+ if( IsBoundingRectValid( pEntry->aRect ))
+ {
+ Size aBoundSize( pEntry->aRect.GetSize() );
+ if( aBoundSize.Height() > nMaxBoundHeight )
+ nMaxBoundHeight = aBoundSize.Height();
+ }
+ else
+ FindBoundingRect( pEntry );
+ maZOrderList.push_back( pEntry );
+ }
+ }
+ else
+ {
+ nCur = 0;
+ pEntry = pHead;
+ while( nCur != nCount )
+ {
+ DBG_ASSERT(pEntry->pflink&&pEntry->pblink,"SvxIconChoiceCtrl_Impl::RecalcAllBoundingRect > Bad link(s)");
+ if( IsBoundingRectValid( pEntry->aRect ))
+ {
+ Size aBoundSize( pEntry->aRect.GetSize() );
+ if( aBoundSize.Height() > nMaxBoundHeight )
+ nMaxBoundHeight = aBoundSize.Height();
+ }
+ else
+ FindBoundingRect( pEntry );
+ maZOrderList.push_back( pEntry );
+ pEntry = pEntry->pflink;
+ nCur++;
+ }
+ }
+ AdjustScrollBars();
+}
+
+void SvxIconChoiceCtrl_Impl::FindBoundingRect( SvxIconChoiceCtrlEntry* pEntry )
+{
+ DBG_ASSERT(!pEntry->IsPosLocked(),"Locked entry pos in FindBoundingRect");
+ if( pEntry->IsPosLocked() && IsBoundingRectValid( pEntry->aRect) )
+ {
+ AdjustVirtSize( pEntry->aRect );
+ return;
+ }
+ Size aSize( CalcBoundingSize() );
+ Point aPos(pGridMap->GetGridRect(pGridMap->GetUnoccupiedGrid()).TopLeft());
+ SetBoundingRect_Impl( pEntry, aPos, aSize );
+}
+
+void SvxIconChoiceCtrl_Impl::SetBoundingRect_Impl( SvxIconChoiceCtrlEntry* pEntry, const Point& rPos,
+ const Size& /*rBoundingSize*/ )
+{
+ tools::Rectangle aGridRect( rPos, Size(nGridDX, nGridDY) );
+ pEntry->aGridRect = aGridRect;
+ Center( pEntry );
+ AdjustVirtSize( pEntry->aRect );
+ pGridMap->OccupyGrids( pEntry );
+}
+
+
+void SvxIconChoiceCtrl_Impl::SetCursor( SvxIconChoiceCtrlEntry* pEntry )
+{
+ if( pEntry == pCursor )
+ {
+ if( pCursor && eSelectionMode == SelectionMode::Single &&
+ !pCursor->IsSelected() )
+ SelectEntry( pCursor, true );
+ return;
+ }
+ ShowCursor( false );
+ SvxIconChoiceCtrlEntry* pOldCursor = pCursor;
+ pCursor = pEntry;
+ if( pOldCursor )
+ {
+ pOldCursor->ClearFlags( SvxIconViewFlags::FOCUSED );
+ if( eSelectionMode == SelectionMode::Single )
+ SelectEntry( pOldCursor, false ); // deselect old cursor
+ }
+ if( pCursor )
+ {
+ ToTop( pCursor );
+ pCursor->SetFlags( SvxIconViewFlags::FOCUSED );
+ if( eSelectionMode == SelectionMode::Single )
+ SelectEntry( pCursor, true );
+ ShowCursor( true );
+ }
+}
+
+
+void SvxIconChoiceCtrl_Impl::ShowCursor( bool bShow )
+{
+ if( !pCursor || !bShow || !pView->HasFocus() )
+ {
+ pView->HideFocus();
+ return;
+ }
+ tools::Rectangle aRect ( CalcFocusRect( pCursor ) );
+ /*pView->*/ShowFocus( aRect );
+}
+
+
+void SvxIconChoiceCtrl_Impl::HideDDIcon()
+{
+ pView->PaintImmediately();
+}
+
+bool SvxIconChoiceCtrl_Impl::HandleScrollCommand( const CommandEvent& rCmd )
+{
+ tools::Rectangle aDocRect( Point(), aVirtOutputSize );
+ tools::Rectangle aVisRect( GetOutputRect() );
+ if( aVisRect.Contains( aDocRect ))
+ return false;
+ Size aDocSize( aDocRect.GetSize() );
+ Size aVisSize( aVisRect.GetSize() );
+ bool bHor = aDocSize.Width() > aVisSize.Width();
+ bool bVer = aDocSize.Height() > aVisSize.Height();
+
+ tools::Long nScrollDX = 0, nScrollDY = 0;
+
+ switch( rCmd.GetCommand() )
+ {
+ case CommandEventId::StartAutoScroll:
+ {
+ pView->EndTracking();
+ StartAutoScrollFlags nScrollFlags = StartAutoScrollFlags::NONE;
+ if( bHor )
+ nScrollFlags |= StartAutoScrollFlags::Horz;
+ if( bVer )
+ nScrollFlags |= StartAutoScrollFlags::Vert;
+ if( nScrollFlags != StartAutoScrollFlags::NONE )
+ {
+ pView->StartAutoScroll( nScrollFlags );
+ return true;
+ }
+ }
+ break;
+
+ case CommandEventId::Wheel:
+ {
+ const CommandWheelData* pData = rCmd.GetWheelData();
+ if( pData && (CommandWheelMode::SCROLL == pData->GetMode()) && !pData->IsHorz() )
+ {
+ double nScrollLines = pData->GetScrollLines();
+ if( nScrollLines == COMMAND_WHEEL_PAGESCROLL )
+ {
+ nScrollDY = GetScrollBarPageSize( aVisSize.Width() );
+ if( pData->GetDelta() < 0 )
+ nScrollDY *= -1;
+ }
+ else
+ {
+ nScrollDY = pData->GetNotchDelta() * static_cast<tools::Long>(nScrollLines);
+ nScrollDY *= GetScrollBarLineSize();
+ }
+ }
+ }
+ break;
+
+ case CommandEventId::AutoScroll:
+ {
+ const CommandScrollData* pData = rCmd.GetAutoScrollData();
+ if( pData )
+ {
+ nScrollDX = pData->GetDeltaX() * GetScrollBarLineSize();
+ nScrollDY = pData->GetDeltaY() * GetScrollBarLineSize();
+ }
+ }
+ break;
+
+ default: break;
+ }
+
+ if( nScrollDX || nScrollDY )
+ {
+ aVisRect.AdjustTop( -nScrollDY );
+ aVisRect.AdjustBottom( -nScrollDY );
+ aVisRect.AdjustLeft( -nScrollDX );
+ aVisRect.AdjustRight( -nScrollDX );
+ MakeVisible( aVisRect );
+ return true;
+ }
+ return false;
+}
+
+
+void SvxIconChoiceCtrl_Impl::Command( const CommandEvent& rCEvt )
+{
+ // scroll mouse event?
+ if( (rCEvt.GetCommand() == CommandEventId::Wheel) ||
+ (rCEvt.GetCommand() == CommandEventId::StartAutoScroll) ||
+ (rCEvt.GetCommand() == CommandEventId::AutoScroll) )
+ {
+ if( HandleScrollCommand( rCEvt ) )
+ return;
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::ToTop( SvxIconChoiceCtrlEntry* pEntry )
+{
+ if( maZOrderList.empty() || pEntry == maZOrderList.back())
+ return;
+
+ auto it = std::find(maZOrderList.begin(), maZOrderList.end(), pEntry);
+ if (it != maZOrderList.end())
+ {
+ maZOrderList.erase( it );
+ maZOrderList.push_back( pEntry );
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::ClipAtVirtOutRect( tools::Rectangle& rRect ) const
+{
+ if( rRect.Bottom() >= aVirtOutputSize.Height() )
+ rRect.SetBottom( aVirtOutputSize.Height() - 1 );
+ if( rRect.Right() >= aVirtOutputSize.Width() )
+ rRect.SetRight( aVirtOutputSize.Width() - 1 );
+ if( rRect.Top() < 0 )
+ rRect.SetTop( 0 );
+ if( rRect.Left() < 0 )
+ rRect.SetLeft( 0 );
+}
+
+// rRect: area of the document (in document coordinates) that we want to make
+// visible
+// bScrBar == true: rectangle was calculated because of a scrollbar event
+
+void SvxIconChoiceCtrl_Impl::MakeVisible( const tools::Rectangle& rRect, bool bScrBar )
+{
+ tools::Rectangle aVirtRect( rRect );
+ ClipAtVirtOutRect( aVirtRect );
+ Point aOrigin( pView->GetMapMode().GetOrigin() );
+ // convert to document coordinate
+ aOrigin *= -1;
+ tools::Rectangle aOutputArea( GetOutputRect() );
+ if( aOutputArea.Contains( aVirtRect ) )
+ return; // is already visible
+
+ tools::Long nDy;
+ if( aVirtRect.Top() < aOutputArea.Top() )
+ {
+ // scroll up (nDy < 0)
+ nDy = aVirtRect.Top() - aOutputArea.Top();
+ }
+ else if( aVirtRect.Bottom() > aOutputArea.Bottom() )
+ {
+ // scroll down (nDy > 0)
+ nDy = aVirtRect.Bottom() - aOutputArea.Bottom();
+ }
+ else
+ nDy = 0;
+
+ tools::Long nDx;
+ if( aVirtRect.Left() < aOutputArea.Left() )
+ {
+ // scroll to the left (nDx < 0)
+ nDx = aVirtRect.Left() - aOutputArea.Left();
+ }
+ else if( aVirtRect.Right() > aOutputArea.Right() )
+ {
+ // scroll to the right (nDx > 0)
+ nDx = aVirtRect.Right() - aOutputArea.Right();
+ }
+ else
+ nDx = 0;
+
+ aOrigin.AdjustX(nDx );
+ aOrigin.AdjustY(nDy );
+ aOutputArea.SetPos( aOrigin );
+ if( GetUpdateMode() )
+ {
+ HideDDIcon();
+ pView->PaintImmediately();
+ ShowCursor( false );
+ }
+
+ // invert origin for SV (so we can scroll/paint using document coordinates)
+ aOrigin *= -1;
+ SetOrigin( aOrigin );
+
+ bool bScrollable = pView->GetBackground().IsScrollable();
+
+ if( bScrollable && GetUpdateMode() )
+ {
+ // scroll in reverse direction!
+ pView->Control::Scroll( -nDx, -nDy, aOutputArea,
+ ScrollFlags::NoChildren | ScrollFlags::UseClipRegion | ScrollFlags::Clip );
+ }
+ else
+ pView->Invalidate(InvalidateFlags::NoChildren);
+
+ if( aHorSBar->IsVisible() || aVerSBar->IsVisible() )
+ {
+ if( !bScrBar )
+ {
+ aOrigin *= -1;
+ // correct thumbs
+ if(aHorSBar->IsVisible() && aHorSBar->GetThumbPos() != aOrigin.X())
+ aHorSBar->SetThumbPos( aOrigin.X() );
+ if(aVerSBar->IsVisible() && aVerSBar->GetThumbPos() != aOrigin.Y())
+ aVerSBar->SetThumbPos( aOrigin.Y() );
+ }
+ }
+
+ if( GetUpdateMode() )
+ ShowCursor( true );
+
+ // check if we still need scrollbars
+ CheckScrollBars();
+ if( bScrollable && GetUpdateMode() )
+ pView->PaintImmediately();
+
+ // If the requested area can not be made completely visible, the
+ // Vis-Rect-Changed handler is called in any case. This case may occur e.g.
+ // if only few pixels of the lower border are invisible, but a scrollbar has
+ // a larger line size.
+ VisRectChanged();
+}
+
+sal_Int32 SvxIconChoiceCtrl_Impl::GetSelectionCount() const
+{
+ if( (nWinBits & WB_HIGHLIGHTFRAME) && pCurHighlightFrame )
+ return 1;
+ return nSelectionCount;
+}
+
+void SvxIconChoiceCtrl_Impl::ToggleSelection( SvxIconChoiceCtrlEntry* pEntry )
+{
+ bool bSel;
+ bSel = !pEntry->IsSelected();
+ SelectEntry( pEntry, bSel, true );
+}
+
+void SvxIconChoiceCtrl_Impl::DeselectAllBut( SvxIconChoiceCtrlEntry const * pThisEntryNot )
+{
+ ClearSelectedRectList();
+
+ // TODO: work through z-order list, if necessary!
+
+ size_t nCount = maEntries.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maEntries[ nCur ].get();
+ if( pEntry != pThisEntryNot && pEntry->IsSelected() )
+ SelectEntry( pEntry, false, true );
+ }
+ pAnchor = nullptr;
+ nFlags &= ~IconChoiceFlags::AddMode;
+}
+
+Size SvxIconChoiceCtrl_Impl::GetMinGrid() const
+{
+ Size aMinSize( aImageSize );
+ aMinSize.AdjustWidth(2 * LROFFS_BOUND );
+ aMinSize.AdjustHeight(TBOFFS_BOUND ); // single offset is enough (FileDlg)
+ Size aTextSize( pView->GetTextWidth( "XXX" ), pView->GetTextHeight() );
+ if( nWinBits & WB_ICON )
+ {
+ aMinSize.AdjustHeight(VER_DIST_BMP_STRING );
+ aMinSize.AdjustHeight(aTextSize.Height() );
+ }
+ else
+ {
+ aMinSize.AdjustWidth(HOR_DIST_BMP_STRING );
+ aMinSize.AdjustWidth(aTextSize.Width() );
+ }
+ return aMinSize;
+}
+
+void SvxIconChoiceCtrl_Impl::SetGrid( const Size& rSize )
+{
+ Size aSize( rSize );
+ Size aMinSize( GetMinGrid() );
+ if( aSize.Width() < aMinSize.Width() )
+ aSize.setWidth( aMinSize.Width() );
+ if( aSize.Height() < aMinSize.Height() )
+ aSize.setHeight( aMinSize.Height() );
+
+ nGridDX = aSize.Width();
+ // HACK: Detail mode is not yet fully implemented, this workaround makes it
+ // fly with a single column
+ if( nWinBits & WB_DETAILS )
+ {
+ const SvxIconChoiceCtrlColumnInfo* pCol = GetColumn( 0 );
+ if( pCol )
+ const_cast<SvxIconChoiceCtrlColumnInfo*>(pCol)->SetWidth( nGridDX );
+ }
+ nGridDY = aSize.Height();
+ SetDefaultTextSize();
+}
+
+// Calculates the maximum size that the text rectangle may use within its
+// bounding rectangle. In WB_ICON mode with SvxIconChoiceCtrlTextMode::Full, Bottom is set to
+// LONG_MAX.
+
+tools::Rectangle SvxIconChoiceCtrl_Impl::CalcMaxTextRect( const SvxIconChoiceCtrlEntry* pEntry ) const
+{
+ tools::Rectangle aBoundRect;
+ // avoid infinite recursion: don't calculate the bounding rectangle here
+ if( IsBoundingRectValid( pEntry->aRect ) )
+ aBoundRect = pEntry->aRect;
+ else
+ aBoundRect = pEntry->aGridRect;
+
+ tools::Rectangle aBmpRect( const_cast<SvxIconChoiceCtrl_Impl*>(this)->CalcBmpRect(
+ const_cast<SvxIconChoiceCtrlEntry*>(pEntry) ) );
+ if( nWinBits & WB_ICON )
+ {
+ aBoundRect.SetTop( aBmpRect.Bottom() );
+ aBoundRect.AdjustTop(VER_DIST_BMP_STRING );
+ if( aBoundRect.Top() > aBoundRect.Bottom())
+ aBoundRect.SetTop( aBoundRect.Bottom() );
+ aBoundRect.AdjustLeft(LROFFS_BOUND );
+ aBoundRect.AdjustLeft( 1 );
+ aBoundRect.AdjustRight( -(LROFFS_BOUND) );
+ aBoundRect.AdjustRight( -1 );
+ if( aBoundRect.Left() > aBoundRect.Right())
+ aBoundRect.SetLeft( aBoundRect.Right() );
+ if( pEntry->GetTextMode() == SvxIconChoiceCtrlTextMode::Full )
+ aBoundRect.SetBottom( LONG_MAX );
+ }
+ else
+ {
+ aBoundRect.SetLeft( aBmpRect.Right() );
+ aBoundRect.AdjustLeft(HOR_DIST_BMP_STRING );
+ aBoundRect.AdjustRight( -(LROFFS_BOUND) );
+ if( aBoundRect.Left() > aBoundRect.Right() )
+ aBoundRect.SetLeft( aBoundRect.Right() );
+ tools::Long nHeight = aBoundRect.GetSize().Height();
+ nHeight = nHeight - aDefaultTextSize.Height();
+ nHeight /= 2;
+ aBoundRect.AdjustTop(nHeight );
+ aBoundRect.AdjustBottom( -nHeight );
+ }
+ return aBoundRect;
+}
+
+void SvxIconChoiceCtrl_Impl::SetDefaultTextSize()
+{
+ tools::Long nDY = nGridDY;
+ nDY -= aImageSize.Height();
+ nDY -= VER_DIST_BMP_STRING;
+ nDY -= 2 * TBOFFS_BOUND;
+ if (nDY <= 0)
+ nDY = 2;
+
+ tools::Long nDX = nGridDX;
+ nDX -= 2 * LROFFS_BOUND;
+ nDX -= 2;
+ if (nDX <= 0)
+ nDX = 2;
+
+ tools::Long nHeight = pView->GetTextHeight();
+ if (nDY < nHeight)
+ nDY = nHeight;
+ if(pView->GetDPIScaleFactor() > 1)
+ {
+ nDY*=2;
+ }
+ aDefaultTextSize = Size(nDX, nDY);
+}
+
+
+void SvxIconChoiceCtrl_Impl::Center( SvxIconChoiceCtrlEntry* pEntry ) const
+{
+ pEntry->aRect = pEntry->aGridRect;
+ Size aSize( CalcBoundingSize() );
+ if( nWinBits & WB_ICON )
+ {
+ // center horizontally
+ tools::Long nBorder = pEntry->aGridRect.GetWidth() - aSize.Width();
+ pEntry->aRect.AdjustLeft(nBorder / 2 );
+ pEntry->aRect.AdjustRight( -(nBorder / 2) );
+ }
+ // center vertically
+ pEntry->aRect.SetBottom( pEntry->aRect.Top() + aSize.Height() );
+}
+
+
+// The deltas are the offsets by which the view is moved on the document.
+// left, up: offsets < 0
+// right, down: offsets > 0
+void SvxIconChoiceCtrl_Impl::Scroll( tools::Long nDeltaX, tools::Long nDeltaY )
+{
+ const MapMode& rMapMode = pView->GetMapMode();
+ Point aOrigin( rMapMode.GetOrigin() );
+ // convert to document coordinate
+ aOrigin *= -1;
+ aOrigin.AdjustY(nDeltaY );
+ aOrigin.AdjustX(nDeltaX );
+ tools::Rectangle aRect( aOrigin, aOutputSize );
+ MakeVisible( aRect, true/*bScrollBar*/ );
+}
+
+
+const Size& SvxIconChoiceCtrl_Impl::GetItemSize( IcnViewFieldType eItem ) const
+{
+ if (eItem == IcnViewFieldType::Text)
+ return aDefaultTextSize;
+ return aImageSize; // IcnViewFieldType::Image
+}
+
+tools::Rectangle SvxIconChoiceCtrl_Impl::CalcFocusRect( SvxIconChoiceCtrlEntry* pEntry )
+{
+ tools::Rectangle aTextRect( CalcTextRect( pEntry ) );
+ tools::Rectangle aBoundRect( GetEntryBoundRect( pEntry ) );
+ return tools::Rectangle(
+ aBoundRect.Left(), aBoundRect.Top() - 1, aBoundRect.Right() - 1,
+ aTextRect.Bottom() + 1);
+}
+
+// the hot spot is the inner 50% of the rectangle
+static tools::Rectangle GetHotSpot( const tools::Rectangle& rRect )
+{
+ tools::Rectangle aResult( rRect );
+ aResult.Normalize();
+ Size aSize( rRect.GetSize() );
+ tools::Long nDelta = aSize.Width() / 4;
+ aResult.AdjustLeft(nDelta );
+ aResult.AdjustRight( -nDelta );
+ nDelta = aSize.Height() / 4;
+ aResult.AdjustTop(nDelta );
+ aResult.AdjustBottom( -nDelta );
+ return aResult;
+}
+
+void SvxIconChoiceCtrl_Impl::SelectRect( SvxIconChoiceCtrlEntry* pEntry1, SvxIconChoiceCtrlEntry* pEntry2,
+ bool bAdd, std::vector<tools::Rectangle>* pOtherRects )
+{
+ DBG_ASSERT(pEntry1 && pEntry2,"SelectEntry: Invalid Entry-Ptr");
+ tools::Rectangle aRect( GetEntryBoundRect( pEntry1 ) );
+ aRect.Union( GetEntryBoundRect( pEntry2 ) );
+ SelectRect( aRect, bAdd, pOtherRects );
+}
+
+void SvxIconChoiceCtrl_Impl::SelectRect( const tools::Rectangle& rRect, bool bAdd,
+ std::vector<tools::Rectangle>* pOtherRects )
+{
+ aCurSelectionRect = rRect;
+ if( maZOrderList.empty() )
+ return;
+
+ // set flag, so ToTop won't be called in Select
+ bool bAlreadySelectingRect(nFlags & IconChoiceFlags::SelectingRect);
+ nFlags |= IconChoiceFlags::SelectingRect;
+
+ CheckBoundingRects();
+ pView->PaintImmediately();
+ const size_t nCount = maZOrderList.size();
+
+ tools::Rectangle aRect( rRect );
+ aRect.Normalize();
+ bool bCalcOverlap = (bAdd && pOtherRects && !pOtherRects->empty());
+
+ bool bResetClipRegion = false;
+ if( !pView->GetOutDev()->IsClipRegion() )
+ {
+ bResetClipRegion = true;
+ pView->GetOutDev()->SetClipRegion(vcl::Region(GetOutputRect()));
+ }
+
+ for( size_t nPos = 0; nPos < nCount; nPos++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maZOrderList[ nPos ];
+
+ if( !IsBoundingRectValid( pEntry->aRect ))
+ FindBoundingRect( pEntry );
+ tools::Rectangle aBoundRect( GetHotSpot( pEntry->aRect ) );
+ bool bSelected = pEntry->IsSelected();
+
+ bool bOverlaps;
+ if( bCalcOverlap )
+ bOverlaps = IsOver( pOtherRects, aBoundRect );
+ else
+ bOverlaps = false;
+ bool bOver = aRect.Overlaps( aBoundRect );
+
+ if( bOver && !bOverlaps )
+ {
+ // is inside the new selection rectangle and outside of any old one
+ // => select
+ if( !bSelected )
+ SelectEntry( pEntry, true, true );
+ }
+ else if( !bAdd )
+ {
+ // is outside of the selection rectangle
+ // => deselect
+ if( bSelected )
+ SelectEntry( pEntry, false, true );
+ }
+ else if (bOverlaps)
+ {
+ // The entry is inside an old (=>span multiple rectangles with Ctrl)
+ // selection rectangle.
+
+ // There is still a bug here! The selection status of an entry in a
+ // previous rectangle has to be restored, if it was touched by the
+ // current selection rectangle but is not inside it any more.
+ // For simplicity's sake, let's assume that all entries in the old
+ // rectangles were correctly selected. It is wrong to just deselect
+ // the intersection.
+ // Possible solution: remember a snapshot of the selection before
+ // spanning the rectangle.
+ if( aBoundRect.Overlaps( rRect))
+ {
+ // deselect intersection between old rectangles and current rectangle
+ if( bSelected )
+ SelectEntry( pEntry, false, true );
+ }
+ else
+ {
+ // select entry of an old rectangle
+ if( !bSelected )
+ SelectEntry( pEntry, true, true );
+ }
+ }
+ else if( !bOver && bSelected )
+ {
+ // this entry is completely outside the rectangle => deselect it
+ SelectEntry( pEntry, false, true );
+ }
+ }
+
+ if( !bAlreadySelectingRect )
+ nFlags &= ~IconChoiceFlags::SelectingRect;
+
+ pView->PaintImmediately();
+ if( bResetClipRegion )
+ pView->GetOutDev()->SetClipRegion();
+}
+
+void SvxIconChoiceCtrl_Impl::SelectRange(
+ SvxIconChoiceCtrlEntry const * pStart,
+ SvxIconChoiceCtrlEntry const * pEnd,
+ bool bAdd )
+{
+ sal_uLong nFront = GetEntryListPos( pStart );
+ sal_uLong nBack = GetEntryListPos( pEnd );
+ sal_uLong nFirst = std::min( nFront, nBack );
+ sal_uLong nLast = std::max( nFront, nBack );
+ sal_uLong i;
+ SvxIconChoiceCtrlEntry* pEntry;
+
+ if ( ! bAdd )
+ {
+ // deselect everything before the first entry if not in
+ // adding mode
+ for ( i=0; i<nFirst; i++ )
+ {
+ pEntry = GetEntry( i );
+ if( pEntry->IsSelected() )
+ SelectEntry( pEntry, false, true );
+ }
+ }
+
+ // select everything between nFirst and nLast
+ for ( i=nFirst; i<=nLast; i++ )
+ {
+ pEntry = GetEntry( i );
+ if( ! pEntry->IsSelected() )
+ SelectEntry( pEntry, true, true );
+ }
+
+ if ( ! bAdd )
+ {
+ // deselect everything behind the last entry if not in
+ // adding mode
+ sal_uLong nEnd = GetEntryCount();
+ for ( ; i<nEnd; i++ )
+ {
+ pEntry = GetEntry( i );
+ if( pEntry->IsSelected() )
+ SelectEntry( pEntry, false, true );
+ }
+ }
+}
+
+bool SvxIconChoiceCtrl_Impl::IsOver( std::vector<tools::Rectangle>* pRectList, const tools::Rectangle& rBoundRect )
+{
+ const sal_uInt16 nCount = pRectList->size();
+ for( sal_uInt16 nCur = 0; nCur < nCount; nCur++ )
+ {
+ tools::Rectangle& rRect = (*pRectList)[ nCur ];
+ if( rBoundRect.Overlaps( rRect ))
+ return true;
+ }
+ return false;
+}
+
+void SvxIconChoiceCtrl_Impl::AddSelectedRect( SvxIconChoiceCtrlEntry* pEntry1,
+ SvxIconChoiceCtrlEntry* pEntry2 )
+{
+ DBG_ASSERT(pEntry1 && pEntry2,"SelectEntry: Invalid Entry-Ptr");
+ tools::Rectangle aRect( GetEntryBoundRect( pEntry1 ) );
+ aRect.Union( GetEntryBoundRect( pEntry2 ) );
+ AddSelectedRect( aRect );
+}
+
+void SvxIconChoiceCtrl_Impl::AddSelectedRect( const tools::Rectangle& rRect )
+{
+ tools::Rectangle newRect = rRect;
+ newRect.Normalize();
+ aSelectedRectList.push_back( newRect );
+}
+
+void SvxIconChoiceCtrl_Impl::ClearSelectedRectList()
+{
+ aSelectedRectList.clear();
+}
+
+IMPL_LINK_NOARG(SvxIconChoiceCtrl_Impl, AutoArrangeHdl, Timer *, void)
+{
+ aAutoArrangeIdle.Stop();
+ Arrange( IsAutoArrange(), 0, 0 );
+}
+
+IMPL_LINK_NOARG(SvxIconChoiceCtrl_Impl, VisRectChangedHdl, Timer *, void)
+{
+ aVisRectChangedIdle.Stop();
+}
+
+IMPL_LINK_NOARG(SvxIconChoiceCtrl_Impl, DocRectChangedHdl, Timer *, void)
+{
+ aDocRectChangedIdle.Stop();
+}
+
+#ifdef DBG_UTIL
+void SvxIconChoiceCtrl_Impl::SetEntryTextMode( SvxIconChoiceCtrlTextMode eMode, SvxIconChoiceCtrlEntry* pEntry )
+{
+ if( !pEntry )
+ {
+ if( eTextMode != eMode )
+ {
+ eTextMode = eMode;
+ Arrange( true, 0, 0 );
+ }
+ }
+ else
+ {
+ if( pEntry->eTextMode != eMode )
+ {
+ pEntry->eTextMode = eMode;
+ InvalidateEntry( pEntry );
+ pView->Invalidate( GetEntryBoundRect( pEntry ) );
+ AdjustVirtSize( pEntry->aRect );
+ }
+ }
+}
+#endif
+
+// Draw my own focusrect, because the focusrect of the outputdevice has got the inverted color
+// of the background. But what will we see, if the backgroundcolor is gray ? - We will see
+// a gray focusrect on a gray background !!!
+
+void SvxIconChoiceCtrl_Impl::ShowFocus ( tools::Rectangle const & rRect )
+{
+ Color aBkgColor(pView->GetBackground().GetColor());
+ Color aPenColor;
+ sal_uInt16 nColor = ( aBkgColor.GetRed() + aBkgColor.GetGreen() + aBkgColor.GetBlue() ) / 3;
+ if (nColor > 128)
+ aPenColor = COL_BLACK;
+ else
+ aPenColor = COL_WHITE;
+
+ aFocus.aPenColor = aPenColor;
+ aFocus.aRect = rRect;
+}
+
+void SvxIconChoiceCtrl_Impl::DrawFocusRect(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetLineColor(aFocus.aPenColor);
+ rRenderContext.SetFillColor();
+ tools::Polygon aPolygon (aFocus.aRect);
+
+ LineInfo aLineInfo(LineStyle::Dash);
+
+ aLineInfo.SetDashLen(1);
+ aLineInfo.SetDotLen(1);
+ aLineInfo.SetDistance(1);
+ aLineInfo.SetDotCount(1);
+
+ rRenderContext.DrawPolyLine(aPolygon, aLineInfo);
+}
+
+bool SvxIconChoiceCtrl_Impl::IsMnemonicChar( sal_Unicode cChar, sal_uLong& rPos ) const
+{
+ bool bRet = false;
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper();
+ size_t nEntryCount = GetEntryCount();
+ for ( size_t i = 0; i < nEntryCount; ++i )
+ {
+ if ( rI18nHelper.MatchMnemonic( GetEntry( i )->GetText(), cChar ) )
+ {
+ bRet = true;
+ rPos = i;
+ break;
+ }
+ }
+
+ return bRet;
+}
+
+
+IMPL_LINK(SvxIconChoiceCtrl_Impl, UserEventHdl, void*, nId, void )
+{
+ if( nId == EVENTID_ADJUST_SCROLLBARS )
+ {
+ nUserEventAdjustScrBars = nullptr;
+ AdjustScrollBars();
+ }
+ else if( nId == EVENTID_SHOW_CURSOR )
+ {
+ ShowCursor( true );
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::CancelUserEvents()
+{
+ if( nUserEventAdjustScrBars )
+ {
+ Application::RemoveUserEvent( nUserEventAdjustScrBars );
+ nUserEventAdjustScrBars = nullptr;
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::InvalidateEntry( SvxIconChoiceCtrlEntry* pEntry )
+{
+ if( pEntry == pCursor )
+ ShowCursor( false );
+ pView->Invalidate( pEntry->aRect );
+ Center( pEntry );
+ pView->Invalidate( pEntry->aRect );
+ if( pEntry == pCursor )
+ ShowCursor( true );
+}
+
+SvxIconChoiceCtrlEntry* SvxIconChoiceCtrl_Impl::GetFirstSelectedEntry() const
+{
+ if( !GetSelectionCount() )
+ return nullptr;
+
+ if( (nWinBits & WB_HIGHLIGHTFRAME) && (eSelectionMode == SelectionMode::NONE) )
+ {
+ return pCurHighlightFrame;
+ }
+
+ size_t nCount = maEntries.size();
+ if( !pHead )
+ {
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maEntries[ nCur ].get();
+ if( pEntry->IsSelected() )
+ {
+ return pEntry;
+ }
+ }
+ }
+ else
+ {
+ SvxIconChoiceCtrlEntry* pEntry = pHead;
+ while( nCount-- )
+ {
+ if( pEntry->IsSelected() )
+ {
+ return pEntry;
+ }
+ pEntry = pEntry->pflink;
+ if( nCount && pEntry == pHead )
+ {
+ OSL_FAIL("SvxIconChoiceCtrl_Impl::GetFirstSelectedEntry > infinite loop!");
+ return nullptr;
+ }
+ }
+ }
+ return nullptr;
+}
+
+void SvxIconChoiceCtrl_Impl::SelectAll()
+{
+ size_t nCount = maEntries.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maEntries[ nCur ].get();
+ SelectEntry( pEntry, true/*bSelect*/, true );
+ }
+ nFlags &= ~IconChoiceFlags::AddMode;
+ pAnchor = nullptr;
+}
+
+
+
+
+sal_Int32 SvxIconChoiceCtrl_Impl::GetEntryListPos( SvxIconChoiceCtrlEntry const * pEntry ) const
+{
+ if( !(nFlags & IconChoiceFlags::EntryListPosValid ))
+ const_cast<SvxIconChoiceCtrl_Impl*>(this)->SetListPositions();
+ return pEntry->nPos;
+}
+
+void SvxIconChoiceCtrl_Impl::InitSettings()
+{
+ const StyleSettings& rStyleSettings = pView->GetSettings().GetStyleSettings();
+
+ // unit (from settings) is Point
+ vcl::Font aFont( rStyleSettings.GetFieldFont() );
+ aFont.SetColor( rStyleSettings.GetWindowTextColor() );
+ pView->SetPointFont( aFont );
+ SetDefaultTextSize();
+
+ pView->SetTextColor( rStyleSettings.GetFieldTextColor() );
+ pView->SetTextFillColor();
+
+ pView->SetBackground( rStyleSettings.GetFieldColor());
+
+ tools::Long nScrBarSize = rStyleSettings.GetScrollBarSize();
+ if( nScrBarSize == nHorSBarHeight && nScrBarSize == nVerSBarWidth )
+ return;
+
+ nHorSBarHeight = nScrBarSize;
+ Size aSize( aHorSBar->GetSizePixel() );
+ aSize.setHeight( nScrBarSize );
+ aHorSBar->Hide();
+ aHorSBar->SetSizePixel( aSize );
+
+ nVerSBarWidth = nScrBarSize;
+ aSize = aVerSBar->GetSizePixel();
+ aSize.setWidth( nScrBarSize );
+ aVerSBar->Hide();
+ aVerSBar->SetSizePixel( aSize );
+
+ Size aOSize( pView->Control::GetOutputSizePixel() );
+ PositionScrollBars( aOSize.Width(), aOSize.Height() );
+ AdjustScrollBars();
+}
+
+void SvxIconChoiceCtrl_Impl::SetPositionMode( SvxIconChoiceCtrlPositionMode eMode )
+{
+ if( eMode == ePositionMode )
+ return;
+
+ SvxIconChoiceCtrlPositionMode eOldMode = ePositionMode;
+ ePositionMode = eMode;
+ size_t nCount = maEntries.size();
+
+ if( eOldMode == SvxIconChoiceCtrlPositionMode::AutoArrange )
+ {
+ // when positioning moved entries "hard", there are problems with
+ // unwanted overlaps, as these entries aren't taken into account in
+ // Arrange.
+ if( maEntries.size() )
+ aAutoArrangeIdle.Start();
+ return;
+ }
+
+ if( ePositionMode == SvxIconChoiceCtrlPositionMode::AutoArrange )
+ {
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = maEntries[ nCur ].get();
+ if( pEntry->GetFlags() & SvxIconViewFlags(SvxIconViewFlags::POS_LOCKED | SvxIconViewFlags::POS_MOVED))
+ SetEntryPos(pEntry, GetEntryBoundRect( pEntry ).TopLeft());
+ }
+
+ if( maEntries.size() )
+ aAutoArrangeIdle.Start();
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::SetEntryPredecessor( SvxIconChoiceCtrlEntry* pEntry,
+ SvxIconChoiceCtrlEntry* pPredecessor )
+{
+ if( !IsAutoArrange() )
+ return;
+
+ if( pEntry == pPredecessor )
+ return;
+
+ sal_uLong nPos1 = GetEntryListPos( pEntry );
+ if( !pHead )
+ {
+ if( pPredecessor )
+ {
+ sal_uLong nPos2 = GetEntryListPos( pPredecessor );
+ if( nPos1 == (nPos2 + 1) )
+ return; // is already the predecessor
+ }
+ else if( !nPos1 )
+ return;
+
+ InitPredecessors();
+ }
+
+ if( !pPredecessor && pHead == pEntry )
+ return; // is already the first one
+
+ bool bSetHead = false;
+ if( !pPredecessor )
+ {
+ bSetHead = true;
+ pPredecessor = pHead->pblink;
+ }
+ if( pEntry == pHead )
+ {
+ pHead = pHead->pflink;
+ bSetHead = false;
+ }
+ if( pEntry != pPredecessor )
+ {
+ pEntry->Unlink();
+ pEntry->SetBacklink( pPredecessor );
+ }
+ if( bSetHead )
+ pHead = pEntry;
+ aAutoArrangeIdle.Start();
+}
+
+SvxIconChoiceCtrlEntry* SvxIconChoiceCtrl_Impl::FindEntryPredecessor( SvxIconChoiceCtrlEntry* pEntry,
+ const Point& rPosTopLeft )
+{
+ Point aPos( rPosTopLeft ); //TopLeft
+ tools::Rectangle aCenterRect( CalcBmpRect( pEntry, &aPos ));
+ Point aNewPos( aCenterRect.Center() );
+ GridId nGrid = GetPredecessorGrid( aNewPos );
+ size_t nCount = maEntries.size();
+ if( nGrid == GRID_NOT_FOUND )
+ return nullptr;
+ if( nGrid >= nCount )
+ nGrid = nCount - 1;
+ if( !pHead )
+ return maEntries[ nGrid ].get();
+
+ SvxIconChoiceCtrlEntry* pCur = pHead; // Grid 0
+ // TODO: go through list from the end if nGrid > nCount/2
+ for( GridId nCur = 0; nCur < nGrid; nCur++ )
+ pCur = pCur->pflink;
+
+ return pCur;
+}
+
+GridId SvxIconChoiceCtrl_Impl::GetPredecessorGrid( const Point& rPos) const
+{
+ Point aPos( rPos );
+ aPos.AdjustX( -(LROFFS_WINBORDER) );
+ aPos.AdjustY( -(TBOFFS_WINBORDER) );
+ tools::Long nMaxCol = aVirtOutputSize.Width() / nGridDX;
+ if( nMaxCol )
+ nMaxCol--;
+ tools::Long nGridX = aPos.X() / nGridDX;
+ if( nGridX > nMaxCol )
+ nGridX = nMaxCol;
+ tools::Long nGridY = aPos.Y() / nGridDY;
+ tools::Long nGridsX = aOutputSize.Width() / nGridDX;
+ GridId nGrid = (nGridY * nGridsX) + nGridX;
+ tools::Long nMiddle = (nGridX * nGridDX) + (nGridDX / 2);
+ if( rPos.X() < nMiddle )
+ {
+ if( !nGrid )
+ nGrid = GRID_NOT_FOUND;
+ else
+ nGrid--;
+ }
+ return nGrid;
+}
+
+bool SvxIconChoiceCtrl_Impl::RequestHelp( const HelpEvent& rHEvt )
+{
+ if ( !(rHEvt.GetMode() & HelpEventMode::QUICK ) )
+ return false;
+
+ Point aPos( pView->ScreenToOutputPixel(rHEvt.GetMousePosPixel() ) );
+ aPos -= pView->GetMapMode().GetOrigin();
+ SvxIconChoiceCtrlEntry* pEntry = GetEntry( aPos, true );
+
+ if ( !pEntry )
+ return false;
+
+ OUString sQuickHelpText = pEntry->GetQuickHelpText();
+ OUString aEntryText( SvtIconChoiceCtrl::GetEntryText( pEntry ) );
+ tools::Rectangle aTextRect( CalcTextRect( pEntry, nullptr, &aEntryText ) );
+ if ( ( !aTextRect.Contains( aPos ) || aEntryText.isEmpty() ) && sQuickHelpText.isEmpty() )
+ return false;
+
+ tools::Rectangle aOptTextRect( aTextRect );
+ aOptTextRect.SetBottom( LONG_MAX );
+ DrawTextFlags nNewFlags = nCurTextDrawFlags;
+ nNewFlags &= ~DrawTextFlags( DrawTextFlags::Clip | DrawTextFlags::EndEllipsis );
+ aOptTextRect = pView->GetTextRect( aOptTextRect, aEntryText, nNewFlags );
+ if ( aOptTextRect != aTextRect || !sQuickHelpText.isEmpty() )
+ {
+ //aTextRect.Right() = aTextRect.Left() + aRealSize.Width() + 4;
+ Point aPt( aOptTextRect.TopLeft() );
+ aPt += pView->GetMapMode().GetOrigin();
+ aPt = pView->OutputToScreenPixel( aPt );
+ // subtract border of tooltip help
+ aPt.AdjustY( -1 );
+ aPt.AdjustX( -3 );
+ aOptTextRect.SetPos( aPt );
+ OUString sHelpText;
+ if ( !sQuickHelpText.isEmpty() )
+ sHelpText = sQuickHelpText;
+ else
+ sHelpText = aEntryText;
+ Help::ShowQuickHelp( static_cast<vcl::Window*>(pView), aOptTextRect, sHelpText, QuickHelpFlags::Left | QuickHelpFlags::VCenter );
+ }
+
+ return true;
+}
+
+void SvxIconChoiceCtrl_Impl::SetColumn( sal_uInt16 nIndex, const SvxIconChoiceCtrlColumnInfo& rInfo)
+{
+ if (!m_pColumns)
+ m_pColumns.reset(new SvxIconChoiceCtrlColumnInfoMap);
+
+ SvxIconChoiceCtrlColumnInfo* pInfo = new SvxIconChoiceCtrlColumnInfo( rInfo );
+ m_pColumns->insert(std::make_pair(nIndex, std::unique_ptr<SvxIconChoiceCtrlColumnInfo>(pInfo)));
+
+ // HACK: Detail mode is not yet fully implemented, this workaround makes it
+ // fly with a single column
+ if( !nIndex && (nWinBits & WB_DETAILS) )
+ nGridDX = pInfo->GetWidth();
+
+ if( GetUpdateMode() )
+ Arrange( IsAutoArrange(), 0, 0 );
+}
+
+const SvxIconChoiceCtrlColumnInfo* SvxIconChoiceCtrl_Impl::GetColumn( sal_uInt16 nIndex ) const
+{
+ if (!m_pColumns)
+ return nullptr;
+ auto const it = m_pColumns->find( nIndex );
+ if (it == m_pColumns->end())
+ return nullptr;
+ return it->second.get();
+}
+
+void SvxIconChoiceCtrl_Impl::DrawHighlightFrame(vcl::RenderContext& rRenderContext, const tools::Rectangle& rBmpRect)
+{
+ tools::Rectangle aBmpRect(rBmpRect);
+ tools::Long nBorder = 2;
+ if (aImageSize.Width() < 32)
+ nBorder = 1;
+ aBmpRect.AdjustRight(nBorder );
+ aBmpRect.AdjustLeft( -nBorder );
+ aBmpRect.AdjustBottom(nBorder );
+ aBmpRect.AdjustTop( -nBorder );
+
+ DecorationView aDecoView(&rRenderContext);
+ DrawHighlightFrameStyle nDecoFlags;
+ if (bHighlightFramePressed)
+ nDecoFlags = DrawHighlightFrameStyle::In;
+ else
+ nDecoFlags = DrawHighlightFrameStyle::Out;
+ aDecoView.DrawHighlightFrame(aBmpRect, nDecoFlags);
+}
+
+void SvxIconChoiceCtrl_Impl::SetEntryHighlightFrame( SvxIconChoiceCtrlEntry* pEntry,
+ bool bKeepHighlightFlags )
+{
+ if( pEntry == pCurHighlightFrame )
+ return;
+
+ if( !bKeepHighlightFlags )
+ bHighlightFramePressed = false;
+
+ if (pCurHighlightFrame)
+ {
+ tools::Rectangle aInvalidationRect(GetEntryBoundRect(pCurHighlightFrame));
+ aInvalidationRect.expand(5);
+ pCurHighlightFrame = nullptr;
+ pView->Invalidate(aInvalidationRect);
+ }
+
+ pCurHighlightFrame = pEntry;
+ if (pEntry)
+ {
+ tools::Rectangle aInvalidationRect(GetEntryBoundRect(pEntry));
+ aInvalidationRect.expand(5);
+ pView->Invalidate(aInvalidationRect);
+ }
+}
+
+void SvxIconChoiceCtrl_Impl::CallSelectHandler()
+{
+ // When single-click mode is active, the selection handler should be called
+ // synchronously, as the selection is automatically taken away once the
+ // mouse cursor doesn't touch the object any more. Else, we might run into
+ // missing calls to Select if the object is selected from a mouse movement,
+ // because when starting the timer, the mouse cursor might have already left
+ // the object.
+ // In special cases (=>SfxFileDialog!), synchronous calls can be forced via
+ // WB_NOASYNCSELECTHDL.
+ if( nWinBits & (WB_NOASYNCSELECTHDL | WB_HIGHLIGHTFRAME) )
+ {
+ pHdlEntry = nullptr;
+ pView->ClickIcon();
+ //pView->Select();
+ }
+ else
+ aCallSelectHdlIdle.Start();
+}
+
+IMPL_LINK_NOARG(SvxIconChoiceCtrl_Impl, CallSelectHdlHdl, Timer *, void)
+{
+ pHdlEntry = nullptr;
+ pView->ClickIcon();
+ //pView->Select();
+}
+
+void SvxIconChoiceCtrl_Impl::SetOrigin( const Point& rPos )
+{
+ MapMode aMapMode( pView->GetMapMode() );
+ aMapMode.SetOrigin( rPos );
+ pView->SetMapMode( aMapMode );
+}
+
+void SvxIconChoiceCtrl_Impl::CallEventListeners( VclEventId nEvent, void* pData )
+{
+ pView->CallImplEventListeners( nEvent, pData );
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imivctl2.cxx b/vcl/source/control/imivctl2.cxx
new file mode 100644
index 0000000000..5b0d2d8adb
--- /dev/null
+++ b/vcl/source/control/imivctl2.cxx
@@ -0,0 +1,715 @@
+/* -*- 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 "imivctl.hxx"
+#include <sal/log.hxx>
+
+IcnCursor_Impl::IcnCursor_Impl( SvxIconChoiceCtrl_Impl* pOwner )
+{
+ pView = pOwner;
+ pCurEntry = nullptr;
+ nDeltaWidth = 0;
+ nDeltaHeight= 0;
+ nCols = 0;
+ nRows = 0;
+}
+
+IcnCursor_Impl::~IcnCursor_Impl()
+{
+}
+
+sal_uInt16 IcnCursor_Impl::GetSortListPos( SvxIconChoiceCtrlEntryPtrVec& rList, tools::Long nValue,
+ bool bVertical )
+{
+ sal_uInt16 nCount = rList.size();
+ if( !nCount )
+ return 0;
+
+ sal_uInt16 nCurPos = 0;
+ tools::Long nPrevValue = LONG_MIN;
+ while( nCount )
+ {
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( rList[nCurPos] );
+ tools::Long nCurValue;
+ if( bVertical )
+ nCurValue = rRect.Top();
+ else
+ nCurValue = rRect.Left();
+ if( nValue >= nPrevValue && nValue <= nCurValue )
+ return nCurPos;
+ nPrevValue = nCurValue;
+ nCount--;
+ nCurPos++;
+ }
+ return rList.size();
+}
+
+void IcnCursor_Impl::ImplCreate()
+{
+ pView->CheckBoundingRects();
+ DBG_ASSERT(xColumns==nullptr&&xRows==nullptr,"ImplCreate: Not cleared");
+
+ SetDeltas();
+
+ xColumns.reset(new IconChoiceMap);
+ xRows.reset(new IconChoiceMap);
+
+ size_t nCount = pView->maEntries.size();
+ for( size_t nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = pView->maEntries[ nCur ].get();
+ // const Rectangle& rRect = pView->GetEntryBoundRect( pEntry );
+ tools::Rectangle rRect( pView->CalcBmpRect( pEntry ) );
+ short nY = static_cast<short>( ((rRect.Top()+rRect.Bottom())/2) / nDeltaHeight );
+ short nX = static_cast<short>( ((rRect.Left()+rRect.Right())/2) / nDeltaWidth );
+
+ // capture rounding errors
+ if( nY >= nRows )
+ nY = sal::static_int_cast< short >(nRows - 1);
+ if( nX >= nCols )
+ nX = sal::static_int_cast< short >(nCols - 1);
+
+ SvxIconChoiceCtrlEntryPtrVec& rColEntry = (*xColumns)[nX];
+ sal_uInt16 nIns = GetSortListPos( rColEntry, rRect.Top(), true );
+ rColEntry.insert( rColEntry.begin() + nIns, pEntry );
+
+ SvxIconChoiceCtrlEntryPtrVec& rRowEntry = (*xRows)[nY];
+ nIns = GetSortListPos( rRowEntry, rRect.Left(), false );
+ rRowEntry.insert( rRowEntry.begin() + nIns, pEntry );
+
+ pEntry->nX = nX;
+ pEntry->nY = nY;
+ }
+}
+
+
+void IcnCursor_Impl::Clear()
+{
+ if( xColumns )
+ {
+ xColumns.reset();
+ xRows.reset();
+ pCurEntry = nullptr;
+ nDeltaWidth = 0;
+ nDeltaHeight = 0;
+ }
+}
+
+SvxIconChoiceCtrlEntry* IcnCursor_Impl::SearchCol(sal_uInt16 nCol, sal_uInt16 nTop, sal_uInt16 nBottom,
+ bool bDown, bool bSimple )
+{
+ DBG_ASSERT(pCurEntry, "SearchCol: No reference entry");
+ IconChoiceMap::iterator mapIt = xColumns->find( nCol );
+ if ( mapIt == xColumns->end() )
+ return nullptr;
+ SvxIconChoiceCtrlEntryPtrVec const & rList = mapIt->second;
+ const sal_uInt16 nCount = rList.size();
+ if( !nCount )
+ return nullptr;
+
+ const tools::Rectangle& rRefRect = pView->GetEntryBoundRect(pCurEntry);
+
+ if( bSimple )
+ {
+ SvxIconChoiceCtrlEntryPtrVec::const_iterator it = std::find( rList.begin(), rList.end(), pCurEntry );
+
+ assert(it != rList.end()); //Entry not in Col-List
+ if (it == rList.end())
+ return nullptr;
+
+ if( bDown )
+ {
+ while( ++it != rList.end() )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = *it;
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( pEntry );
+ if( rRect.Top() > rRefRect.Top() )
+ return pEntry;
+ }
+ return nullptr;
+ }
+ else
+ {
+ SvxIconChoiceCtrlEntryPtrVec::const_reverse_iterator it2(it);
+ while (it2 != rList.rend())
+ {
+ SvxIconChoiceCtrlEntry* pEntry = *it2;
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( pEntry );
+ if( rRect.Top() < rRefRect.Top() )
+ return pEntry;
+ ++it2;
+ }
+ return nullptr;
+ }
+ }
+
+ if( nTop > nBottom )
+ std::swap(nTop, nBottom);
+
+ tools::Long nMinDistance = LONG_MAX;
+ SvxIconChoiceCtrlEntry* pResult = nullptr;
+ for( sal_uInt16 nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = rList[ nCur ];
+ if( pEntry != pCurEntry )
+ {
+ sal_uInt16 nY = pEntry->nY;
+ if( nY >= nTop && nY <= nBottom )
+ {
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( pEntry );
+ tools::Long nDistance = rRect.Top() - rRefRect.Top();
+ if( nDistance < 0 )
+ nDistance *= -1;
+ if( nDistance && nDistance < nMinDistance )
+ {
+ nMinDistance = nDistance;
+ pResult = pEntry;
+ }
+ }
+ }
+ }
+ return pResult;
+}
+
+SvxIconChoiceCtrlEntry* IcnCursor_Impl::SearchRow(sal_uInt16 nRow, sal_uInt16 nLeft, sal_uInt16 nRight,
+ bool bRight, bool bSimple )
+{
+ DBG_ASSERT(pCurEntry,"SearchRow: No reference entry");
+ IconChoiceMap::iterator mapIt = xRows->find( nRow );
+ if ( mapIt == xRows->end() )
+ return nullptr;
+ SvxIconChoiceCtrlEntryPtrVec const & rList = mapIt->second;
+ const sal_uInt16 nCount = rList.size();
+ if( !nCount )
+ return nullptr;
+
+ const tools::Rectangle& rRefRect = pView->GetEntryBoundRect(pCurEntry);
+
+ if( bSimple )
+ {
+ SvxIconChoiceCtrlEntryPtrVec::const_iterator it = std::find( rList.begin(), rList.end(), pCurEntry );
+
+ assert(it != rList.end()); //Entry not in Row-List
+ if (it == rList.end())
+ return nullptr;
+
+ if( bRight )
+ {
+ while( ++it != rList.end() )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = *it;
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( pEntry );
+ if( rRect.Left() > rRefRect.Left() )
+ return pEntry;
+ }
+ return nullptr;
+ }
+ else
+ {
+ SvxIconChoiceCtrlEntryPtrVec::const_reverse_iterator it2(it);
+ while (it2 != rList.rend())
+ {
+ SvxIconChoiceCtrlEntry* pEntry = *it2;
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( pEntry );
+ if( rRect.Left() < rRefRect.Left() )
+ return pEntry;
+ ++it2;
+ }
+ return nullptr;
+ }
+
+ }
+ if( nRight < nLeft )
+ std::swap(nRight, nLeft);
+
+ tools::Long nMinDistance = LONG_MAX;
+ SvxIconChoiceCtrlEntry* pResult = nullptr;
+ for( sal_uInt16 nCur = 0; nCur < nCount; nCur++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = rList[ nCur ];
+ if( pEntry != pCurEntry )
+ {
+ sal_uInt16 nX = pEntry->nX;
+ if( nX >= nLeft && nX <= nRight )
+ {
+ const tools::Rectangle& rRect = pView->GetEntryBoundRect( pEntry );
+ tools::Long nDistance = rRect.Left() - rRefRect.Left();
+ if( nDistance < 0 )
+ nDistance *= -1;
+ if( nDistance && nDistance < nMinDistance )
+ {
+ nMinDistance = nDistance;
+ pResult = pEntry;
+ }
+ }
+ }
+ }
+ return pResult;
+}
+
+
+/*
+ Searches, starting from the passed value, the next entry to the left/to the
+ right. Example for bRight = sal_True:
+
+ c
+ b c
+ a b c
+ S 1 1 1 ====> search direction
+ a b c
+ b c
+ c
+
+ S : starting position
+ 1 : first searched rectangle
+ a,b,c : 2nd, 3rd, 4th searched rectangle
+*/
+
+SvxIconChoiceCtrlEntry* IcnCursor_Impl::GoLeftRight( SvxIconChoiceCtrlEntry* pCtrlEntry, bool bRight )
+{
+ SvxIconChoiceCtrlEntry* pResult;
+ pCurEntry = pCtrlEntry;
+ Create();
+ sal_uInt16 nY = pCtrlEntry->nY;
+ sal_uInt16 nX = pCtrlEntry->nX;
+ DBG_ASSERT(nY< nRows,"GoLeftRight:Bad column");
+ DBG_ASSERT(nX< nCols,"GoLeftRight:Bad row");
+ // neighbor in same row?
+ if( bRight )
+ pResult = SearchRow(
+ nY, nX, sal::static_int_cast< sal_uInt16 >(nCols-1), true, true );
+ else
+ pResult = SearchRow( nY, 0, nX, false, true );
+ if( pResult )
+ return pResult;
+
+ tools::Long nCurCol = nX;
+
+ tools::Long nColOffs, nLastCol;
+ if( bRight )
+ {
+ nColOffs = 1;
+ nLastCol = nCols;
+ }
+ else
+ {
+ nColOffs = -1;
+ nLastCol = -1; // 0-1
+ }
+
+ sal_uInt16 nRowMin = nY;
+ sal_uInt16 nRowMax = nY;
+ do
+ {
+ SvxIconChoiceCtrlEntry* pEntry = SearchCol(static_cast<sal_uInt16>(nCurCol), nRowMin, nRowMax, true, false);
+ if( pEntry )
+ return pEntry;
+ if( nRowMin )
+ nRowMin--;
+ if( nRowMax < (nRows-1))
+ nRowMax++;
+ nCurCol += nColOffs;
+ } while( nCurCol != nLastCol );
+ return nullptr;
+}
+
+SvxIconChoiceCtrlEntry* IcnCursor_Impl::GoPageUpDown( SvxIconChoiceCtrlEntry* pStart, bool bDown)
+{
+ if( pView->IsAutoArrange() && !(pView->nWinBits & WB_ALIGN_TOP) )
+ {
+ const tools::Long nPos = static_cast<tools::Long>(pView->GetEntryListPos( pStart ));
+ tools::Long nEntriesInView = pView->aOutputSize.Height() / pView->nGridDY;
+ nEntriesInView *=
+ ((pView->aOutputSize.Width()+(pView->nGridDX/2)) / pView->nGridDX );
+ tools::Long nNewPos = nPos;
+ if( bDown )
+ {
+ nNewPos += nEntriesInView;
+ if( nNewPos >= static_cast<tools::Long>(pView->maEntries.size()) )
+ nNewPos = pView->maEntries.size() - 1;
+ }
+ else
+ {
+ nNewPos -= nEntriesInView;
+ if( nNewPos < 0 )
+ nNewPos = 0;
+ }
+ if( nPos != nNewPos )
+ return pView->maEntries[ static_cast<size_t>(nNewPos) ].get();
+ return nullptr;
+ }
+ tools::Long nOpt = pView->GetEntryBoundRect( pStart ).Top();
+ if( bDown )
+ {
+ nOpt += pView->aOutputSize.Height();
+ nOpt -= pView->nGridDY;
+ }
+ else
+ {
+ nOpt -= pView->aOutputSize.Height();
+ nOpt += pView->nGridDY;
+ }
+ if( nOpt < 0 )
+ nOpt = 0;
+
+ tools::Long nPrevErr = LONG_MAX;
+
+ SvxIconChoiceCtrlEntry* pPrev = pStart;
+ SvxIconChoiceCtrlEntry* pNext = GoUpDown( pStart, bDown );
+ while( pNext )
+ {
+ tools::Long nCur = pView->GetEntryBoundRect( pNext ).Top();
+ tools::Long nErr = nOpt - nCur;
+ if( nErr < 0 )
+ nErr *= -1;
+ if( nErr > nPrevErr )
+ return pPrev;
+ nPrevErr = nErr;
+ pPrev = pNext;
+ pNext = GoUpDown( pNext, bDown );
+ }
+ if( pPrev != pStart )
+ return pPrev;
+ return nullptr;
+}
+
+SvxIconChoiceCtrlEntry* IcnCursor_Impl::GoUpDown( SvxIconChoiceCtrlEntry* pCtrlEntry, bool bDown)
+{
+ if( pView->IsAutoArrange() && !(pView->nWinBits & WB_ALIGN_TOP) )
+ {
+ sal_uLong nPos = pView->GetEntryListPos( pCtrlEntry );
+ if( bDown && nPos < (pView->maEntries.size() - 1) )
+ return pView->maEntries[ nPos + 1 ].get();
+ else if( !bDown && nPos > 0 )
+ return pView->maEntries[ nPos - 1 ].get();
+ return nullptr;
+ }
+
+ SvxIconChoiceCtrlEntry* pResult;
+ pCurEntry = pCtrlEntry;
+ Create();
+ sal_uInt16 nY = pCtrlEntry->nY;
+ sal_uInt16 nX = pCtrlEntry->nX;
+ DBG_ASSERT(nY<nRows,"GoUpDown:Bad column");
+ DBG_ASSERT(nX<nCols,"GoUpDown:Bad row");
+
+ // neighbor in same column?
+ if( bDown )
+ pResult = SearchCol(
+ nX, nY, sal::static_int_cast< sal_uInt16 >(nRows-1), true, true );
+ else
+ pResult = SearchCol( nX, 0, nY, false, true );
+ if( pResult )
+ return pResult;
+
+ tools::Long nCurRow = nY;
+
+ tools::Long nRowOffs, nLastRow;
+ if( bDown )
+ {
+ nRowOffs = 1;
+ nLastRow = nRows;
+ }
+ else
+ {
+ nRowOffs = -1;
+ nLastRow = -1; // 0-1
+ }
+
+ sal_uInt16 nColMin = nX;
+ sal_uInt16 nColMax = nX;
+ do
+ {
+ SvxIconChoiceCtrlEntry* pEntry = SearchRow(static_cast<sal_uInt16>(nCurRow), nColMin, nColMax, true, false);
+ if( pEntry )
+ return pEntry;
+ if( nColMin )
+ nColMin--;
+ if( nColMax < (nCols-1))
+ nColMax++;
+ nCurRow += nRowOffs;
+ } while( nCurRow != nLastRow );
+ return nullptr;
+}
+
+void IcnCursor_Impl::SetDeltas()
+{
+ const Size& rSize = pView->aVirtOutputSize;
+ nCols = rSize.Width() / pView->nGridDX;
+ if( !nCols )
+ nCols = 1;
+ nRows = rSize.Height() / pView->nGridDY;
+ if( (nRows * pView->nGridDY) < rSize.Height() )
+ nRows++;
+ if( !nRows )
+ nRows = 1;
+
+ nDeltaWidth = static_cast<short>(rSize.Width() / nCols);
+ nDeltaHeight = static_cast<short>(rSize.Height() / nRows);
+ if( !nDeltaHeight )
+ {
+ nDeltaHeight = 1;
+ SAL_INFO("vcl", "SetDeltas:Bad height");
+ }
+ if( !nDeltaWidth )
+ {
+ nDeltaWidth = 1;
+ SAL_INFO("vcl", "SetDeltas:Bad width");
+ }
+}
+
+IcnGridMap_Impl::IcnGridMap_Impl(SvxIconChoiceCtrl_Impl* pView)
+ : _pView(pView), _nGridCols(0), _nGridRows(0)
+{
+}
+
+IcnGridMap_Impl::~IcnGridMap_Impl()
+{
+}
+
+void IcnGridMap_Impl::Expand()
+{
+ if( !_pGridMap )
+ Create_Impl();
+ else
+ {
+ sal_uInt16 nNewGridRows = _nGridRows;
+ sal_uInt16 nNewGridCols = _nGridCols;
+ if( _pView->nWinBits & WB_ALIGN_TOP )
+ nNewGridRows += 50;
+ else
+ nNewGridCols += 50;
+
+ size_t nNewCellCount = static_cast<size_t>(nNewGridRows) * nNewGridCols;
+ bool* pNewGridMap = new bool[nNewCellCount];
+ size_t nOldCellCount = static_cast<size_t>(_nGridRows) * _nGridCols;
+ memcpy(pNewGridMap, _pGridMap.get(), nOldCellCount * sizeof(bool));
+ memset(pNewGridMap + nOldCellCount, 0, (nNewCellCount-nOldCellCount) * sizeof(bool));
+ _pGridMap.reset( pNewGridMap );
+ _nGridRows = nNewGridRows;
+ _nGridCols = nNewGridCols;
+ }
+}
+
+void IcnGridMap_Impl::Create_Impl()
+{
+ DBG_ASSERT(!_pGridMap,"Unnecessary call to IcnGridMap_Impl::Create_Impl()");
+ if( _pGridMap )
+ return;
+ GetMinMapSize( _nGridCols, _nGridRows );
+ if( _pView->nWinBits & WB_ALIGN_TOP )
+ _nGridRows += 50; // avoid resize of gridmap too often
+ else
+ _nGridCols += 50;
+
+ size_t nCellCount = static_cast<size_t>(_nGridRows) * _nGridCols;
+ _pGridMap.reset( new bool[nCellCount] );
+ memset(_pGridMap.get(), 0, nCellCount * sizeof(bool));
+
+ const size_t nCount = _pView->maEntries.size();
+ for( size_t nCur=0; nCur < nCount; nCur++ )
+ OccupyGrids( _pView->maEntries[ nCur ].get() );
+}
+
+void IcnGridMap_Impl::GetMinMapSize( sal_uInt16& rDX, sal_uInt16& rDY ) const
+{
+ tools::Long nX, nY;
+ if( _pView->nWinBits & WB_ALIGN_TOP )
+ {
+ // The view grows in vertical direction. Its max. width is _pView->nMaxVirtWidth
+ nX = _pView->nMaxVirtWidth;
+ if( !nX )
+ nX = _pView->pView->GetOutputSizePixel().Width();
+ if( !(_pView->nFlags & IconChoiceFlags::Arranging) )
+ nX -= _pView->nVerSBarWidth;
+
+ nY = _pView->aVirtOutputSize.Height();
+ }
+ else
+ {
+ // The view grows in horizontal direction. Its max. height is _pView->nMaxVirtHeight
+ nY = _pView->nMaxVirtHeight;
+ if( !nY )
+ nY = _pView->pView->GetOutputSizePixel().Height();
+ if( !(_pView->nFlags & IconChoiceFlags::Arranging) )
+ nY -= _pView->nHorSBarHeight;
+ nX = _pView->aVirtOutputSize.Width();
+ }
+
+ if( !nX )
+ nX = DEFAULT_MAX_VIRT_WIDTH;
+ if( !nY )
+ nY = DEFAULT_MAX_VIRT_HEIGHT;
+
+ tools::Long nDX = nX / _pView->nGridDX;
+ tools::Long nDY = nY / _pView->nGridDY;
+
+ if( !nDX )
+ nDX++;
+ if( !nDY )
+ nDY++;
+
+ rDX = static_cast<sal_uInt16>(nDX);
+ rDY = static_cast<sal_uInt16>(nDY);
+}
+
+GridId IcnGridMap_Impl::GetGrid( sal_uInt16 nGridX, sal_uInt16 nGridY )
+{
+ Create();
+ if( _pView->nWinBits & WB_ALIGN_TOP )
+ return nGridX + ( static_cast<GridId>(nGridY) * _nGridCols );
+ else
+ return nGridY + ( static_cast<GridId>(nGridX) * _nGridRows );
+}
+
+GridId IcnGridMap_Impl::GetGrid( const Point& rDocPos )
+{
+ Create();
+
+ tools::Long nX = rDocPos.X();
+ tools::Long nY = rDocPos.Y();
+ nX -= LROFFS_WINBORDER;
+ nY -= TBOFFS_WINBORDER;
+ nX /= _pView->nGridDX;
+ nY /= _pView->nGridDY;
+ if( nX >= _nGridCols )
+ {
+ nX = _nGridCols - 1;
+ }
+ if( nY >= _nGridRows )
+ {
+ nY = _nGridRows - 1;
+ }
+ GridId nId = GetGrid( static_cast<sal_uInt16>(nX), static_cast<sal_uInt16>(nY) );
+ DBG_ASSERT(nId <o3tl::make_unsigned(_nGridCols*_nGridRows),"GetGrid failed");
+ return nId;
+}
+
+tools::Rectangle IcnGridMap_Impl::GetGridRect( GridId nId )
+{
+ Create();
+ sal_uInt16 nGridX, nGridY;
+ GetGridCoord( nId, nGridX, nGridY );
+ const tools::Long nLeft = nGridX * _pView->nGridDX+ LROFFS_WINBORDER;
+ const tools::Long nTop = nGridY * _pView->nGridDY + TBOFFS_WINBORDER;
+ return tools::Rectangle(
+ nLeft, nTop,
+ nLeft + _pView->nGridDX,
+ nTop + _pView->nGridDY );
+}
+
+GridId IcnGridMap_Impl::GetUnoccupiedGrid()
+{
+ Create();
+ sal_uLong nStart = 0;
+ bool bExpanded = false;
+
+ while( true )
+ {
+ const sal_uLong nCount = static_cast<sal_uInt16>(_nGridCols * _nGridRows);
+ for( sal_uLong nCur = nStart; nCur < nCount; nCur++ )
+ {
+ if( !_pGridMap[ nCur ] )
+ {
+ _pGridMap[ nCur ] = true;
+ return static_cast<GridId>(nCur);
+ }
+ }
+ DBG_ASSERT(!bExpanded,"ExpandGrid failed");
+ if( bExpanded )
+ return 0; // prevent never ending loop
+ bExpanded = true;
+ Expand();
+ nStart = nCount;
+ }
+}
+
+// An entry only means that there's a GridRect lying under its center. This
+// variant is much faster than allocating via the bounding rectangle but can
+// lead to small overlaps.
+void IcnGridMap_Impl::OccupyGrids( const SvxIconChoiceCtrlEntry* pEntry )
+{
+ if( !_pGridMap || !SvxIconChoiceCtrl_Impl::IsBoundingRectValid( pEntry->aRect ))
+ return;
+ OccupyGrid( GetGrid( pEntry->aRect.Center()) );
+}
+
+void IcnGridMap_Impl::Clear()
+{
+ if( _pGridMap )
+ {
+ _pGridMap.reset();
+ _nGridRows = 0;
+ _nGridCols = 0;
+ _aLastOccupiedGrid.SetEmpty();
+ }
+}
+
+sal_uLong IcnGridMap_Impl::GetGridCount( const Size& rSizePixel, sal_uInt16 nDX, sal_uInt16 nDY)
+{
+ tools::Long ndx = (rSizePixel.Width() - LROFFS_WINBORDER) / nDX;
+ if( ndx < 0 ) ndx *= -1;
+ tools::Long ndy = (rSizePixel.Height() - TBOFFS_WINBORDER) / nDY;
+ if( ndy < 0 ) ndy *= -1;
+ return static_cast<sal_uLong>(ndx * ndy);
+}
+
+void IcnGridMap_Impl::OutputSizeChanged()
+{
+ if( !_pGridMap )
+ return;
+
+ sal_uInt16 nCols, nRows;
+ GetMinMapSize( nCols, nRows );
+ if( _pView->nWinBits & WB_ALIGN_TOP )
+ {
+ if( nCols != _nGridCols )
+ Clear();
+ else if( nRows >= _nGridRows )
+ Expand();
+ }
+ else
+ {
+ if( nRows != _nGridRows )
+ Clear();
+ else if( nCols >= _nGridCols )
+ Expand();
+ }
+}
+
+// Independently of the view's alignment (TOP or LEFT), the gridmap
+// should contain the data in a continuous region, to make it possible
+// to copy the whole block if the gridmap needs to be expanded.
+void IcnGridMap_Impl::GetGridCoord( GridId nId, sal_uInt16& rGridX, sal_uInt16& rGridY )
+{
+ Create();
+ if( _pView->nWinBits & WB_ALIGN_TOP )
+ {
+ rGridX = static_cast<sal_uInt16>(nId % _nGridCols);
+ rGridY = static_cast<sal_uInt16>(nId / _nGridCols);
+ }
+ else
+ {
+ rGridX = static_cast<sal_uInt16>(nId / _nGridRows);
+ rGridY = static_cast<sal_uInt16>(nId % _nGridRows);
+ }
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/imp_listbox.cxx b/vcl/source/control/imp_listbox.cxx
new file mode 100644
index 0000000000..bb4da51859
--- /dev/null
+++ b/vcl/source/control/imp_listbox.cxx
@@ -0,0 +1,3029 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/event.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <vcl/naturalsort.hxx>
+#include <vcl/vcllayout.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/toolkit/scrbar.hxx>
+
+#include <listbox.hxx>
+#include <svdata.hxx>
+#include <window.h>
+
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+#include <comphelper/string.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <limits>
+
+#define MULTILINE_ENTRY_DRAW_FLAGS ( DrawTextFlags::WordBreak | DrawTextFlags::MultiLine | DrawTextFlags::VCenter )
+
+using namespace ::com::sun::star;
+
+constexpr tools::Long gnBorder = 1;
+
+void ImplInitDropDownButton( PushButton* pButton )
+{
+ pButton->SetSymbol( SymbolType::SPIN_DOWN );
+
+ if ( pButton->IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
+ && ! pButton->IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown) )
+ pButton->SetBackground();
+}
+
+ImplEntryList::ImplEntryList( vcl::Window* pWindow )
+{
+ mpWindow = pWindow;
+ mnLastSelected = LISTBOX_ENTRY_NOTFOUND;
+ mnSelectionAnchor = LISTBOX_ENTRY_NOTFOUND;
+ mnImages = 0;
+ mbCallSelectionChangedHdl = true;
+
+ mnMRUCount = 0;
+ mnMaxMRUCount = 0;
+}
+
+ImplEntryList::~ImplEntryList()
+{
+ Clear();
+}
+
+void ImplEntryList::Clear()
+{
+ mnImages = 0;
+ maEntries.clear();
+}
+
+void ImplEntryList::dispose()
+{
+ Clear();
+ mpWindow.clear();
+}
+
+void ImplEntryList::SelectEntry( sal_Int32 nPos, bool bSelect )
+{
+ if (0 <= nPos && o3tl::make_unsigned(nPos) < maEntries.size())
+ {
+ std::vector<std::unique_ptr<ImplEntryType> >::iterator iter = maEntries.begin()+nPos;
+
+ if ( ( (*iter)->mbIsSelected != bSelect ) &&
+ ( ( (*iter)->mnFlags & ListBoxEntryFlags::DisableSelection) == ListBoxEntryFlags::NONE ) )
+ {
+ (*iter)->mbIsSelected = bSelect;
+ if ( mbCallSelectionChangedHdl )
+ maSelectionChangedHdl.Call( nPos );
+ }
+ }
+}
+
+namespace
+{
+ comphelper::string::NaturalStringSorter& GetSorter()
+ {
+ static comphelper::string::NaturalStringSorter gSorter(
+ ::comphelper::getProcessComponentContext(),
+ Application::GetSettings().GetLanguageTag().getLocale());
+ return gSorter;
+ };
+}
+
+namespace vcl
+{
+ sal_Int32 NaturalSortCompare(const OUString &rA, const OUString &rB)
+ {
+ const comphelper::string::NaturalStringSorter &rSorter = GetSorter();
+ return rSorter.compare(rA, rB);
+ }
+}
+
+sal_Int32 ImplEntryList::InsertEntry( sal_Int32 nPos, ImplEntryType* pNewEntry, bool bSort )
+{
+ assert(nPos >= 0);
+ assert(maEntries.size() < LISTBOX_MAX_ENTRIES);
+
+ if ( !!pNewEntry->maImage )
+ mnImages++;
+
+ sal_Int32 insPos = 0;
+ const sal_Int32 nEntriesSize = static_cast<sal_Int32>(maEntries.size());
+
+ if ( !bSort || maEntries.empty())
+ {
+ if (0 <= nPos && nPos < nEntriesSize)
+ {
+ insPos = nPos;
+ maEntries.insert( maEntries.begin() + nPos, std::unique_ptr<ImplEntryType>(pNewEntry) );
+ }
+ else
+ {
+ insPos = nEntriesSize;
+ maEntries.push_back(std::unique_ptr<ImplEntryType>(pNewEntry));
+ }
+ }
+ else
+ {
+ const comphelper::string::NaturalStringSorter &rSorter = GetSorter();
+
+ const OUString& rStr = pNewEntry->maStr;
+
+ ImplEntryType* pTemp = GetEntry( nEntriesSize-1 );
+
+ try
+ {
+ sal_Int32 nComp = rSorter.compare(rStr, pTemp->maStr);
+
+ // fast insert for sorted data
+ if ( nComp >= 0 )
+ {
+ insPos = nEntriesSize;
+ maEntries.push_back(std::unique_ptr<ImplEntryType>(pNewEntry));
+ }
+ else
+ {
+ pTemp = GetEntry( mnMRUCount );
+
+ nComp = rSorter.compare(rStr, pTemp->maStr);
+ if ( nComp <= 0 )
+ {
+ insPos = 0;
+ maEntries.insert(maEntries.begin(), std::unique_ptr<ImplEntryType>(pNewEntry));
+ }
+ else
+ {
+ sal_uLong nLow = mnMRUCount;
+ sal_uLong nHigh = maEntries.size()-1;
+ sal_Int32 nMid;
+
+ // binary search
+ do
+ {
+ nMid = static_cast<sal_Int32>((nLow + nHigh) / 2);
+ pTemp = GetEntry( nMid );
+
+ nComp = rSorter.compare(rStr, pTemp->maStr);
+
+ if ( nComp < 0 )
+ nHigh = nMid-1;
+ else
+ {
+ if ( nComp > 0 )
+ nLow = nMid + 1;
+ else
+ break;
+ }
+ }
+ while ( nLow <= nHigh );
+
+ if ( nComp >= 0 )
+ nMid++;
+
+ insPos = nMid;
+ maEntries.insert(maEntries.begin()+nMid, std::unique_ptr<ImplEntryType>(pNewEntry));
+ }
+ }
+ }
+ catch (uno::RuntimeException& )
+ {
+ // XXX this is arguable, if the exception occurred because pNewEntry is
+ // garbage you wouldn't insert it. If the exception occurred because the
+ // Collator implementation is garbage then give the user a chance to see
+ // his stuff
+ insPos = 0;
+ maEntries.insert(maEntries.begin(), std::unique_ptr<ImplEntryType>(pNewEntry));
+ }
+
+ }
+
+ return insPos;
+}
+
+void ImplEntryList::RemoveEntry( sal_Int32 nPos )
+{
+ if (0 <= nPos && o3tl::make_unsigned(nPos) < maEntries.size())
+ {
+ std::vector<std::unique_ptr<ImplEntryType> >::iterator iter = maEntries.begin()+ nPos;
+
+ if ( !!(*iter)->maImage )
+ mnImages--;
+
+ maEntries.erase(iter);
+ }
+}
+
+sal_Int32 ImplEntryList::FindEntry( std::u16string_view rString, bool bSearchMRUArea ) const
+{
+ const sal_Int32 nEntries = static_cast<sal_Int32>(maEntries.size());
+ for ( sal_Int32 n = bSearchMRUArea ? 0 : GetMRUCount(); n < nEntries; n++ )
+ {
+ OUString aComp( vcl::I18nHelper::filterFormattingChars( maEntries[n]->maStr ) );
+ if ( aComp == rString )
+ return n;
+ }
+ return LISTBOX_ENTRY_NOTFOUND;
+}
+
+sal_Int32 ImplEntryList::FindMatchingEntry( const OUString& rStr, sal_Int32 nStart, bool bLazy ) const
+{
+ sal_Int32 nPos = LISTBOX_ENTRY_NOTFOUND;
+ sal_Int32 nEntryCount = GetEntryCount();
+
+ const vcl::I18nHelper& rI18nHelper = mpWindow->GetSettings().GetLocaleI18nHelper();
+ for ( sal_Int32 n = nStart; n < nEntryCount; )
+ {
+ ImplEntryType* pImplEntry = GetEntry( n );
+ bool bMatch;
+ if ( bLazy )
+ {
+ bMatch = rI18nHelper.MatchString( rStr, pImplEntry->maStr );
+ }
+ else
+ {
+ bMatch = pImplEntry->maStr.startsWith(rStr);
+ }
+ if ( bMatch )
+ {
+ nPos = n;
+ break;
+ }
+
+ n++;
+ }
+
+ return nPos;
+}
+
+tools::Long ImplEntryList::GetAddedHeight( sal_Int32 i_nEndIndex, sal_Int32 i_nBeginIndex ) const
+{
+ tools::Long nHeight = 0;
+ sal_Int32 nStart = std::min(i_nEndIndex, i_nBeginIndex);
+ sal_Int32 nStop = std::max(i_nEndIndex, i_nBeginIndex);
+ sal_Int32 nEntryCount = GetEntryCount();
+ if( 0 <= nStop && nStop != LISTBOX_ENTRY_NOTFOUND && nEntryCount != 0 )
+ {
+ // sanity check
+ if( nStop > nEntryCount-1 )
+ nStop = nEntryCount-1;
+ if (nStart < 0)
+ nStart = 0;
+ else if( nStart > nEntryCount-1 )
+ nStart = nEntryCount-1;
+
+ sal_Int32 nIndex = nStart;
+ while( nIndex != LISTBOX_ENTRY_NOTFOUND && nIndex < nStop )
+ {
+ tools::Long nPosHeight = GetEntryPtr( nIndex )->getHeightWithMargin();
+ if (nHeight > ::std::numeric_limits<tools::Long>::max() - nPosHeight)
+ {
+ SAL_WARN( "vcl", "ImplEntryList::GetAddedHeight: truncated");
+ break;
+ }
+ nHeight += nPosHeight;
+ nIndex++;
+ }
+ }
+ else
+ nHeight = 0;
+ return i_nEndIndex > i_nBeginIndex ? nHeight : -nHeight;
+}
+
+tools::Long ImplEntryList::GetEntryHeight( sal_Int32 nPos ) const
+{
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ return pImplEntry ? pImplEntry->getHeightWithMargin() : 0;
+}
+
+OUString ImplEntryList::GetEntryText( sal_Int32 nPos ) const
+{
+ OUString aEntryText;
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ if ( pImplEntry )
+ aEntryText = pImplEntry->maStr;
+ return aEntryText;
+}
+
+bool ImplEntryList::HasEntryImage( sal_Int32 nPos ) const
+{
+ bool bImage = false;
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ if ( pImplEntry )
+ bImage = !!pImplEntry->maImage;
+ return bImage;
+}
+
+Image ImplEntryList::GetEntryImage( sal_Int32 nPos ) const
+{
+ Image aImage;
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ if ( pImplEntry )
+ aImage = pImplEntry->maImage;
+ return aImage;
+}
+
+void ImplEntryList::SetEntryData( sal_Int32 nPos, void* pNewData )
+{
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ if ( pImplEntry )
+ pImplEntry->mpUserData = pNewData;
+}
+
+void* ImplEntryList::GetEntryData( sal_Int32 nPos ) const
+{
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ return pImplEntry ? pImplEntry->mpUserData : nullptr;
+}
+
+void ImplEntryList::SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags )
+{
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ if ( pImplEntry )
+ pImplEntry->mnFlags = nFlags;
+}
+
+sal_Int32 ImplEntryList::GetSelectedEntryCount() const
+{
+ sal_Int32 nSelCount = 0;
+ for ( sal_Int32 n = GetEntryCount(); n; )
+ {
+ ImplEntryType* pImplEntry = GetEntry( --n );
+ if ( pImplEntry->mbIsSelected )
+ nSelCount++;
+ }
+ return nSelCount;
+}
+
+OUString ImplEntryList::GetSelectedEntry( sal_Int32 nIndex ) const
+{
+ return GetEntryText( GetSelectedEntryPos( nIndex ) );
+}
+
+sal_Int32 ImplEntryList::GetSelectedEntryPos( sal_Int32 nIndex ) const
+{
+ sal_Int32 nSelEntryPos = LISTBOX_ENTRY_NOTFOUND;
+ sal_Int32 nSel = 0;
+ sal_Int32 nEntryCount = GetEntryCount();
+
+ for ( sal_Int32 n = 0; n < nEntryCount; n++ )
+ {
+ ImplEntryType* pImplEntry = GetEntry( n );
+ if ( pImplEntry->mbIsSelected )
+ {
+ if ( nSel == nIndex )
+ {
+ nSelEntryPos = n;
+ break;
+ }
+ nSel++;
+ }
+ }
+
+ return nSelEntryPos;
+}
+
+bool ImplEntryList::IsEntryPosSelected( sal_Int32 nIndex ) const
+{
+ ImplEntryType* pImplEntry = GetEntry( nIndex );
+ return pImplEntry && pImplEntry->mbIsSelected;
+}
+
+bool ImplEntryList::IsEntrySelectable( sal_Int32 nPos ) const
+{
+ ImplEntryType* pImplEntry = GetEntry( nPos );
+ return pImplEntry == nullptr || ((pImplEntry->mnFlags & ListBoxEntryFlags::DisableSelection) == ListBoxEntryFlags::NONE);
+}
+
+sal_Int32 ImplEntryList::FindFirstSelectable( sal_Int32 nPos, bool bForward /* = true */ ) const
+{
+ if( IsEntrySelectable( nPos ) )
+ return nPos;
+
+ if( bForward )
+ {
+ for( nPos = nPos + 1; nPos < GetEntryCount(); nPos++ )
+ {
+ if( IsEntrySelectable( nPos ) )
+ return nPos;
+ }
+ }
+ else
+ {
+ while( nPos )
+ {
+ nPos--;
+ if( IsEntrySelectable( nPos ) )
+ return nPos;
+ }
+ }
+
+ return LISTBOX_ENTRY_NOTFOUND;
+}
+
+ImplListBoxWindow::ImplListBoxWindow( vcl::Window* pParent, WinBits nWinStyle ) :
+ Control( pParent, 0 ),
+ maEntryList( this ),
+ maQuickSelectionEngine( *this )
+{
+
+ mnTop = 0;
+ mnLeft = 0;
+ mnSelectModifier = 0;
+ mnUserDrawEntry = LISTBOX_ENTRY_NOTFOUND;
+ mbTrack = false;
+ mbTravelSelect = false;
+ mbTrackingSelect = false;
+ mbSelectionChanged = false;
+ mbMouseMoveSelect = false;
+ mbMulti = false;
+ mbGrabFocus = false;
+ mbUserDrawEnabled = false;
+ mbInUserDraw = false;
+ mbReadOnly = false;
+ mbHasFocusRect = false;
+ mbRight = ( nWinStyle & WB_RIGHT );
+ mbCenter = ( nWinStyle & WB_CENTER );
+ mbSimpleMode = ( nWinStyle & WB_SIMPLEMODE );
+ mbSort = ( nWinStyle & WB_SORT );
+ mbIsDropdown = ( nWinStyle & WB_DROPDOWN );
+ mbEdgeBlending = false;
+
+ mnCurrentPos = LISTBOX_ENTRY_NOTFOUND;
+ mnTrackingSaveSelection = LISTBOX_ENTRY_NOTFOUND;
+
+ GetOutDev()->SetLineColor();
+ SetTextFillColor();
+ SetBackground( Wallpaper( GetSettings().GetStyleSettings().GetFieldColor() ) );
+
+ ApplySettings(*GetOutDev());
+ ImplCalcMetrics();
+}
+
+ImplListBoxWindow::~ImplListBoxWindow()
+{
+ disposeOnce();
+}
+
+void ImplListBoxWindow::dispose()
+{
+ maEntryList.dispose();
+ Control::dispose();
+}
+
+void ImplListBoxWindow::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ ApplyControlFont(rRenderContext, rStyleSettings.GetFieldFont());
+ ApplyControlForeground(rRenderContext, rStyleSettings.GetListBoxWindowTextColor());
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else
+ rRenderContext.SetBackground(rStyleSettings.GetListBoxWindowBackgroundColor());
+}
+
+void ImplListBoxWindow::ImplCalcMetrics()
+{
+ mnMaxWidth = 0;
+ mnMaxTxtWidth = 0;
+ mnMaxImgWidth = 0;
+ mnMaxImgTxtWidth= 0;
+ mnMaxImgHeight = 0;
+
+ mnTextHeight = static_cast<sal_uInt16>(GetTextHeight());
+ mnMaxTxtHeight = mnTextHeight + gnBorder;
+ mnMaxHeight = mnMaxTxtHeight;
+
+ if ( maUserItemSize.Height() > mnMaxHeight )
+ mnMaxHeight = static_cast<sal_uInt16>(maUserItemSize.Height());
+ if ( maUserItemSize.Width() > mnMaxWidth )
+ mnMaxWidth= static_cast<sal_uInt16>(maUserItemSize.Width());
+
+ for ( sal_Int32 n = maEntryList.GetEntryCount(); n; )
+ {
+ ImplEntryType* pEntry = maEntryList.GetMutableEntryPtr( --n );
+ ImplUpdateEntryMetrics( *pEntry );
+ }
+
+ if( mnCurrentPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ Size aSz( GetOutputSizePixel().Width(), maEntryList.GetEntryPtr( mnCurrentPos )->getHeightWithMargin() );
+ maFocusRect.SetSize( aSz );
+ }
+}
+
+void ImplListBoxWindow::Clear()
+{
+ maEntryList.Clear();
+
+ mnMaxHeight = mnMaxTxtHeight;
+ mnMaxWidth = 0;
+ mnMaxTxtWidth = 0;
+ mnMaxImgTxtWidth= 0;
+ mnMaxImgWidth = 0;
+ mnMaxImgHeight = 0;
+ mnTop = 0;
+ mnLeft = 0;
+ ImplClearLayoutData();
+
+ mnCurrentPos = LISTBOX_ENTRY_NOTFOUND;
+ maQuickSelectionEngine.Reset();
+
+ Invalidate();
+}
+
+void ImplListBoxWindow::SetUserItemSize( const Size& rSz )
+{
+ ImplClearLayoutData();
+ maUserItemSize = rSz;
+ ImplCalcMetrics();
+}
+
+namespace {
+
+struct ImplEntryMetrics
+{
+ bool bText;
+ bool bImage;
+ tools::Long nEntryWidth;
+ tools::Long nEntryHeight;
+ tools::Long nTextWidth;
+ tools::Long nImgWidth;
+ tools::Long nImgHeight;
+};
+
+}
+
+tools::Long ImplEntryType::getHeightWithMargin() const
+{
+ return mnHeight + ImplGetSVData()->maNWFData.mnListBoxEntryMargin;
+}
+
+SalLayoutGlyphs* ImplEntryType::GetTextGlyphs(const OutputDevice* pOutputDevice)
+{
+ if (maStrGlyphs.IsValid())
+ // Use pre-calculated result.
+ return &maStrGlyphs;
+
+ std::unique_ptr<SalLayout> pLayout = pOutputDevice->ImplLayout(
+ maStr, 0, maStr.getLength(), Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly);
+ if (!pLayout)
+ return nullptr;
+
+ // Remember the calculation result.
+ maStrGlyphs = pLayout->GetGlyphs();
+
+ return &maStrGlyphs;
+}
+
+void ImplListBoxWindow::ImplUpdateEntryMetrics( ImplEntryType& rEntry )
+{
+ ImplEntryMetrics aMetrics;
+ aMetrics.bText = !rEntry.maStr.isEmpty();
+ aMetrics.bImage = !!rEntry.maImage;
+ aMetrics.nEntryWidth = 0;
+ aMetrics.nEntryHeight = 0;
+ aMetrics.nTextWidth = 0;
+ aMetrics.nImgWidth = 0;
+ aMetrics.nImgHeight = 0;
+
+ if ( aMetrics.bText )
+ {
+ if( rEntry.mnFlags & ListBoxEntryFlags::MultiLine )
+ {
+ // multiline case
+ Size aCurSize( PixelToLogic( GetSizePixel() ) );
+ // set the current size to a large number
+ // GetTextRect should shrink it to the actual size
+ aCurSize.setHeight( 0x7fffff );
+ tools::Rectangle aTextRect( Point( 0, 0 ), aCurSize );
+ aTextRect = GetTextRect( aTextRect, rEntry.maStr, DrawTextFlags::WordBreak | DrawTextFlags::MultiLine );
+ aMetrics.nTextWidth = aTextRect.GetWidth();
+ if( aMetrics.nTextWidth > mnMaxTxtWidth )
+ mnMaxTxtWidth = aMetrics.nTextWidth;
+ aMetrics.nEntryWidth = mnMaxTxtWidth;
+ aMetrics.nEntryHeight = aTextRect.GetHeight() + gnBorder;
+ }
+ else
+ {
+ // normal single line case
+ const SalLayoutGlyphs* pGlyphs = rEntry.GetTextGlyphs(GetOutDev());
+ aMetrics.nTextWidth
+ = static_cast<sal_uInt16>(GetTextWidth(rEntry.maStr, 0, -1, nullptr, pGlyphs));
+ if( aMetrics.nTextWidth > mnMaxTxtWidth )
+ mnMaxTxtWidth = aMetrics.nTextWidth;
+ aMetrics.nEntryWidth = mnMaxTxtWidth;
+ aMetrics.nEntryHeight = mnTextHeight + gnBorder;
+ }
+ }
+ if ( aMetrics.bImage )
+ {
+ Size aImgSz = rEntry.maImage.GetSizePixel();
+ aMetrics.nImgWidth = static_cast<sal_uInt16>(CalcZoom( aImgSz.Width() ));
+ aMetrics.nImgHeight = static_cast<sal_uInt16>(CalcZoom( aImgSz.Height() ));
+
+ if( aMetrics.nImgWidth > mnMaxImgWidth )
+ mnMaxImgWidth = aMetrics.nImgWidth;
+ if( aMetrics.nImgHeight > mnMaxImgHeight )
+ mnMaxImgHeight = aMetrics.nImgHeight;
+
+ mnMaxImgTxtWidth = std::max( mnMaxImgTxtWidth, aMetrics.nTextWidth );
+ aMetrics.nEntryHeight = std::max( aMetrics.nImgHeight, aMetrics.nEntryHeight );
+
+ }
+
+ bool bIsUserDrawEnabled = IsUserDrawEnabled();
+ if (bIsUserDrawEnabled || aMetrics.bImage)
+ {
+ aMetrics.nEntryWidth = std::max( aMetrics.nImgWidth, maUserItemSize.Width() );
+ if (!bIsUserDrawEnabled && aMetrics.bText)
+ aMetrics.nEntryWidth += aMetrics.nTextWidth + IMG_TXT_DISTANCE;
+ aMetrics.nEntryHeight = std::max( std::max( mnMaxImgHeight, maUserItemSize.Height() ) + 2,
+ aMetrics.nEntryHeight );
+ }
+
+ if (!aMetrics.bText && !aMetrics.bImage && !bIsUserDrawEnabled)
+ {
+ // entries which have no (aka an empty) text, and no image,
+ // and are not user-drawn, should be shown nonetheless
+ aMetrics.nEntryHeight = mnTextHeight + gnBorder;
+ }
+
+ if ( aMetrics.nEntryWidth > mnMaxWidth )
+ mnMaxWidth = aMetrics.nEntryWidth;
+ if ( aMetrics.nEntryHeight > mnMaxHeight )
+ mnMaxHeight = aMetrics.nEntryHeight;
+
+ rEntry.mnHeight = aMetrics.nEntryHeight;
+}
+
+void ImplListBoxWindow::ImplCallSelect()
+{
+ if ( !IsTravelSelect() && GetEntryList().GetMaxMRUCount() )
+ {
+ // Insert the selected entry as MRU, if not already first MRU
+ sal_Int32 nSelected = GetEntryList().GetSelectedEntryPos( 0 );
+ sal_Int32 nMRUCount = GetEntryList().GetMRUCount();
+ OUString aSelected = GetEntryList().GetEntryText( nSelected );
+ sal_Int32 nFirstMatchingEntryPos = GetEntryList().FindEntry( aSelected, true );
+ if ( nFirstMatchingEntryPos || !nMRUCount )
+ {
+ bool bSelectNewEntry = false;
+ if ( nFirstMatchingEntryPos < nMRUCount )
+ {
+ RemoveEntry( nFirstMatchingEntryPos );
+ nMRUCount--;
+ if ( nFirstMatchingEntryPos == nSelected )
+ bSelectNewEntry = true;
+ }
+ else if ( nMRUCount == GetEntryList().GetMaxMRUCount() )
+ {
+ RemoveEntry( nMRUCount - 1 );
+ nMRUCount--;
+ }
+
+ ImplClearLayoutData();
+
+ ImplEntryType* pNewEntry = new ImplEntryType( aSelected );
+ pNewEntry->mbIsSelected = bSelectNewEntry;
+ GetEntryList().InsertEntry( 0, pNewEntry, false );
+ ImplUpdateEntryMetrics( *pNewEntry );
+ GetEntryList().SetMRUCount( ++nMRUCount );
+ SetSeparatorPos( nMRUCount ? nMRUCount-1 : 0 );
+ maMRUChangedHdl.Call( nullptr );
+ }
+ }
+
+ maSelectHdl.Call( nullptr );
+ mbSelectionChanged = false;
+}
+
+sal_Int32 ImplListBoxWindow::InsertEntry(sal_Int32 nPos, ImplEntryType* pNewEntry, bool bSort)
+{
+ assert(nPos >= 0);
+ assert(maEntryList.GetEntryCount() < LISTBOX_MAX_ENTRIES);
+
+ ImplClearLayoutData();
+ sal_Int32 nNewPos = maEntryList.InsertEntry( nPos, pNewEntry, bSort );
+
+ if( GetStyle() & WB_WORDBREAK )
+ pNewEntry->mnFlags |= ListBoxEntryFlags::MultiLine;
+
+ ImplUpdateEntryMetrics( *pNewEntry );
+ return nNewPos;
+}
+
+sal_Int32 ImplListBoxWindow::InsertEntry( sal_Int32 nPos, ImplEntryType* pNewEntry )
+{
+ return InsertEntry(nPos, pNewEntry, mbSort);
+}
+
+void ImplListBoxWindow::RemoveEntry( sal_Int32 nPos )
+{
+ ImplClearLayoutData();
+ maEntryList.RemoveEntry( nPos );
+ if( mnCurrentPos >= maEntryList.GetEntryCount() )
+ mnCurrentPos = LISTBOX_ENTRY_NOTFOUND;
+ ImplCalcMetrics();
+}
+
+void ImplListBoxWindow::SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags )
+{
+ maEntryList.SetEntryFlags( nPos, nFlags );
+ ImplEntryType* pEntry = maEntryList.GetMutableEntryPtr( nPos );
+ if( pEntry )
+ ImplUpdateEntryMetrics( *pEntry );
+}
+
+void ImplListBoxWindow::ImplShowFocusRect()
+{
+ if ( mbHasFocusRect )
+ HideFocus();
+ ShowFocus( maFocusRect );
+ mbHasFocusRect = true;
+}
+
+void ImplListBoxWindow::ImplHideFocusRect()
+{
+ if ( mbHasFocusRect )
+ {
+ HideFocus();
+ mbHasFocusRect = false;
+ }
+}
+
+sal_Int32 ImplListBoxWindow::GetEntryPosForPoint( const Point& rPoint ) const
+{
+ tools::Long nY = gnBorder;
+
+ sal_Int32 nSelect = mnTop;
+ const ImplEntryType* pEntry = maEntryList.GetEntryPtr( nSelect );
+ while (pEntry)
+ {
+ tools::Long nEntryHeight = pEntry->getHeightWithMargin();
+ if (rPoint.Y() <= nEntryHeight + nY)
+ break;
+ nY += nEntryHeight;
+ pEntry = maEntryList.GetEntryPtr( ++nSelect );
+ }
+ if( pEntry == nullptr )
+ nSelect = LISTBOX_ENTRY_NOTFOUND;
+
+ return nSelect;
+}
+
+bool ImplListBoxWindow::IsVisible( sal_Int32 i_nEntry ) const
+{
+ bool bRet = false;
+
+ if( i_nEntry >= mnTop )
+ {
+ if( maEntryList.GetAddedHeight( i_nEntry, mnTop ) <
+ PixelToLogic( GetSizePixel() ).Height() )
+ {
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+tools::Long ImplListBoxWindow::GetEntryHeightWithMargin() const
+{
+ tools::Long nMargin = ImplGetSVData()->maNWFData.mnListBoxEntryMargin;
+ return mnMaxHeight + nMargin;
+}
+
+sal_Int32 ImplListBoxWindow::GetLastVisibleEntry() const
+{
+ sal_Int32 nPos = mnTop;
+ tools::Long nWindowHeight = GetSizePixel().Height();
+ sal_Int32 nCount = maEntryList.GetEntryCount();
+ tools::Long nDiff;
+ for( nDiff = 0; nDiff < nWindowHeight && nPos < nCount; nDiff = maEntryList.GetAddedHeight( nPos, mnTop ) )
+ nPos++;
+
+ if( nDiff > nWindowHeight && nPos > mnTop )
+ nPos--;
+
+ if( nPos >= nCount )
+ nPos = nCount-1;
+
+ return nPos;
+}
+
+void ImplListBoxWindow::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ mbMouseMoveSelect = false; // only till the first MouseButtonDown
+ maQuickSelectionEngine.Reset();
+
+ if ( !IsReadOnly() )
+ {
+ if( rMEvt.GetClicks() == 1 )
+ {
+ sal_Int32 nSelect = GetEntryPosForPoint( rMEvt.GetPosPixel() );
+ if( nSelect != LISTBOX_ENTRY_NOTFOUND )
+ {
+ if ( !mbMulti && GetEntryList().GetSelectedEntryCount() )
+ mnTrackingSaveSelection = GetEntryList().GetSelectedEntryPos( 0 );
+ else
+ mnTrackingSaveSelection = LISTBOX_ENTRY_NOTFOUND;
+
+ mnCurrentPos = nSelect;
+ mbTrackingSelect = true;
+ bool bCurPosChange = (mnCurrentPos != nSelect);
+ (void)SelectEntries( nSelect, LET_MBDOWN, rMEvt.IsShift(), rMEvt.IsMod1() ,bCurPosChange);
+ mbTrackingSelect = false;
+ if ( mbGrabFocus )
+ GrabFocus();
+
+ StartTracking( StartTrackingFlags::ScrollRepeat );
+ }
+ }
+ if( rMEvt.GetClicks() == 2 )
+ {
+ maDoubleClickHdl.Call( this );
+ }
+ }
+ else // if ( mbGrabFocus )
+ {
+ GrabFocus();
+ }
+}
+
+void ImplListBoxWindow::MouseMove( const MouseEvent& rMEvt )
+{
+ if (rMEvt.IsLeaveWindow() || mbMulti || !IsMouseMoveSelect() || !maEntryList.GetEntryCount())
+ return;
+
+ tools::Rectangle aRect( Point(), GetOutputSizePixel() );
+ if( !aRect.Contains( rMEvt.GetPosPixel() ) )
+ return;
+
+ if ( IsMouseMoveSelect() )
+ {
+ sal_Int32 nSelect = GetEntryPosForPoint( rMEvt.GetPosPixel() );
+ if( nSelect == LISTBOX_ENTRY_NOTFOUND )
+ nSelect = maEntryList.GetEntryCount() - 1;
+ nSelect = std::min( nSelect, GetLastVisibleEntry() );
+ nSelect = std::min( nSelect, static_cast<sal_Int32>( maEntryList.GetEntryCount() - 1 ) );
+ // Select only visible Entries with MouseMove, otherwise Tracking...
+ if ( IsVisible( nSelect ) &&
+ maEntryList.IsEntrySelectable( nSelect ) &&
+ ( ( nSelect != mnCurrentPos ) || !GetEntryList().GetSelectedEntryCount() || ( nSelect != GetEntryList().GetSelectedEntryPos( 0 ) ) ) )
+ {
+ mbTrackingSelect = true;
+ if ( SelectEntries( nSelect, LET_TRACKING ) )
+ {
+ // When list box selection change by mouse move, notify
+ // VclEventId::ListboxSelect vcl event.
+ maListItemSelectHdl.Call(nullptr);
+ }
+ mbTrackingSelect = false;
+ }
+ }
+
+ // if the DD button was pressed and someone moved into the ListBox
+ // with the mouse button pressed...
+ if ( rMEvt.IsLeft() && !rMEvt.IsSynthetic() )
+ {
+ if ( !mbMulti && GetEntryList().GetSelectedEntryCount() )
+ mnTrackingSaveSelection = GetEntryList().GetSelectedEntryPos( 0 );
+ else
+ mnTrackingSaveSelection = LISTBOX_ENTRY_NOTFOUND;
+
+ StartTracking( StartTrackingFlags::ScrollRepeat );
+ }
+}
+
+void ImplListBoxWindow::DeselectAll()
+{
+ while ( GetEntryList().GetSelectedEntryCount() )
+ {
+ sal_Int32 nS = GetEntryList().GetSelectedEntryPos( 0 );
+ SelectEntry( nS, false );
+ }
+}
+
+void ImplListBoxWindow::SelectEntry( sal_Int32 nPos, bool bSelect )
+{
+ if( (maEntryList.IsEntryPosSelected( nPos ) == bSelect) || !maEntryList.IsEntrySelectable( nPos ) )
+ return;
+
+ ImplHideFocusRect();
+ if( bSelect )
+ {
+ if( !mbMulti )
+ {
+ // deselect the selected entry
+ sal_Int32 nDeselect = GetEntryList().GetSelectedEntryPos( 0 );
+ if( nDeselect != LISTBOX_ENTRY_NOTFOUND )
+ {
+ //SelectEntryPos( nDeselect, false );
+ GetEntryList().SelectEntry( nDeselect, false );
+ if (IsUpdateMode() && IsReallyVisible())
+ Invalidate();
+ }
+ }
+ maEntryList.SelectEntry( nPos, true );
+ mnCurrentPos = nPos;
+ if ( ( nPos != LISTBOX_ENTRY_NOTFOUND ) && IsUpdateMode() )
+ {
+ Invalidate();
+ if ( !IsVisible( nPos ) )
+ {
+ ImplClearLayoutData();
+ sal_Int32 nVisibleEntries = GetLastVisibleEntry()-mnTop;
+ if ( !nVisibleEntries || !IsReallyVisible() || ( nPos < GetTopEntry() ) )
+ {
+ Resize();
+ ShowProminentEntry( nPos );
+ }
+ else
+ {
+ ShowProminentEntry( nPos );
+ }
+ }
+ }
+ }
+ else
+ {
+ maEntryList.SelectEntry( nPos, false );
+ Invalidate();
+ }
+ mbSelectionChanged = true;
+}
+
+bool ImplListBoxWindow::SelectEntries( sal_Int32 nSelect, LB_EVENT_TYPE eLET, bool bShift, bool bCtrl, bool bSelectPosChange /*=FALSE*/ )
+{
+ bool bSelectionChanged = false;
+
+ if( IsEnabled() && maEntryList.IsEntrySelectable( nSelect ) )
+ {
+ bool bFocusChanged = false;
+
+ // here (Single-ListBox) only one entry can be deselected
+ if( !mbMulti )
+ {
+ sal_Int32 nDeselect = maEntryList.GetSelectedEntryPos( 0 );
+ if( nSelect != nDeselect )
+ {
+ SelectEntry( nSelect, true );
+ maEntryList.SetLastSelected( nSelect );
+ bFocusChanged = true;
+ bSelectionChanged = true;
+ }
+ }
+ // MultiListBox without Modifier
+ else if( mbSimpleMode && !bCtrl && !bShift )
+ {
+ sal_Int32 nEntryCount = maEntryList.GetEntryCount();
+ for ( sal_Int32 nPos = 0; nPos < nEntryCount; nPos++ )
+ {
+ bool bSelect = nPos == nSelect;
+ if ( maEntryList.IsEntryPosSelected( nPos ) != bSelect )
+ {
+ SelectEntry( nPos, bSelect );
+ bFocusChanged = true;
+ bSelectionChanged = true;
+ }
+ }
+ maEntryList.SetLastSelected( nSelect );
+ maEntryList.SetSelectionAnchor( nSelect );
+ }
+ // MultiListBox only with CTRL/SHIFT or not in SimpleMode
+ else if( ( !mbSimpleMode /* && !bShift */ ) || ( mbSimpleMode && ( bCtrl || bShift ) ) )
+ {
+ // Space for selection change
+ if( !bShift && ( ( eLET == LET_KEYSPACE ) || ( eLET == LET_MBDOWN ) ) )
+ {
+ bool bSelect = !maEntryList.IsEntryPosSelected( nSelect );
+ SelectEntry( nSelect, bSelect );
+ maEntryList.SetLastSelected( nSelect );
+ maEntryList.SetSelectionAnchor( nSelect );
+ if ( !maEntryList.IsEntryPosSelected( nSelect ) )
+ maEntryList.SetSelectionAnchor( LISTBOX_ENTRY_NOTFOUND );
+ bFocusChanged = true;
+ bSelectionChanged = true;
+ }
+ else if( ( ( eLET == LET_TRACKING ) && ( nSelect != mnCurrentPos ) ) ||
+ ( bShift && ( ( eLET == LET_KEYMOVE ) || ( eLET == LET_MBDOWN ) ) ) )
+ {
+ mnCurrentPos = nSelect;
+ bFocusChanged = true;
+
+ sal_Int32 nAnchor = maEntryList.GetSelectionAnchor();
+ if( ( nAnchor == LISTBOX_ENTRY_NOTFOUND ) && maEntryList.GetSelectedEntryCount() )
+ {
+ nAnchor = maEntryList.GetSelectedEntryPos( maEntryList.GetSelectedEntryCount() - 1 );
+ }
+ if( nAnchor != LISTBOX_ENTRY_NOTFOUND )
+ {
+ // All entries from Anchor to nSelect have to be selected
+ sal_Int32 nStart = std::min( nSelect, nAnchor );
+ sal_Int32 nEnd = std::max( nSelect, nAnchor );
+ for ( sal_Int32 n = nStart; n <= nEnd; n++ )
+ {
+ if ( !maEntryList.IsEntryPosSelected( n ) )
+ {
+ SelectEntry( n, true );
+ bSelectionChanged = true;
+ }
+ }
+
+ // if appropriate some more has to be deselected...
+ sal_Int32 nLast = maEntryList.GetLastSelected();
+ if ( nLast != LISTBOX_ENTRY_NOTFOUND )
+ {
+ if ( ( nLast > nSelect ) && ( nLast > nAnchor ) )
+ {
+ for ( sal_Int32 n = nSelect+1; n <= nLast; n++ )
+ {
+ if ( maEntryList.IsEntryPosSelected( n ) )
+ {
+ SelectEntry( n, false );
+ bSelectionChanged = true;
+ }
+ }
+ }
+ else if ( ( nLast < nSelect ) && ( nLast < nAnchor ) )
+ {
+ for ( sal_Int32 n = nLast; n < nSelect; n++ )
+ {
+ if ( maEntryList.IsEntryPosSelected( n ) )
+ {
+ SelectEntry( n, false );
+ bSelectionChanged = true;
+ }
+ }
+ }
+ }
+ maEntryList.SetLastSelected( nSelect );
+ }
+ }
+ else if( eLET != LET_TRACKING )
+ {
+ ImplHideFocusRect();
+ Invalidate();
+ bFocusChanged = true;
+ }
+ }
+ else if( bShift )
+ {
+ bFocusChanged = true;
+ }
+
+ if( bSelectionChanged )
+ mbSelectionChanged = true;
+
+ if( bFocusChanged )
+ {
+ tools::Long nHeightDiff = maEntryList.GetAddedHeight( nSelect, mnTop );
+ maFocusRect.SetPos( Point( 0, nHeightDiff ) );
+ Size aSz( maFocusRect.GetWidth(),
+ maEntryList.GetEntryHeight( nSelect ) );
+ maFocusRect.SetSize( aSz );
+ if( HasFocus() )
+ ImplShowFocusRect();
+ if (bSelectPosChange)
+ {
+ maFocusHdl.Call(nSelect);
+ }
+ }
+ ImplClearLayoutData();
+ }
+ return bSelectionChanged;
+}
+
+void ImplListBoxWindow::Tracking( const TrackingEvent& rTEvt )
+{
+ tools::Rectangle aRect( Point(), GetOutputSizePixel() );
+ bool bInside = aRect.Contains( rTEvt.GetMouseEvent().GetPosPixel() );
+
+ if( rTEvt.IsTrackingCanceled() || rTEvt.IsTrackingEnded() ) // MouseButtonUp
+ {
+ if ( bInside && !rTEvt.IsTrackingCanceled() )
+ {
+ mnSelectModifier = rTEvt.GetMouseEvent().GetModifier();
+ ImplCallSelect();
+ }
+ else
+ {
+ maCancelHdl.Call( nullptr );
+ if ( !mbMulti )
+ {
+ mbTrackingSelect = true;
+ SelectEntry( mnTrackingSaveSelection, true );
+ mbTrackingSelect = false;
+ if ( mnTrackingSaveSelection != LISTBOX_ENTRY_NOTFOUND )
+ {
+ tools::Long nHeightDiff = maEntryList.GetAddedHeight( mnCurrentPos, mnTop );
+ maFocusRect.SetPos( Point( 0, nHeightDiff ) );
+ Size aSz( maFocusRect.GetWidth(),
+ maEntryList.GetEntryHeight( mnCurrentPos ) );
+ maFocusRect.SetSize( aSz );
+ ImplShowFocusRect();
+ }
+ }
+ }
+
+ mbTrack = false;
+ }
+ else
+ {
+ bool bTrackOrQuickClick = mbTrack;
+ if( !mbTrack )
+ {
+ if ( bInside )
+ {
+ mbTrack = true;
+ }
+
+ // this case only happens, if the mouse button is pressed very briefly
+ if( rTEvt.IsTrackingEnded() && mbTrack )
+ {
+ bTrackOrQuickClick = true;
+ mbTrack = false;
+ }
+ }
+
+ if( bTrackOrQuickClick )
+ {
+ MouseEvent aMEvt = rTEvt.GetMouseEvent();
+ Point aPt( aMEvt.GetPosPixel() );
+ bool bShift = aMEvt.IsShift();
+ bool bCtrl = aMEvt.IsMod1();
+
+ sal_Int32 nSelect = LISTBOX_ENTRY_NOTFOUND;
+ if( aPt.Y() < 0 )
+ {
+ if ( mnCurrentPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = mnCurrentPos ? ( mnCurrentPos - 1 ) : 0;
+ if( nSelect < mnTop )
+ SetTopEntry( mnTop-1 );
+ }
+ }
+ else if( aPt.Y() > GetOutputSizePixel().Height() )
+ {
+ if ( mnCurrentPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = std::min( static_cast<sal_Int32>(mnCurrentPos+1), static_cast<sal_Int32>(maEntryList.GetEntryCount()-1) );
+ if( nSelect >= GetLastVisibleEntry() )
+ SetTopEntry( mnTop+1 );
+ }
+ }
+ else
+ {
+ nSelect = static_cast<sal_Int32>( ( aPt.Y() + gnBorder ) / mnMaxHeight ) + mnTop;
+ nSelect = std::min( nSelect, GetLastVisibleEntry() );
+ nSelect = std::min( nSelect, static_cast<sal_Int32>( maEntryList.GetEntryCount() - 1 ) );
+ }
+
+ if ( bInside )
+ {
+ if ( ( nSelect != mnCurrentPos ) || !GetEntryList().GetSelectedEntryCount() )
+ {
+ mbTrackingSelect = true;
+ SelectEntries(nSelect, LET_TRACKING, bShift, bCtrl);
+ mbTrackingSelect = false;
+ }
+ }
+ else
+ {
+ if ( !mbMulti && GetEntryList().GetSelectedEntryCount() )
+ {
+ mbTrackingSelect = true;
+ SelectEntry( GetEntryList().GetSelectedEntryPos( 0 ), false );
+ mbTrackingSelect = false;
+ }
+ }
+ mnCurrentPos = nSelect;
+ if ( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
+ {
+ ImplHideFocusRect();
+ }
+ else
+ {
+ tools::Long nHeightDiff = maEntryList.GetAddedHeight( mnCurrentPos, mnTop );
+ maFocusRect.SetPos( Point( 0, nHeightDiff ) );
+ Size aSz( maFocusRect.GetWidth(), maEntryList.GetEntryHeight( mnCurrentPos ) );
+ maFocusRect.SetSize( aSz );
+ ImplShowFocusRect();
+ }
+ }
+ }
+}
+
+void ImplListBoxWindow::KeyInput( const KeyEvent& rKEvt )
+{
+ if( !ProcessKeyInput( rKEvt ) )
+ Control::KeyInput( rKEvt );
+}
+
+bool ImplListBoxWindow::ProcessKeyInput( const KeyEvent& rKEvt )
+{
+ // entry to be selected
+ sal_Int32 nSelect = LISTBOX_ENTRY_NOTFOUND;
+ LB_EVENT_TYPE eLET = LET_KEYMOVE;
+
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+
+ bool bShift = aKeyCode.IsShift();
+ bool bCtrl = aKeyCode.IsMod1() || aKeyCode.IsMod3();
+ bool bMod2 = aKeyCode.IsMod2();
+ bool bDone = false;
+ bool bHandleKey = false;
+
+ switch( aKeyCode.GetCode() )
+ {
+ case KEY_UP:
+ {
+ if ( IsReadOnly() )
+ {
+ if ( GetTopEntry() )
+ SetTopEntry( GetTopEntry()-1 );
+ }
+ else if ( !bMod2 )
+ {
+ if( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = maEntryList.FindFirstSelectable( 0 );
+ }
+ else if ( mnCurrentPos )
+ {
+ // search first selectable above the current position
+ nSelect = maEntryList.FindFirstSelectable( mnCurrentPos - 1, false );
+ }
+
+ if( ( nSelect != LISTBOX_ENTRY_NOTFOUND ) && ( nSelect < mnTop ) )
+ SetTopEntry( mnTop-1 );
+
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_DOWN:
+ {
+ if ( IsReadOnly() )
+ {
+ SetTopEntry( GetTopEntry()+1 );
+ }
+ else if ( !bMod2 )
+ {
+ if( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = maEntryList.FindFirstSelectable( 0 );
+ }
+ else if ( (mnCurrentPos+1) < maEntryList.GetEntryCount() )
+ {
+ // search first selectable below the current position
+ nSelect = maEntryList.FindFirstSelectable( mnCurrentPos + 1 );
+ }
+
+ if( ( nSelect != LISTBOX_ENTRY_NOTFOUND ) && ( nSelect >= GetLastVisibleEntry() ) )
+ SetTopEntry( mnTop+1 );
+
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_PAGEUP:
+ {
+ if ( IsReadOnly() )
+ {
+ sal_Int32 nCurVis = GetLastVisibleEntry() - mnTop +1;
+ SetTopEntry( ( mnTop > nCurVis ) ?
+ (mnTop-nCurVis) : 0 );
+ }
+ else if ( !bCtrl && !bMod2 )
+ {
+ if( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = maEntryList.FindFirstSelectable( 0 );
+ }
+ else if ( mnCurrentPos )
+ {
+ if( mnCurrentPos == mnTop )
+ {
+ sal_Int32 nCurVis = GetLastVisibleEntry() - mnTop +1;
+ SetTopEntry( ( mnTop > nCurVis ) ? ( mnTop-nCurVis+1 ) : 0 );
+ }
+
+ // find first selectable starting from mnTop looking forward
+ nSelect = maEntryList.FindFirstSelectable( mnTop );
+ }
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_PAGEDOWN:
+ {
+ if ( IsReadOnly() )
+ {
+ SetTopEntry( GetLastVisibleEntry() );
+ }
+ else if ( !bCtrl && !bMod2 )
+ {
+ if( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = maEntryList.FindFirstSelectable( 0 );
+ }
+ else if ( (mnCurrentPos+1) < maEntryList.GetEntryCount() )
+ {
+ sal_Int32 nCount = maEntryList.GetEntryCount();
+ sal_Int32 nCurVis = GetLastVisibleEntry() - mnTop;
+ sal_Int32 nTmp = std::min( nCurVis, nCount );
+ nTmp += mnTop - 1;
+ if( mnCurrentPos == nTmp && mnCurrentPos != nCount - 1 )
+ {
+ tools::Long nTmp2 = std::min( static_cast<tools::Long>(nCount-nCurVis), static_cast<tools::Long>(static_cast<tools::Long>(mnTop)+static_cast<tools::Long>(nCurVis)-1) );
+ nTmp2 = std::max( tools::Long(0) , nTmp2 );
+ nTmp = static_cast<sal_Int32>(nTmp2+(nCurVis-1) );
+ SetTopEntry( static_cast<sal_Int32>(nTmp2) );
+ }
+ // find first selectable starting from nTmp looking backwards
+ nSelect = maEntryList.FindFirstSelectable( nTmp, false );
+ }
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_HOME:
+ {
+ if ( IsReadOnly() )
+ {
+ SetTopEntry( 0 );
+ }
+ else if ( !bCtrl && !bMod2 && mnCurrentPos )
+ {
+ nSelect = maEntryList.FindFirstSelectable( maEntryList.GetEntryCount() ? 0 : LISTBOX_ENTRY_NOTFOUND );
+ if( mnTop != 0 )
+ SetTopEntry( 0 );
+
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_END:
+ {
+ if ( IsReadOnly() )
+ {
+ SetTopEntry( 0xFFFF );
+ }
+ else if ( !bCtrl && !bMod2 )
+ {
+ if( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND )
+ {
+ nSelect = maEntryList.FindFirstSelectable( 0 );
+ }
+ else if ( (mnCurrentPos+1) < maEntryList.GetEntryCount() )
+ {
+ sal_Int32 nCount = maEntryList.GetEntryCount();
+ nSelect = maEntryList.FindFirstSelectable( nCount - 1, false );
+ sal_Int32 nCurVis = GetLastVisibleEntry() - mnTop + 1;
+ if( nCount > nCurVis )
+ SetTopEntry( nCount - nCurVis );
+ }
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_LEFT:
+ {
+ if ( !bCtrl && !bMod2 )
+ {
+ ScrollHorz( -HORZ_SCROLL );
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_RIGHT:
+ {
+ if ( !bCtrl && !bMod2 )
+ {
+ ScrollHorz( HORZ_SCROLL );
+ bDone = true;
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_RETURN:
+ {
+ if ( !bMod2 && !IsReadOnly() )
+ {
+ mnSelectModifier = rKEvt.GetKeyCode().GetModifier();
+ ImplCallSelect();
+ bDone = false; // do not catch RETURN
+ }
+ maQuickSelectionEngine.Reset();
+ }
+ break;
+
+ case KEY_SPACE:
+ {
+ if ( !bMod2 && !IsReadOnly() )
+ {
+ if( mbMulti && ( !mbSimpleMode || ( mbSimpleMode && bCtrl && !bShift ) ) )
+ {
+ nSelect = mnCurrentPos;
+ eLET = LET_KEYSPACE;
+ }
+ bDone = true;
+ }
+ bHandleKey = true;
+ }
+ break;
+
+ case KEY_A:
+ {
+ if( bCtrl && mbMulti )
+ {
+ // paint only once
+ bool bUpdates = IsUpdateMode();
+ SetUpdateMode( false );
+
+ sal_Int32 nEntryCount = maEntryList.GetEntryCount();
+ for( sal_Int32 i = 0; i < nEntryCount; i++ )
+ SelectEntry( i, true );
+
+ // tdf#97066 - Update selected items
+ ImplCallSelect();
+
+ // restore update mode
+ SetUpdateMode( bUpdates );
+ Invalidate();
+
+ maQuickSelectionEngine.Reset();
+
+ bDone = true;
+ }
+ else
+ {
+ bHandleKey = true;
+ }
+ }
+ break;
+
+ default:
+ bHandleKey = true;
+ break;
+ }
+ if (bHandleKey && !IsReadOnly())
+ {
+ bDone = maQuickSelectionEngine.HandleKeyEvent( rKEvt );
+ }
+
+ if ( ( nSelect != LISTBOX_ENTRY_NOTFOUND )
+ && ( ( !maEntryList.IsEntryPosSelected( nSelect ) )
+ || ( eLET == LET_KEYSPACE )
+ )
+ )
+ {
+ SAL_WARN_IF( maEntryList.IsEntryPosSelected( nSelect ) && !mbMulti, "vcl", "ImplListBox: Selecting same Entry" );
+ sal_Int32 nCount = maEntryList.GetEntryCount();
+ if (nSelect >= nCount)
+ nSelect = nCount ? nCount-1 : LISTBOX_ENTRY_NOTFOUND;
+ bool bCurPosChange = (mnCurrentPos != nSelect);
+ mnCurrentPos = nSelect;
+ if(SelectEntries( nSelect, eLET, bShift, bCtrl, bCurPosChange))
+ {
+ // tdf#129043 Correctly deliver events when changing values with arrow keys in combobox
+ if (mbIsDropdown && IsReallyVisible())
+ mbTravelSelect = true;
+ mnSelectModifier = rKEvt.GetKeyCode().GetModifier();
+ ImplCallSelect();
+ mbTravelSelect = false;
+ }
+ }
+
+ return bDone;
+}
+
+namespace
+{
+ vcl::StringEntryIdentifier lcl_getEntry( const ImplEntryList& _rList, sal_Int32 _nPos, OUString& _out_entryText )
+ {
+ OSL_PRECOND( ( _nPos != LISTBOX_ENTRY_NOTFOUND ), "lcl_getEntry: invalid position!" );
+ sal_Int32 nEntryCount( _rList.GetEntryCount() );
+ if ( _nPos >= nEntryCount )
+ _nPos = 0;
+ _out_entryText = _rList.GetEntryText( _nPos );
+
+ // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based
+ // => normalize
+ return reinterpret_cast< vcl::StringEntryIdentifier >( _nPos + 1 );
+ }
+
+ sal_Int32 lcl_getEntryPos( vcl::StringEntryIdentifier _entry )
+ {
+ // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL
+ return static_cast< sal_Int32 >( reinterpret_cast< sal_Int64 >( _entry ) ) - 1;
+ }
+}
+
+vcl::StringEntryIdentifier ImplListBoxWindow::CurrentEntry( OUString& _out_entryText ) const
+{
+ return lcl_getEntry( GetEntryList(), ( mnCurrentPos == LISTBOX_ENTRY_NOTFOUND ) ? 0 : mnCurrentPos, _out_entryText );
+}
+
+vcl::StringEntryIdentifier ImplListBoxWindow::NextEntry( vcl::StringEntryIdentifier _currentEntry, OUString& _out_entryText ) const
+{
+ sal_Int32 nNextPos = lcl_getEntryPos( _currentEntry ) + 1;
+ return lcl_getEntry( GetEntryList(), nNextPos, _out_entryText );
+}
+
+void ImplListBoxWindow::SelectEntry( vcl::StringEntryIdentifier _entry )
+{
+ sal_Int32 nSelect = lcl_getEntryPos( _entry );
+ if ( maEntryList.IsEntryPosSelected( nSelect ) )
+ {
+ // ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted
+ // to select the given entry by typing its starting letters. No need to act.
+ return;
+ }
+
+ // normalize
+ OSL_ENSURE( nSelect < maEntryList.GetEntryCount(), "ImplListBoxWindow::SelectEntry: how that?" );
+ sal_Int32 nCount = maEntryList.GetEntryCount();
+ if (nSelect >= nCount)
+ nSelect = nCount ? nCount-1 : LISTBOX_ENTRY_NOTFOUND;
+
+ // make visible
+ ShowProminentEntry( nSelect );
+
+ // actually select
+ mnCurrentPos = nSelect;
+ if ( SelectEntries( nSelect, LET_KEYMOVE ) )
+ {
+ mbTravelSelect = true;
+ mnSelectModifier = 0;
+ ImplCallSelect();
+ mbTravelSelect = false;
+ }
+}
+
+void ImplListBoxWindow::ImplPaint(vcl::RenderContext& rRenderContext, sal_Int32 nPos)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ const ImplEntryType* pEntry = maEntryList.GetEntryPtr( nPos );
+ if (!pEntry)
+ return;
+
+ tools::Long nWidth = GetOutputSizePixel().Width();
+ tools::Long nY = maEntryList.GetAddedHeight(nPos, mnTop);
+ tools::Rectangle aRect(Point(0, nY), Size(nWidth, pEntry->getHeightWithMargin()));
+
+ bool bSelected = maEntryList.IsEntryPosSelected(nPos);
+ if (bSelected)
+ {
+ rRenderContext.SetTextColor(!IsEnabled() ? rStyleSettings.GetDisableColor() : rStyleSettings.GetListBoxWindowHighlightTextColor());
+ rRenderContext.SetFillColor(rStyleSettings.GetListBoxWindowHighlightColor());
+ rRenderContext.SetLineColor();
+ rRenderContext.DrawRect(aRect);
+ }
+ else
+ {
+ ApplySettings(rRenderContext);
+ if (!IsEnabled())
+ rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
+ }
+ rRenderContext.SetTextFillColor();
+
+ if (IsUserDrawEnabled())
+ {
+ mbInUserDraw = true;
+ mnUserDrawEntry = nPos;
+ aRect.AdjustLeft( -mnLeft );
+ if (nPos < GetEntryList().GetMRUCount())
+ nPos = GetEntryList().FindEntry(GetEntryList().GetEntryText(nPos));
+ nPos = nPos - GetEntryList().GetMRUCount();
+
+ UserDrawEvent aUDEvt(&rRenderContext, aRect, nPos, bSelected);
+ maUserDrawHdl.Call( &aUDEvt );
+ mbInUserDraw = false;
+ }
+ else
+ {
+ DrawEntry(rRenderContext, nPos, true, true);
+ }
+}
+
+void ImplListBoxWindow::DrawEntry(vcl::RenderContext& rRenderContext, sal_Int32 nPos, bool bDrawImage, bool bDrawText)
+{
+ const ImplEntryType* pEntry = maEntryList.GetEntryPtr(nPos);
+ if (!pEntry)
+ return;
+
+ tools::Long nEntryHeight = pEntry->getHeightWithMargin();
+
+ // when changing this function don't forget to adjust ImplWin::DrawEntry()
+
+ if (mbInUserDraw)
+ nPos = mnUserDrawEntry; // real entry, not the matching entry from MRU
+
+ tools::Long nY = maEntryList.GetAddedHeight(nPos, mnTop);
+
+ if (bDrawImage && maEntryList.HasImages())
+ {
+ Image aImage = maEntryList.GetEntryImage(nPos);
+ if (!!aImage)
+ {
+ Size aImgSz = aImage.GetSizePixel();
+ Point aPtImg(gnBorder - mnLeft, nY + ((nEntryHeight - aImgSz.Height()) / 2));
+
+ if (!IsZoom())
+ {
+ rRenderContext.DrawImage(aPtImg, aImage);
+ }
+ else
+ {
+ aImgSz.setWidth( CalcZoom(aImgSz.Width()) );
+ aImgSz.setHeight( CalcZoom(aImgSz.Height()) );
+ rRenderContext.DrawImage(aPtImg, aImgSz, aImage);
+ }
+
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ const sal_uInt16 nEdgeBlendingPercent(GetEdgeBlending() ? rStyleSettings.GetEdgeBlending() : 0);
+
+ if (nEdgeBlendingPercent && aImgSz.Width() && aImgSz.Height())
+ {
+ const Color& rTopLeft(rStyleSettings.GetEdgeBlendingTopLeftColor());
+ const Color& rBottomRight(rStyleSettings.GetEdgeBlendingBottomRightColor());
+ const sal_uInt8 nAlpha((nEdgeBlendingPercent * 255) / 100);
+ const BitmapEx aBlendFrame(createBlendFrame(aImgSz, nAlpha, rTopLeft, rBottomRight));
+
+ if (!aBlendFrame.IsEmpty())
+ {
+ rRenderContext.DrawBitmapEx(aPtImg, aBlendFrame);
+ }
+ }
+ }
+ }
+
+ if (bDrawText)
+ {
+ OUString aStr(maEntryList.GetEntryText(nPos));
+ if (!aStr.isEmpty())
+ {
+ tools::Long nMaxWidth = std::max(mnMaxWidth, GetOutputSizePixel().Width() - 2 * gnBorder);
+ // a multiline entry should only be as wide as the window
+ if (pEntry->mnFlags & ListBoxEntryFlags::MultiLine)
+ nMaxWidth = GetOutputSizePixel().Width() - 2 * gnBorder;
+
+ tools::Rectangle aTextRect(Point(gnBorder - mnLeft, nY),
+ Size(nMaxWidth, nEntryHeight));
+
+ if (maEntryList.HasEntryImage(nPos) || IsUserDrawEnabled())
+ {
+ tools::Long nImageWidth = std::max(mnMaxImgWidth, maUserItemSize.Width());
+ aTextRect.AdjustLeft(nImageWidth + IMG_TXT_DISTANCE );
+ }
+
+ DrawTextFlags nDrawStyle = ImplGetTextStyle();
+ if (pEntry->mnFlags & ListBoxEntryFlags::MultiLine)
+ nDrawStyle |= MULTILINE_ENTRY_DRAW_FLAGS;
+ if (pEntry->mnFlags & ListBoxEntryFlags::DrawDisabled)
+ nDrawStyle |= DrawTextFlags::Disable;
+
+ rRenderContext.DrawText(aTextRect, aStr, nDrawStyle);
+ }
+ }
+
+ if ( !maSeparators.empty() && ( isSeparator(nPos) || isSeparator(nPos-1) ) )
+ {
+ Color aOldLineColor(rRenderContext.GetLineColor());
+ rRenderContext.SetLineColor((GetBackground() != COL_LIGHTGRAY) ? COL_LIGHTGRAY : COL_GRAY);
+ Point aStartPos(0, nY);
+ if (isSeparator(nPos))
+ aStartPos.AdjustY(pEntry->getHeightWithMargin() - 1 );
+ Point aEndPos(aStartPos);
+ aEndPos.setX( GetOutputSizePixel().Width() );
+ rRenderContext.DrawLine(aStartPos, aEndPos);
+ rRenderContext.SetLineColor(aOldLineColor);
+ }
+}
+
+void ImplListBoxWindow::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const_cast<ImplListBoxWindow*>(this)->Invalidate(tools::Rectangle(Point(0, 0), GetOutDev()->GetOutputSize()));
+}
+
+void ImplListBoxWindow::ImplDoPaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ sal_Int32 nCount = maEntryList.GetEntryCount();
+
+ bool bShowFocusRect = mbHasFocusRect;
+ if (mbHasFocusRect)
+ ImplHideFocusRect();
+
+ tools::Long nY = 0; // + gnBorder;
+ tools::Long nHeight = GetOutputSizePixel().Height();// - mnMaxHeight + gnBorder;
+
+ for (sal_Int32 i = mnTop; i < nCount && nY < nHeight + mnMaxHeight; i++)
+ {
+ const ImplEntryType* pEntry = maEntryList.GetEntryPtr(i);
+ tools::Long nEntryHeight = pEntry->getHeightWithMargin();
+ if (nY + nEntryHeight >= rRect.Top() &&
+ nY <= rRect.Bottom() + mnMaxHeight)
+ {
+ ImplPaint(rRenderContext, i);
+ }
+ nY += nEntryHeight;
+ }
+
+ tools::Long nHeightDiff = maEntryList.GetAddedHeight(mnCurrentPos, mnTop);
+ maFocusRect.SetPos(Point(0, nHeightDiff));
+ Size aSz(maFocusRect.GetWidth(), maEntryList.GetEntryHeight(mnCurrentPos));
+ maFocusRect.SetSize(aSz);
+ if (HasFocus() && bShowFocusRect)
+ ImplShowFocusRect();
+}
+
+void ImplListBoxWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (SupportsDoubleBuffering())
+ {
+ // This widget is explicitly double-buffered, so avoid partial paints.
+ tools::Rectangle aRect(Point(0, 0), GetOutputSizePixel());
+ ImplDoPaint(rRenderContext, aRect);
+ }
+ else
+ ImplDoPaint(rRenderContext, rRect);
+}
+
+sal_uInt16 ImplListBoxWindow::GetDisplayLineCount() const
+{
+ // FIXME: ListBoxEntryFlags::MultiLine
+
+ const sal_Int32 nCount = maEntryList.GetEntryCount()-mnTop;
+ tools::Long nHeight = GetOutputSizePixel().Height();// - mnMaxHeight + gnBorder;
+ sal_uInt16 nEntries = static_cast< sal_uInt16 >( ( nHeight + mnMaxHeight - 1 ) / mnMaxHeight );
+ if( nEntries > nCount )
+ nEntries = static_cast<sal_uInt16>(nCount);
+
+ return nEntries;
+}
+
+void ImplListBoxWindow::Resize()
+{
+ Control::Resize();
+
+ bool bShowFocusRect = mbHasFocusRect;
+ if ( bShowFocusRect )
+ ImplHideFocusRect();
+
+ if( mnCurrentPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ Size aSz( GetOutputSizePixel().Width(), maEntryList.GetEntryHeight( mnCurrentPos ) );
+ maFocusRect.SetSize( aSz );
+ }
+
+ if ( bShowFocusRect )
+ ImplShowFocusRect();
+
+ ImplClearLayoutData();
+}
+
+void ImplListBoxWindow::GetFocus()
+{
+ sal_Int32 nPos = mnCurrentPos;
+ if ( nPos == LISTBOX_ENTRY_NOTFOUND )
+ nPos = 0;
+ tools::Long nHeightDiff = maEntryList.GetAddedHeight( nPos, mnTop );
+ maFocusRect.SetPos( Point( 0, nHeightDiff ) );
+ Size aSz( maFocusRect.GetWidth(), maEntryList.GetEntryHeight( nPos ) );
+ maFocusRect.SetSize( aSz );
+ ImplShowFocusRect();
+ Control::GetFocus();
+}
+
+void ImplListBoxWindow::LoseFocus()
+{
+ ImplHideFocusRect();
+ Control::LoseFocus();
+}
+
+void ImplListBoxWindow::SetTopEntry( sal_Int32 nTop )
+{
+ if( maEntryList.GetEntryCount() == 0 )
+ return;
+
+ tools::Long nWHeight = PixelToLogic( GetSizePixel() ).Height();
+
+ sal_Int32 nLastEntry = maEntryList.GetEntryCount()-1;
+ if( nTop > nLastEntry )
+ nTop = nLastEntry;
+ const ImplEntryType* pLast = maEntryList.GetEntryPtr( nLastEntry );
+ while( nTop > 0 && maEntryList.GetAddedHeight( nLastEntry, nTop-1 ) + pLast->getHeightWithMargin() <= nWHeight )
+ nTop--;
+
+ if ( nTop == mnTop )
+ return;
+
+ ImplClearLayoutData();
+ tools::Long nDiff = maEntryList.GetAddedHeight( mnTop, nTop );
+ PaintImmediately();
+ ImplHideFocusRect();
+ mnTop = nTop;
+ Scroll( 0, nDiff );
+ PaintImmediately();
+ if( HasFocus() )
+ ImplShowFocusRect();
+ maScrollHdl.Call( this );
+}
+
+void ImplListBoxWindow::ShowProminentEntry( sal_Int32 nEntryPos )
+{
+ sal_Int32 nPos = nEntryPos;
+ auto nWHeight = PixelToLogic( GetSizePixel() ).Height();
+ while( nEntryPos > 0 && maEntryList.GetAddedHeight( nPos+1, nEntryPos ) < nWHeight/2 )
+ nEntryPos--;
+
+ SetTopEntry( nEntryPos );
+}
+
+void ImplListBoxWindow::SetLeftIndent( tools::Long n )
+{
+ ScrollHorz( n - mnLeft );
+}
+
+void ImplListBoxWindow::ScrollHorz( tools::Long n )
+{
+ tools::Long nDiff = 0;
+ if ( n > 0 )
+ {
+ tools::Long nWidth = GetOutputSizePixel().Width();
+ if( ( mnMaxWidth - mnLeft + n ) > nWidth )
+ nDiff = n;
+ }
+ else if ( n < 0 )
+ {
+ if( mnLeft )
+ {
+ tools::Long nAbs = -n;
+ nDiff = - std::min( mnLeft, nAbs );
+ }
+ }
+
+ if ( nDiff )
+ {
+ ImplClearLayoutData();
+ mnLeft = sal::static_int_cast<sal_uInt16>(mnLeft + nDiff);
+ PaintImmediately();
+ ImplHideFocusRect();
+ Scroll( -nDiff, 0 );
+ PaintImmediately();
+ if( HasFocus() )
+ ImplShowFocusRect();
+ maScrollHdl.Call( this );
+ }
+}
+
+void ImplListBoxWindow::SetSeparatorPos( sal_Int32 n )
+{
+ maSeparators.clear();
+
+ if ( n != LISTBOX_ENTRY_NOTFOUND )
+ {
+ maSeparators.insert( n );
+ }
+}
+
+sal_Int32 ImplListBoxWindow::GetSeparatorPos() const
+{
+ if (!maSeparators.empty())
+ return *(maSeparators.begin());
+ else
+ return LISTBOX_ENTRY_NOTFOUND;
+}
+
+bool ImplListBoxWindow::isSeparator( const sal_Int32 &n) const
+{
+ return maSeparators.find(n) != maSeparators.end();
+}
+
+Size ImplListBoxWindow::CalcSize(sal_Int32 nMaxLines) const
+{
+ // FIXME: ListBoxEntryFlags::MultiLine
+
+ Size aSz;
+ aSz.setHeight(nMaxLines * GetEntryHeightWithMargin());
+ aSz.setWidth( mnMaxWidth + 2*gnBorder );
+ return aSz;
+}
+
+tools::Rectangle ImplListBoxWindow::GetBoundingRectangle( sal_Int32 nItem ) const
+{
+ const ImplEntryType* pEntry = maEntryList.GetEntryPtr( nItem );
+ Size aSz( GetSizePixel().Width(), pEntry ? pEntry->getHeightWithMargin() : GetEntryHeightWithMargin() );
+ tools::Long nY = maEntryList.GetAddedHeight( nItem, GetTopEntry() ) + GetEntryList().GetMRUCount()*GetEntryHeightWithMargin();
+ tools::Rectangle aRect( Point( 0, nY ), aSz );
+ return aRect;
+}
+
+void ImplListBoxWindow::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( nType == StateChangedType::Zoom )
+ {
+ ApplySettings(*GetOutDev());
+ ImplCalcMetrics();
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::UpdateMode )
+ {
+ if ( IsUpdateMode() && IsReallyVisible() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlFont )
+ {
+ ApplySettings(*GetOutDev());
+ ImplCalcMetrics();
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ApplySettings(*GetOutDev());
+ Invalidate();
+ }
+ else if( nType == StateChangedType::Enable )
+ {
+ Invalidate();
+ }
+
+ ImplClearLayoutData();
+}
+
+void ImplListBoxWindow::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplClearLayoutData();
+ ApplySettings(*GetOutDev());
+ ImplCalcMetrics();
+ Invalidate();
+ }
+}
+
+DrawTextFlags ImplListBoxWindow::ImplGetTextStyle() const
+{
+ DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
+
+ if (maEntryList.HasImages())
+ nTextStyle |= DrawTextFlags::Left;
+ else if (mbCenter)
+ nTextStyle |= DrawTextFlags::Center;
+ else if (mbRight)
+ nTextStyle |= DrawTextFlags::Right;
+ else
+ nTextStyle |= DrawTextFlags::Left;
+
+ return nTextStyle;
+}
+
+ImplListBox::ImplListBox( vcl::Window* pParent, WinBits nWinStyle ) :
+ Control( pParent, nWinStyle ),
+ maLBWindow(VclPtr<ImplListBoxWindow>::Create( this, nWinStyle&(~WB_BORDER) ))
+{
+ // for native widget rendering we must be able to detect this window type
+ SetType( WindowType::LISTBOXWINDOW );
+
+ mpVScrollBar = VclPtr<ScrollBar>::Create( this, WB_VSCROLL | WB_DRAG );
+ mpHScrollBar = VclPtr<ScrollBar>::Create( this, WB_HSCROLL | WB_DRAG );
+ mpScrollBarBox = VclPtr<ScrollBarBox>::Create( this );
+
+ Link<ScrollBar*,void> aLink( LINK( this, ImplListBox, ScrollBarHdl ) );
+ mpVScrollBar->SetScrollHdl( aLink );
+ mpHScrollBar->SetScrollHdl( aLink );
+
+ mbVScroll = false;
+ mbHScroll = false;
+ mbAutoHScroll = ( nWinStyle & WB_AUTOHSCROLL );
+ mbEdgeBlending = false;
+
+ maLBWindow->SetScrollHdl( LINK( this, ImplListBox, LBWindowScrolled ) );
+ maLBWindow->SetMRUChangedHdl( LINK( this, ImplListBox, MRUChanged ) );
+ maLBWindow->SetEdgeBlending(GetEdgeBlending());
+ maLBWindow->Show();
+}
+
+ImplListBox::~ImplListBox()
+{
+ disposeOnce();
+}
+
+void ImplListBox::dispose()
+{
+ mpHScrollBar.disposeAndClear();
+ mpVScrollBar.disposeAndClear();
+ mpScrollBarBox.disposeAndClear();
+ maLBWindow.disposeAndClear();
+ Control::dispose();
+}
+
+void ImplListBox::Clear()
+{
+ maLBWindow->Clear();
+ if ( GetEntryList().GetMRUCount() )
+ {
+ maLBWindow->GetEntryList().SetMRUCount( 0 );
+ maLBWindow->SetSeparatorPos( LISTBOX_ENTRY_NOTFOUND );
+ }
+ mpVScrollBar->SetThumbPos( 0 );
+ mpHScrollBar->SetThumbPos( 0 );
+ CompatStateChanged( StateChangedType::Data );
+}
+
+sal_Int32 ImplListBox::InsertEntry( sal_Int32 nPos, const OUString& rStr )
+{
+ ImplEntryType* pNewEntry = new ImplEntryType( rStr );
+ sal_Int32 nNewPos = maLBWindow->InsertEntry( nPos, pNewEntry );
+ CompatStateChanged( StateChangedType::Data );
+ return nNewPos;
+}
+
+sal_Int32 ImplListBox::InsertEntry( sal_Int32 nPos, const OUString& rStr, const Image& rImage )
+{
+ ImplEntryType* pNewEntry = new ImplEntryType( rStr, rImage );
+ sal_Int32 nNewPos = maLBWindow->InsertEntry( nPos, pNewEntry );
+ CompatStateChanged( StateChangedType::Data );
+ return nNewPos;
+}
+
+void ImplListBox::RemoveEntry( sal_Int32 nPos )
+{
+ maLBWindow->RemoveEntry( nPos );
+ CompatStateChanged( StateChangedType::Data );
+}
+
+void ImplListBox::SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags )
+{
+ maLBWindow->SetEntryFlags( nPos, nFlags );
+}
+
+void ImplListBox::SelectEntry( sal_Int32 nPos, bool bSelect )
+{
+ maLBWindow->SelectEntry( nPos, bSelect );
+}
+
+void ImplListBox::SetNoSelection()
+{
+ maLBWindow->DeselectAll();
+}
+
+void ImplListBox::GetFocus()
+{
+ if (maLBWindow)
+ maLBWindow->GrabFocus();
+ else
+ Control::GetFocus();
+}
+
+void ImplListBox::Resize()
+{
+ Control::Resize();
+ ImplResizeControls();
+ ImplCheckScrollBars();
+}
+
+IMPL_LINK_NOARG(ImplListBox, MRUChanged, LinkParamNone*, void)
+{
+ CompatStateChanged( StateChangedType::Data );
+}
+
+IMPL_LINK_NOARG(ImplListBox, LBWindowScrolled, ImplListBoxWindow*, void)
+{
+ tools::Long nSet = GetTopEntry();
+ if( nSet > mpVScrollBar->GetRangeMax() )
+ mpVScrollBar->SetRangeMax( GetEntryList().GetEntryCount() );
+ mpVScrollBar->SetThumbPos( GetTopEntry() );
+
+ mpHScrollBar->SetThumbPos( GetLeftIndent() );
+
+ maScrollHdl.Call( this );
+}
+
+IMPL_LINK( ImplListBox, ScrollBarHdl, ScrollBar*, pSB, void )
+{
+ sal_uInt16 nPos = static_cast<sal_uInt16>(pSB->GetThumbPos());
+ if( pSB == mpVScrollBar )
+ SetTopEntry( nPos );
+ else if( pSB == mpHScrollBar )
+ SetLeftIndent( nPos );
+ if( GetParent() )
+ GetParent()->Invalidate( InvalidateFlags::Update );
+}
+
+void ImplListBox::ImplCheckScrollBars()
+{
+ bool bArrange = false;
+
+ Size aOutSz = GetOutputSizePixel();
+ sal_Int32 nEntries = GetEntryList().GetEntryCount();
+ sal_uInt16 nMaxVisEntries = static_cast<sal_uInt16>(aOutSz.Height() / GetEntryHeightWithMargin());
+
+ // vertical ScrollBar
+ if( nEntries > nMaxVisEntries )
+ {
+ if( !mbVScroll )
+ bArrange = true;
+ mbVScroll = true;
+
+ // check of the scrolled-out region
+ if( GetEntryList().GetSelectedEntryCount() == 1 &&
+ GetEntryList().GetSelectedEntryPos( 0 ) != LISTBOX_ENTRY_NOTFOUND )
+ ShowProminentEntry( GetEntryList().GetSelectedEntryPos( 0 ) );
+ else
+ SetTopEntry( GetTopEntry() ); // MaxTop is being checked...
+ }
+ else
+ {
+ if( mbVScroll )
+ bArrange = true;
+ mbVScroll = false;
+ SetTopEntry( 0 );
+ }
+
+ // horizontal ScrollBar
+ if( mbAutoHScroll )
+ {
+ tools::Long nWidth = static_cast<sal_uInt16>(aOutSz.Width());
+ if ( mbVScroll )
+ nWidth -= mpVScrollBar->GetSizePixel().Width();
+
+ tools::Long nMaxWidth = GetMaxEntryWidth();
+ if( nWidth < nMaxWidth )
+ {
+ if( !mbHScroll )
+ bArrange = true;
+ mbHScroll = true;
+
+ if ( !mbVScroll ) // maybe we do need one now
+ {
+ nMaxVisEntries = static_cast<sal_uInt16>( ( aOutSz.Height() - mpHScrollBar->GetSizePixel().Height() ) / GetEntryHeightWithMargin() );
+ if( nEntries > nMaxVisEntries )
+ {
+ bArrange = true;
+ mbVScroll = true;
+
+ // check of the scrolled-out region
+ if( GetEntryList().GetSelectedEntryCount() == 1 &&
+ GetEntryList().GetSelectedEntryPos( 0 ) != LISTBOX_ENTRY_NOTFOUND )
+ ShowProminentEntry( GetEntryList().GetSelectedEntryPos( 0 ) );
+ else
+ SetTopEntry( GetTopEntry() ); // MaxTop is being checked...
+ }
+ }
+
+ // check of the scrolled-out region
+ sal_uInt16 nMaxLI = static_cast<sal_uInt16>(nMaxWidth - nWidth);
+ if ( nMaxLI < GetLeftIndent() )
+ SetLeftIndent( nMaxLI );
+ }
+ else
+ {
+ if( mbHScroll )
+ bArrange = true;
+ mbHScroll = false;
+ SetLeftIndent( 0 );
+ }
+ }
+
+ if( bArrange )
+ ImplResizeControls();
+
+ ImplInitScrollBars();
+}
+
+void ImplListBox::ImplInitScrollBars()
+{
+ Size aOutSz = maLBWindow->GetOutputSizePixel();
+
+ if ( mbVScroll )
+ {
+ sal_Int32 nEntries = GetEntryList().GetEntryCount();
+ sal_uInt16 nVisEntries = static_cast<sal_uInt16>(aOutSz.Height() / GetEntryHeightWithMargin());
+ mpVScrollBar->SetRangeMax( nEntries );
+ mpVScrollBar->SetVisibleSize( nVisEntries );
+ mpVScrollBar->SetPageSize( nVisEntries - 1 );
+ }
+
+ if ( mbHScroll )
+ {
+ mpHScrollBar->SetRangeMax( GetMaxEntryWidth() + HORZ_SCROLL );
+ mpHScrollBar->SetVisibleSize( static_cast<sal_uInt16>(aOutSz.Width()) );
+ mpHScrollBar->SetLineSize( HORZ_SCROLL );
+ mpHScrollBar->SetPageSize( aOutSz.Width() - HORZ_SCROLL );
+ }
+}
+
+void ImplListBox::ImplResizeControls()
+{
+ // Here we only position the Controls; if the Scrollbars are to be
+ // visible is already determined in ImplCheckScrollBars
+
+ Size aOutSz = GetOutputSizePixel();
+ tools::Long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
+ nSBWidth = CalcZoom( nSBWidth );
+
+ Size aInnerSz( aOutSz );
+ if ( mbVScroll )
+ aInnerSz.AdjustWidth( -nSBWidth );
+ if ( mbHScroll )
+ aInnerSz.AdjustHeight( -nSBWidth );
+
+ Point aWinPos( 0, 0 );
+ maLBWindow->SetPosSizePixel( aWinPos, aInnerSz );
+
+ // ScrollBarBox
+ if( mbVScroll && mbHScroll )
+ {
+ Point aBoxPos( aInnerSz.Width(), aInnerSz.Height() );
+ mpScrollBarBox->SetPosSizePixel( aBoxPos, Size( nSBWidth, nSBWidth ) );
+ mpScrollBarBox->Show();
+ }
+ else
+ {
+ mpScrollBarBox->Hide();
+ }
+
+ // vertical ScrollBar
+ if( mbVScroll )
+ {
+ // Scrollbar on left or right side?
+ Point aVPos( aOutSz.Width() - nSBWidth, 0 );
+ mpVScrollBar->SetPosSizePixel( aVPos, Size( nSBWidth, aInnerSz.Height() ) );
+ mpVScrollBar->Show();
+ }
+ else
+ {
+ mpVScrollBar->Hide();
+ // #107254# Don't reset top entry after resize, but check for max top entry
+ SetTopEntry( GetTopEntry() );
+ }
+
+ // horizontal ScrollBar
+ if( mbHScroll )
+ {
+ Point aHPos( 0, aOutSz.Height() - nSBWidth );
+ mpHScrollBar->SetPosSizePixel( aHPos, Size( aInnerSz.Width(), nSBWidth ) );
+ mpHScrollBar->Show();
+ }
+ else
+ {
+ mpHScrollBar->Hide();
+ SetLeftIndent( 0 );
+ }
+}
+
+void ImplListBox::StateChanged( StateChangedType nType )
+{
+ if ( nType == StateChangedType::InitShow )
+ {
+ ImplCheckScrollBars();
+ }
+ else if ( ( nType == StateChangedType::UpdateMode ) || ( nType == StateChangedType::Data ) )
+ {
+ bool bUpdate = IsUpdateMode();
+ maLBWindow->SetUpdateMode( bUpdate );
+ if ( bUpdate && IsReallyVisible() )
+ ImplCheckScrollBars();
+ }
+ else if( nType == StateChangedType::Enable )
+ {
+ mpHScrollBar->Enable( IsEnabled() );
+ mpVScrollBar->Enable( IsEnabled() );
+ mpScrollBarBox->Enable( IsEnabled() );
+ maLBWindow->Enable( IsEnabled() );
+
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Zoom )
+ {
+ maLBWindow->SetZoom( GetZoom() );
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlFont )
+ {
+ maLBWindow->SetControlFont( GetControlFont() );
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ maLBWindow->SetControlForeground( GetControlForeground() );
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ maLBWindow->SetControlBackground( GetControlBackground() );
+ }
+ else if( nType == StateChangedType::Mirroring )
+ {
+ maLBWindow->EnableRTL( IsRTLEnabled() );
+ mpHScrollBar->EnableRTL( IsRTLEnabled() );
+ mpVScrollBar->EnableRTL( IsRTLEnabled() );
+ ImplResizeControls();
+ }
+
+ Control::StateChanged( nType );
+}
+
+bool ImplListBox::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if ( rNEvt.GetType() == NotifyEventType::COMMAND )
+ {
+ const CommandEvent& rCEvt = *rNEvt.GetCommandEvent();
+ if ( rCEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ const CommandWheelData* pData = rCEvt.GetWheelData();
+ if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) )
+ {
+ bDone = HandleScrollCommand( rCEvt, mpHScrollBar, mpVScrollBar );
+ }
+ }
+ else if (rCEvt.GetCommand() == CommandEventId::GesturePan)
+ {
+ bDone = HandleScrollCommand(rCEvt, mpHScrollBar, mpVScrollBar);
+ }
+ }
+
+ return bDone || Window::EventNotify( rNEvt );
+}
+
+const Wallpaper& ImplListBox::GetDisplayBackground() const
+{
+ return maLBWindow->GetDisplayBackground();
+}
+
+bool ImplListBox::HandleWheelAsCursorTravel(const CommandEvent& rCEvt, Control& rControl)
+{
+ bool bDone = false;
+ if ( rCEvt.GetCommand() == CommandEventId::Wheel )
+ {
+ const CommandWheelData* pData = rCEvt.GetWheelData();
+ if( !pData->GetModifier() && ( pData->GetMode() == CommandWheelMode::SCROLL ) )
+ {
+ if (!rControl.HasChildPathFocus())
+ rControl.GrabFocus();
+ sal_uInt16 nKey = ( pData->GetDelta() < 0 ) ? KEY_DOWN : KEY_UP;
+ KeyEvent aKeyEvent( 0, vcl::KeyCode( nKey ) );
+ bDone = ProcessKeyInput( aKeyEvent );
+ }
+ }
+ return bDone;
+}
+
+void ImplListBox::SetMRUEntries( std::u16string_view rEntries, sal_Unicode cSep )
+{
+ bool bChanges = GetEntryList().GetMRUCount() != 0;
+
+ // Remove old MRU entries
+ for ( sal_Int32 n = GetEntryList().GetMRUCount();n; )
+ maLBWindow->RemoveEntry( --n );
+
+ sal_Int32 nMRUCount = 0;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OUString aEntry( o3tl::getToken(rEntries, 0, cSep, nIndex ) );
+ // Accept only existing entries
+ if ( GetEntryList().FindEntry( aEntry ) != LISTBOX_ENTRY_NOTFOUND )
+ {
+ ImplEntryType* pNewEntry = new ImplEntryType( aEntry );
+ maLBWindow->InsertEntry(nMRUCount++, pNewEntry, false);
+ bChanges = true;
+ }
+ }
+ while ( nIndex >= 0 );
+
+ if ( bChanges )
+ {
+ maLBWindow->GetEntryList().SetMRUCount( nMRUCount );
+ SetSeparatorPos( nMRUCount ? nMRUCount-1 : 0 );
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+OUString ImplListBox::GetMRUEntries( sal_Unicode cSep ) const
+{
+ OUStringBuffer aEntries;
+ for ( sal_Int32 n = 0; n < GetEntryList().GetMRUCount(); n++ )
+ {
+ aEntries.append(GetEntryList().GetEntryText( n ));
+ if( n < ( GetEntryList().GetMRUCount() - 1 ) )
+ aEntries.append(cSep);
+ }
+ return aEntries.makeStringAndClear();
+}
+
+void ImplListBox::SetEdgeBlending(bool bNew)
+{
+ if(mbEdgeBlending != bNew)
+ {
+ mbEdgeBlending = bNew;
+ maLBWindow->SetEdgeBlending(GetEdgeBlending());
+ }
+}
+
+void ImplListBox::SetHighlightColor(const Color& rColor)
+{
+ AllSettings aSettings(GetSettings());
+ StyleSettings aStyle(aSettings.GetStyleSettings());
+ aStyle.SetHighlightColor(rColor);
+ aSettings.SetStyleSettings(aStyle);
+ SetSettings(aSettings);
+
+ AllSettings aSettingsLB(maLBWindow->GetSettings());
+ StyleSettings aStyleLB(aSettingsLB.GetStyleSettings());
+ aStyleLB.SetListBoxWindowHighlightColor(rColor);
+ aSettingsLB.SetStyleSettings(aStyleLB);
+ maLBWindow->SetSettings(aSettingsLB);
+}
+
+void ImplListBox::SetHighlightTextColor(const Color& rColor)
+{
+ AllSettings aSettings(GetSettings());
+ StyleSettings aStyle(aSettings.GetStyleSettings());
+ aStyle.SetHighlightTextColor(rColor);
+ aSettings.SetStyleSettings(aStyle);
+ SetSettings(aSettings);
+
+ AllSettings aSettingsLB(maLBWindow->GetSettings());
+ StyleSettings aStyleLB(aSettingsLB.GetStyleSettings());
+ aStyleLB.SetListBoxWindowHighlightTextColor(rColor);
+ aSettingsLB.SetStyleSettings(aStyleLB);
+ maLBWindow->SetSettings(aSettingsLB);
+}
+
+ImplWin::ImplWin( vcl::Window* pParent, WinBits nWinStyle ) :
+ Control ( pParent, nWinStyle )
+{
+ if ( IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
+ && ! IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown) )
+ SetBackground();
+ else
+ SetBackground( Wallpaper( GetSettings().GetStyleSettings().GetFieldColor() ) );
+
+ ImplGetWindowImpl()->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
+
+ mbEdgeBlending = false;
+ mnItemPos = LISTBOX_ENTRY_NOTFOUND;
+}
+
+void ImplWin::MouseButtonDown( const MouseEvent& )
+{
+ if( IsEnabled() )
+ {
+ maMBDownHdl.Call(this);
+ }
+}
+
+void ImplWin::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ ImplWin* pThis = const_cast<ImplWin*>(this);
+ pThis->ImplDraw(*pThis->GetOutDev(), true);
+}
+
+void ImplWin::ImplDraw(vcl::RenderContext& rRenderContext, bool bLayout)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ if (!bLayout)
+ {
+ bool bNativeOK = false;
+ bool bHasFocus = HasFocus();
+ bool bIsEnabled = IsEnabled();
+
+ ControlState nState = ControlState::ENABLED;
+ if (rRenderContext.IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
+ && rRenderContext.IsNativeControlSupported(ControlType::Listbox, ControlPart::HasBackgroundTexture) )
+ {
+ // Repaint the (focused) area similarly to
+ // ImplSmallBorderWindowView::DrawWindow() in
+ // vcl/source/window/brdwin.cxx
+ vcl::Window *pWin = GetParent();
+
+ ImplControlValue aControlValue;
+ bIsEnabled &= pWin->IsEnabled();
+ if ( !bIsEnabled )
+ nState &= ~ControlState::ENABLED;
+ bHasFocus |= pWin->HasFocus();
+ if ( bHasFocus )
+ nState |= ControlState::FOCUSED;
+
+ // The listbox is painted over the entire control including the
+ // border, but ImplWin does not contain the border => correction
+ // needed.
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ pWin->GetBorder( nLeft, nTop, nRight, nBottom );
+ Point aPoint( -nLeft, -nTop );
+ tools::Rectangle aCtrlRegion( aPoint - GetPosPixel(), pWin->GetSizePixel() );
+
+ bool bMouseOver = pWin->IsMouseOver();
+ if (!bMouseOver)
+ {
+ vcl::Window *pChild = pWin->GetWindow( GetWindowType::FirstChild );
+ while( pChild )
+ {
+ bMouseOver = pChild->IsMouseOver();
+ if (bMouseOver)
+ break;
+ pChild = pChild->GetWindow( GetWindowType::Next );
+ }
+ }
+ if( bMouseOver )
+ nState |= ControlState::ROLLOVER;
+
+ Color aBackgroundColor = COL_AUTO;
+ if (IsControlBackground())
+ aBackgroundColor = GetControlBackground();
+
+ // if parent has no border, then nobody has drawn the background
+ // since no border window exists. so draw it here.
+ WinBits nParentStyle = pWin->GetStyle();
+ if( ! (nParentStyle & WB_BORDER) || (nParentStyle & WB_NOBORDER) )
+ {
+ tools::Rectangle aParentRect( Point( 0, 0 ), pWin->GetSizePixel() );
+ pWin->GetOutDev()->DrawNativeControl( ControlType::Listbox, ControlPart::Entire, aParentRect,
+ nState, aControlValue, OUString(), aBackgroundColor);
+ }
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Listbox, ControlPart::Entire, aCtrlRegion,
+ nState, aControlValue, OUString(), aBackgroundColor);
+ }
+
+ if (bIsEnabled)
+ {
+ if (bHasFocus && !ImplGetSVData()->maNWFData.mbDDListBoxNoTextArea)
+ {
+ if ( !ImplGetSVData()->maNWFData.mbNoFocusRects )
+ {
+ rRenderContext.SetFillColor( rStyleSettings.GetHighlightColor() );
+ rRenderContext.SetTextColor( rStyleSettings.GetHighlightTextColor() );
+ }
+ else
+ {
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor();
+ rRenderContext.SetTextColor( rStyleSettings.GetFieldTextColor() );
+ }
+ rRenderContext.DrawRect( maFocusRect );
+ }
+ else
+ {
+ Color aColor;
+ if (IsControlForeground())
+ aColor = GetControlForeground();
+ else if (ImplGetSVData()->maNWFData.mbDDListBoxNoTextArea)
+ {
+ if( bNativeOK && (nState & ControlState::ROLLOVER) )
+ aColor = rStyleSettings.GetButtonRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetButtonTextColor();
+ }
+ else
+ {
+ if( bNativeOK && (nState & ControlState::ROLLOVER) )
+ aColor = rStyleSettings.GetFieldRolloverTextColor();
+ else
+ aColor = rStyleSettings.GetFieldTextColor();
+ }
+ rRenderContext.SetTextColor(aColor);
+ if (!bNativeOK)
+ rRenderContext.Erase(maFocusRect);
+ }
+ }
+ else // Disabled
+ {
+ rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
+ if (!bNativeOK)
+ rRenderContext.Erase(maFocusRect);
+ }
+ }
+
+ DrawEntry(rRenderContext, bLayout);
+}
+
+void ImplWin::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ ApplyControlFont(rRenderContext, rStyleSettings.GetFieldFont());
+ ApplyControlForeground(rRenderContext, rStyleSettings.GetFieldTextColor());
+
+ if (IsControlBackground())
+ rRenderContext.SetBackground(GetControlBackground());
+ else
+ rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
+}
+
+void ImplWin::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& )
+{
+ ImplDraw(rRenderContext);
+}
+
+void ImplWin::DrawEntry(vcl::RenderContext& rRenderContext, bool bLayout)
+{
+ tools::Long nBorder = 1;
+ Size aOutSz(GetOutputSizePixel());
+
+ bool bImage = !!maImage;
+ if (bImage && !bLayout)
+ {
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+ Size aImgSz = maImage.GetSizePixel();
+ Point aPtImg( nBorder, ( ( aOutSz.Height() - aImgSz.Height() ) / 2 ) );
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+
+ // check for HC mode
+ Image *pImage = &maImage;
+
+ if ( !IsZoom() )
+ {
+ rRenderContext.DrawImage( aPtImg, *pImage, nStyle );
+ }
+ else
+ {
+ aImgSz.setWidth( CalcZoom( aImgSz.Width() ) );
+ aImgSz.setHeight( CalcZoom( aImgSz.Height() ) );
+ rRenderContext.DrawImage( aPtImg, aImgSz, *pImage, nStyle );
+ }
+
+ const sal_uInt16 nEdgeBlendingPercent(GetEdgeBlending() ? rStyleSettings.GetEdgeBlending() : 0);
+
+ if(nEdgeBlendingPercent)
+ {
+ const Color& rTopLeft(rStyleSettings.GetEdgeBlendingTopLeftColor());
+ const Color& rBottomRight(rStyleSettings.GetEdgeBlendingBottomRightColor());
+ const sal_uInt8 nAlpha((nEdgeBlendingPercent * 255) / 100);
+ const BitmapEx aBlendFrame(createBlendFrame(aImgSz, nAlpha, rTopLeft, rBottomRight));
+
+ if(!aBlendFrame.IsEmpty())
+ {
+ rRenderContext.DrawBitmapEx(aPtImg, aBlendFrame);
+ }
+ }
+ }
+
+ if( !maString.isEmpty() )
+ {
+ DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
+
+ if ( bImage && !bLayout )
+ nTextStyle |= DrawTextFlags::Left;
+ else if ( GetStyle() & WB_CENTER )
+ nTextStyle |= DrawTextFlags::Center;
+ else if ( GetStyle() & WB_RIGHT )
+ nTextStyle |= DrawTextFlags::Right;
+ else
+ nTextStyle |= DrawTextFlags::Left;
+
+ tools::Rectangle aTextRect( Point( nBorder, 0 ), Size( aOutSz.Width()-2*nBorder, aOutSz.Height() ) );
+
+ if ( bImage )
+ {
+ aTextRect.AdjustLeft(maImage.GetSizePixel().Width() + IMG_TXT_DISTANCE );
+ }
+
+ std::vector< tools::Rectangle >* pVector = bLayout ? &mxLayoutData->m_aUnicodeBoundRects : nullptr;
+ OUString* pDisplayText = bLayout ? &mxLayoutData->m_aDisplayText : nullptr;
+ rRenderContext.DrawText( aTextRect, maString, nTextStyle, pVector, pDisplayText );
+ }
+
+ if( HasFocus() && !bLayout )
+ ShowFocus( maFocusRect );
+}
+
+void ImplWin::Resize()
+{
+ Control::Resize();
+ maFocusRect.SetSize( GetOutputSizePixel() );
+ Invalidate();
+}
+
+void ImplWin::GetFocus()
+{
+ ShowFocus( maFocusRect );
+ if (IsNativeWidgetEnabled() &&
+ IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire))
+ {
+ vcl::Window* pWin = GetParent()->GetWindow( GetWindowType::Border );
+ if( ! pWin )
+ pWin = GetParent();
+ pWin->Invalidate();
+ }
+ else
+ Invalidate();
+ Control::GetFocus();
+}
+
+void ImplWin::LoseFocus()
+{
+ HideFocus();
+ if (IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Listbox, ControlPart::Entire))
+ {
+ vcl::Window* pWin = GetParent()->GetWindow( GetWindowType::Border );
+ if( ! pWin )
+ pWin = GetParent();
+ pWin->Invalidate();
+ }
+ else
+ Invalidate();
+ Control::LoseFocus();
+}
+
+void ImplWin::ShowFocus(const tools::Rectangle& rRect)
+{
+ if (IsNativeControlSupported(ControlType::Listbox, ControlPart::Focus))
+ {
+ ImplControlValue aControlValue;
+
+ vcl::Window *pWin = GetParent();
+ tools::Rectangle aParentRect(Point(0, 0), pWin->GetSizePixel());
+ pWin->GetOutDev()->DrawNativeControl(ControlType::Listbox, ControlPart::Focus, aParentRect,
+ ControlState::FOCUSED, aControlValue, OUString());
+ }
+ Control::ShowFocus(rRect);
+}
+
+ImplBtn::ImplBtn( vcl::Window* pParent, WinBits nWinStyle ) :
+ PushButton( pParent, nWinStyle )
+{
+}
+
+void ImplBtn::MouseButtonDown( const MouseEvent& )
+{
+ if( IsEnabled() )
+ maMBDownHdl.Call(this);
+}
+
+ImplListBoxFloatingWindow::ImplListBoxFloatingWindow( vcl::Window* pParent ) :
+ FloatingWindow( pParent, WB_BORDER | WB_SYSTEMWINDOW | WB_NOSHADOW ) // no drop shadow for list boxes
+{
+ // for native widget rendering we must be able to detect this window type
+ SetType( WindowType::LISTBOXWINDOW );
+
+ mpImplLB = nullptr;
+ mnDDLineCount = 0;
+ mbAutoWidth = false;
+
+ mnPopupModeStartSaveSelection = LISTBOX_ENTRY_NOTFOUND;
+
+ vcl::Window * pBorderWindow = ImplGetBorderWindow();
+ if( pBorderWindow )
+ {
+ SetAccessibleRole(accessibility::AccessibleRole::PANEL);
+ pBorderWindow->SetAccessibleRole(accessibility::AccessibleRole::WINDOW);
+ }
+ else
+ {
+ SetAccessibleRole(accessibility::AccessibleRole::WINDOW);
+ }
+
+}
+
+ImplListBoxFloatingWindow::~ImplListBoxFloatingWindow()
+{
+ disposeOnce();
+}
+
+void ImplListBoxFloatingWindow::dispose()
+{
+ mpImplLB.clear();
+ FloatingWindow::dispose();
+}
+
+
+bool ImplListBoxFloatingWindow::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ if( !GetParent()->HasChildPathFocus( true ) )
+ EndPopupMode();
+ }
+
+ return FloatingWindow::PreNotify( rNEvt );
+}
+
+void ImplListBoxFloatingWindow::setPosSizePixel( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, PosSizeFlags nFlags )
+{
+ FloatingWindow::setPosSizePixel( nX, nY, nWidth, nHeight, nFlags );
+
+ // Fix #60890# ( MBA ): to be able to resize the Listbox even in its open state
+ // after a call to Resize(), we adjust its position if necessary
+ if ( IsReallyVisible() && ( nFlags & PosSizeFlags::Height ) )
+ {
+ Point aPos = GetParent()->GetPosPixel();
+ aPos = GetParent()->GetParent()->OutputToScreenPixel( aPos );
+
+ if ( nFlags & PosSizeFlags::X )
+ aPos.setX( nX );
+
+ if ( nFlags & PosSizeFlags::Y )
+ aPos.setY( nY );
+
+ sal_uInt16 nIndex;
+ SetPosPixel( ImplCalcPos( this, tools::Rectangle( aPos, GetParent()->GetSizePixel() ), FloatWinPopupFlags::Down, nIndex ) );
+ }
+
+// if( !IsReallyVisible() )
+ {
+ // The ImplListBox does not get a Resize() as not visible.
+ // But the windows must get a Resize(), so that the number of
+ // visible entries is correct for PgUp/PgDown.
+ // The number also cannot be calculated by List/Combobox, as for
+ // this the presence of the vertical Scrollbar has to be known.
+ mpImplLB->SetSizePixel( GetOutputSizePixel() );
+ static_cast<vcl::Window*>(mpImplLB)->Resize();
+ static_cast<vcl::Window*>(mpImplLB->GetMainWindow())->Resize();
+ }
+}
+
+void ImplListBoxFloatingWindow::Resize()
+{
+ mpImplLB->GetMainWindow()->ImplClearLayoutData();
+ FloatingWindow::Resize();
+}
+
+Size ImplListBoxFloatingWindow::CalcFloatSize() const
+{
+ Size aFloatSz( maPrefSz );
+
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ GetBorder( nLeft, nTop, nRight, nBottom );
+
+ sal_Int32 nLines = mpImplLB->GetEntryList().GetEntryCount();
+ if ( mnDDLineCount && ( nLines > mnDDLineCount ) )
+ nLines = mnDDLineCount;
+
+ Size aSz = mpImplLB->CalcSize( nLines );
+ tools::Long nMaxHeight = aSz.Height() + nTop + nBottom;
+
+ if ( mnDDLineCount )
+ aFloatSz.setHeight( nMaxHeight );
+
+ if( mbAutoWidth )
+ {
+ // AutoSize first only for width...
+
+ aFloatSz.setWidth( aSz.Width() + nLeft + nRight );
+ aFloatSz.AdjustWidth(nRight ); // adding some space looks better...
+
+ if ( ( aFloatSz.Height() < nMaxHeight ) || ( mnDDLineCount && ( mnDDLineCount < mpImplLB->GetEntryList().GetEntryCount() ) ) )
+ {
+ // then we also need the vertical Scrollbar
+ tools::Long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
+ aFloatSz.AdjustWidth(nSBWidth );
+ }
+
+ tools::Long nDesktopWidth = GetDesktopRectPixel().getOpenWidth();
+ if (aFloatSz.Width() > nDesktopWidth)
+ // Don't exceed the desktop width.
+ aFloatSz.setWidth( nDesktopWidth );
+ }
+
+ if ( aFloatSz.Height() > nMaxHeight )
+ aFloatSz.setHeight( nMaxHeight );
+
+ // Minimal height, in case height is not set to Float height.
+ // The parent of FloatWin must be DropDown-Combo/Listbox.
+ Size aParentSz = GetParent()->GetSizePixel();
+ if( (!mnDDLineCount || !nLines) && ( aFloatSz.Height() < aParentSz.Height() ) )
+ aFloatSz.setHeight( aParentSz.Height() );
+
+ // do not get narrower than the parent...
+ if( aFloatSz.Width() < aParentSz.Width() )
+ aFloatSz.setWidth( aParentSz.Width() );
+
+ // align height to entries...
+ tools::Long nInnerHeight = aFloatSz.Height() - nTop - nBottom;
+ tools::Long nEntryHeight = mpImplLB->GetEntryHeightWithMargin();
+ if ( nInnerHeight % nEntryHeight )
+ {
+ nInnerHeight /= nEntryHeight;
+ nInnerHeight++;
+ nInnerHeight *= nEntryHeight;
+ aFloatSz.setHeight( nInnerHeight + nTop + nBottom );
+ }
+
+ if (aFloatSz.Width() < aSz.Width())
+ {
+ // The max width of list box entries exceeds the window width.
+ // Account for the scroll bar height.
+ tools::Long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
+ aFloatSz.AdjustHeight(nSBWidth );
+ }
+
+ return aFloatSz;
+}
+
+void ImplListBoxFloatingWindow::StartFloat( bool bStartTracking )
+{
+ if( IsInPopupMode() )
+ return;
+
+ Size aFloatSz = CalcFloatSize();
+
+ SetSizePixel( aFloatSz );
+ mpImplLB->SetSizePixel( GetOutputSizePixel() );
+
+ sal_Int32 nPos = mpImplLB->GetEntryList().GetSelectedEntryPos( 0 );
+ mnPopupModeStartSaveSelection = nPos;
+
+ Size aSz = GetParent()->GetSizePixel();
+ Point aPos = GetParent()->GetPosPixel();
+ aPos = GetParent()->GetParent()->OutputToScreenPixel( aPos );
+ // FIXME: this ugly hack is for Mac/Aqua
+ // should be replaced by a real mechanism to place the float rectangle
+ if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
+ GetParent()->IsNativeWidgetEnabled() )
+ {
+ const sal_Int32 nLeft = 4, nTop = 4, nRight = 4, nBottom = 4;
+ aPos.AdjustX(nLeft );
+ aPos.AdjustY(nTop );
+ aSz.AdjustWidth( -(nLeft + nRight) );
+ aSz.AdjustHeight( -(nTop + nBottom) );
+ }
+ tools::Rectangle aRect( aPos, aSz );
+
+ // check if the control's parent is un-mirrored which is the case for form controls in a mirrored UI
+ // where the document is unmirrored
+ // because StartPopupMode() expects a rectangle in mirrored coordinates we have to re-mirror
+ vcl::Window *pGrandparent = GetParent()->GetParent();
+ const OutputDevice *pGrandparentOutDev = pGrandparent->GetOutDev();
+
+ if( pGrandparent->GetOutDev()->ImplIsAntiparallel() )
+ pGrandparentOutDev->ReMirror( aRect );
+
+ // mouse-button right: close the List-Box-Float-win and don't stop the handling fdo#84795
+ StartPopupMode( aRect, LISTBOX_FLOATWINPOPUPFLAGS );
+
+ if( nPos != LISTBOX_ENTRY_NOTFOUND )
+ mpImplLB->ShowProminentEntry( nPos );
+
+ if( bStartTracking )
+ mpImplLB->GetMainWindow()->EnableMouseMoveSelect( true );
+
+ if ( mpImplLB->GetMainWindow()->IsGrabFocusAllowed() )
+ mpImplLB->GetMainWindow()->GrabFocus();
+
+ mpImplLB->GetMainWindow()->ImplClearLayoutData();
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/ivctrl.cxx b/vcl/source/control/ivctrl.cxx
new file mode 100644
index 0000000000..a2f502ff81
--- /dev/null
+++ b/vcl/source/control/ivctrl.cxx
@@ -0,0 +1,679 @@
+/* -*- 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 <utility>
+#include <vcl/toolkit/ivctrl.hxx>
+#include "imivctl.hxx"
+#include <vcl/accessiblefactory.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/uitest/eventdescription.hxx>
+#include <verticaltabctrl.hxx>
+
+using namespace ::com::sun::star::accessibility;
+
+namespace
+{
+void collectUIInformation( const OUString& aID, const OUString& aPos)
+{
+ EventDescription aDescription;
+ aDescription.aID = aID;
+ aDescription.aParameters = {{ "POS" , aPos}};
+ aDescription.aAction = "SELECT";
+ aDescription.aKeyWord = "VerticalTab";
+ UITestLogger::getInstance().logEvent(aDescription);
+}
+}
+
+/*****************************************************************************
+|
+| class : SvxIconChoiceCtrlEntry
+|
+\*****************************************************************************/
+
+SvxIconChoiceCtrlEntry::SvxIconChoiceCtrlEntry( OUString _aText,
+ Image _aImage )
+ : aImage(std::move(_aImage))
+ , aText(std::move(_aText))
+ , nPos(0)
+ , pblink(nullptr)
+ , pflink(nullptr)
+ , eTextMode(SvxIconChoiceCtrlTextMode::Short)
+ , nX(0)
+ , nY(0)
+ , nFlags(SvxIconViewFlags::NONE)
+{
+}
+
+OUString SvxIconChoiceCtrlEntry::GetDisplayText() const
+{
+ return MnemonicGenerator::EraseAllMnemonicChars( aText );
+}
+
+
+SvxIconChoiceCtrlColumnInfo::SvxIconChoiceCtrlColumnInfo( const SvxIconChoiceCtrlColumnInfo& rInfo )
+{
+ nWidth = rInfo.nWidth;
+}
+
+/*****************************************************************************
+|
+| class : SvtIconChoiceCtrl
+|
+\*****************************************************************************/
+
+SvtIconChoiceCtrl::SvtIconChoiceCtrl( vcl::Window* pParent, WinBits nWinStyle ) :
+
+ // WB_CLIPCHILDREN on, as ScrollBars lie on the window!
+ Control( pParent, nWinStyle | WB_CLIPCHILDREN ),
+
+ _pImpl ( new SvxIconChoiceCtrl_Impl( this, nWinStyle ) )
+{
+ GetOutDev()->SetLineColor();
+ _pImpl->InitSettings();
+ _pImpl->SetPositionMode( SvxIconChoiceCtrlPositionMode::AutoArrange );
+}
+
+void SvtIconChoiceCtrl::SetSelectionMode(SelectionMode eMode)
+{
+ _pImpl->SetSelectionMode(eMode);
+}
+
+SvtIconChoiceCtrl::~SvtIconChoiceCtrl()
+{
+ disposeOnce();
+}
+
+void SvtIconChoiceCtrl::dispose()
+{
+ if (_pImpl)
+ {
+ _pImpl->CallEventListeners( VclEventId::ObjectDying, nullptr );
+ _pImpl.reset();
+ }
+ Control::dispose();
+}
+
+SvxIconChoiceCtrlEntry* SvtIconChoiceCtrl::InsertEntry( const OUString& rText, const Image& rImage )
+{
+ SvxIconChoiceCtrlEntry* pEntry = new SvxIconChoiceCtrlEntry( rText, rImage);
+
+ _pImpl->InsertEntry(std::unique_ptr<SvxIconChoiceCtrlEntry>(pEntry), _pImpl->GetEntryCount());
+
+ return pEntry;
+}
+
+void SvtIconChoiceCtrl::RemoveEntry(sal_Int32 nIndex)
+{
+ _pImpl->RemoveEntry(nIndex);
+}
+
+void SvtIconChoiceCtrl::DrawEntryImage( SvxIconChoiceCtrlEntry const * pEntry, const Point& rPos, OutputDevice& rDev )
+{
+ rDev.DrawImage( rPos, pEntry->GetImage() );
+}
+
+OUString SvtIconChoiceCtrl::GetEntryText( SvxIconChoiceCtrlEntry const * pEntry )
+{
+ return pEntry->GetText();
+}
+
+void SvtIconChoiceCtrl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ _pImpl->Paint(rRenderContext, rRect);
+}
+
+void SvtIconChoiceCtrl::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if( !_pImpl->MouseButtonDown( rMEvt ) )
+ Control::MouseButtonDown( rMEvt );
+}
+
+void SvtIconChoiceCtrl::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ if( !_pImpl->MouseButtonUp( rMEvt ) )
+ Control::MouseButtonUp( rMEvt );
+}
+
+void SvtIconChoiceCtrl::MouseMove( const MouseEvent& rMEvt )
+{
+ if( !_pImpl->MouseMove( rMEvt ) )
+ Control::MouseMove( rMEvt );
+}
+void SvtIconChoiceCtrl::ArrangeIcons()
+{
+ if ( GetStyle() & WB_ALIGN_TOP )
+ {
+ Size aFullSize;
+ tools::Rectangle aEntryRect;
+
+ for ( sal_Int32 i = 0; i < GetEntryCount(); i++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = GetEntry ( i );
+ aEntryRect = _pImpl->GetEntryBoundRect ( pEntry );
+
+ aFullSize.setWidth ( aFullSize.getWidth()+aEntryRect.GetWidth() );
+ }
+
+ _pImpl->Arrange ( false, aFullSize.getWidth(), 0 );
+ }
+ else if ( GetStyle() & WB_ALIGN_LEFT )
+ {
+ Size aFullSize;
+ tools::Rectangle aEntryRect;
+
+ for ( sal_Int32 i = 0; i < GetEntryCount(); i++ )
+ {
+ SvxIconChoiceCtrlEntry* pEntry = GetEntry ( i );
+ aEntryRect = _pImpl->GetEntryBoundRect ( pEntry );
+
+ aFullSize.setHeight ( aFullSize.getHeight()+aEntryRect.GetHeight() );
+ }
+
+ _pImpl->Arrange ( false, 0, aFullSize.getHeight() );
+ }
+ else
+ {
+ _pImpl->Arrange(false, 0, 0);
+ }
+ _pImpl->Arrange( false, 0, 1000 );
+}
+void SvtIconChoiceCtrl::Resize()
+{
+ _pImpl->Resize();
+ Control::Resize();
+}
+
+void SvtIconChoiceCtrl::GetFocus()
+{
+ _pImpl->GetFocus();
+ Control::GetFocus();
+ SvxIconChoiceCtrlEntry* pSelectedEntry = GetSelectedEntry();
+ if ( pSelectedEntry )
+ _pImpl->CallEventListeners( VclEventId::ListboxSelect, pSelectedEntry );
+}
+
+void SvtIconChoiceCtrl::LoseFocus()
+{
+ if (_pImpl)
+ _pImpl->LoseFocus();
+ Control::LoseFocus();
+}
+
+void SvtIconChoiceCtrl::SetFont(const vcl::Font& rFont)
+{
+ if (rFont != GetFont())
+ {
+ Control::SetFont(rFont);
+ _pImpl->FontModified();
+ }
+}
+
+void SvtIconChoiceCtrl::SetPointFont(const vcl::Font& rFont)
+{
+ if (rFont != GetPointFont(*GetOutDev())) //FIXME
+ {
+ Control::SetPointFont(*GetOutDev(), rFont); //FIXME
+ _pImpl->FontModified();
+ }
+}
+
+WinBits SvtIconChoiceCtrl::GetStyle() const
+{
+ return _pImpl->GetStyle();
+}
+
+void SvtIconChoiceCtrl::Command(const CommandEvent& rCEvt)
+{
+ _pImpl->Command( rCEvt );
+ //pass at least alt press/release to parent impl
+ if (rCEvt.GetCommand() == CommandEventId::ModKeyChange)
+ Control::Command(rCEvt);
+}
+
+#ifdef DBG_UTIL
+void SvtIconChoiceCtrl::SetEntryTextMode( SvxIconChoiceCtrlTextMode eMode, SvxIconChoiceCtrlEntry* pEntry )
+{
+ _pImpl->SetEntryTextMode( eMode, pEntry );
+}
+#endif
+
+sal_Int32 SvtIconChoiceCtrl::GetEntryCount() const
+{
+ return _pImpl ? _pImpl->GetEntryCount() : 0;
+}
+
+SvxIconChoiceCtrlEntry* SvtIconChoiceCtrl::GetEntry( sal_Int32 nPos ) const
+{
+ return _pImpl ? _pImpl->GetEntry( nPos ) : nullptr;
+}
+
+SvxIconChoiceCtrlEntry* SvtIconChoiceCtrl::GetSelectedEntry() const
+{
+ return _pImpl ? _pImpl->GetFirstSelectedEntry() : nullptr;
+}
+
+void SvtIconChoiceCtrl::ClickIcon()
+{
+ GetSelectedEntry();
+ _aClickIconHdl.Call( this );
+}
+
+void SvtIconChoiceCtrl::KeyInput( const KeyEvent& rKEvt )
+{
+ bool bKeyUsed = DoKeyInput( rKEvt );
+ if ( !bKeyUsed )
+ {
+ Control::KeyInput( rKEvt );
+ }
+}
+bool SvtIconChoiceCtrl::DoKeyInput( const KeyEvent& rKEvt )
+{
+ return _pImpl->KeyInput( rKEvt );
+}
+sal_Int32 SvtIconChoiceCtrl::GetEntryListPos( SvxIconChoiceCtrlEntry const * pEntry ) const
+{
+ return _pImpl->GetEntryListPos( pEntry );
+}
+SvxIconChoiceCtrlEntry* SvtIconChoiceCtrl::GetCursor( ) const
+{
+ return _pImpl->GetCurEntry( );
+}
+void SvtIconChoiceCtrl::SetCursor( SvxIconChoiceCtrlEntry* pEntry )
+{
+ _pImpl->SetCursor( pEntry );
+}
+
+void SvtIconChoiceCtrl::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ if ( ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTS) ) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ _pImpl->InitSettings();
+ Invalidate(InvalidateFlags::NoChildren);
+ }
+ else
+ Control::DataChanged( rDCEvt );
+}
+
+void SvtIconChoiceCtrl::SetBackground( const Wallpaper& rPaper )
+{
+ if( rPaper == GetBackground() )
+ return;
+
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ // if it is the default (empty) wallpaper
+ if (rPaper.IsEmpty())
+ {
+ Control::SetBackground( rStyleSettings.GetFieldColor() );
+ }
+ else
+ {
+ Wallpaper aBackground( rPaper );
+ // HACK, as background might be transparent!
+ if( !aBackground.IsBitmap() )
+ aBackground.SetStyle( WallpaperStyle::Tile );
+
+ WallpaperStyle eStyle = aBackground.GetStyle();
+ Color aBack( aBackground.GetColor());
+ if( aBack == COL_TRANSPARENT &&
+ (!aBackground.IsBitmap() ||
+ aBackground.GetBitmap().IsAlpha() ||
+ (eStyle != WallpaperStyle::Tile && eStyle != WallpaperStyle::Scale)) )
+ {
+ aBackground.SetColor( rStyleSettings.GetFieldColor() );
+ }
+ if( aBackground.IsScrollable() )
+ {
+ tools::Rectangle aRect;
+ aRect.SetSize( Size(32765, 32765) );
+ aBackground.SetRect( aRect );
+ }
+ else
+ {
+ tools::Rectangle aRect( _pImpl->GetOutputRect() );
+ aBackground.SetRect( aRect );
+ }
+ Control::SetBackground( aBackground );
+ }
+
+ // If text colors are attributed "hard," don't use automatism to select
+ // a readable text color.
+ vcl::Font aFont( GetFont() );
+ aFont.SetColor( rStyleSettings.GetFieldTextColor() );
+ SetFont( aFont );
+
+ Invalidate(InvalidateFlags::NoChildren);
+}
+
+void SvtIconChoiceCtrl::RequestHelp( const HelpEvent& rHEvt )
+{
+ if ( !_pImpl->RequestHelp( rHEvt ) )
+ Control::RequestHelp( rHEvt );
+}
+
+tools::Rectangle SvtIconChoiceCtrl::GetBoundingBox( SvxIconChoiceCtrlEntry* pEntry ) const
+{
+ return _pImpl->GetEntryBoundRect( pEntry );
+}
+
+void SvtIconChoiceCtrl::FillLayoutData() const
+{
+ CreateLayoutData();
+ const_cast<SvtIconChoiceCtrl*>(this)->Invalidate();
+}
+
+tools::Rectangle SvtIconChoiceCtrl::GetEntryCharacterBounds( const sal_Int32 _nEntryPos, const sal_Int32 _nCharacterIndex ) const
+{
+ tools::Rectangle aRect;
+
+ Pair aEntryCharacterRange = GetLineStartEnd( _nEntryPos );
+ if ( aEntryCharacterRange.A() + _nCharacterIndex < aEntryCharacterRange.B() )
+ {
+ aRect = GetCharacterBounds( aEntryCharacterRange.A() + _nCharacterIndex );
+ }
+
+ return aRect;
+}
+
+void SvtIconChoiceCtrl::SetNoSelection()
+{
+ _pImpl->SetNoSelection();
+}
+
+void SvtIconChoiceCtrl::CallImplEventListeners(VclEventId nEvent, void* pData)
+{
+ CallEventListeners(nEvent, pData);
+}
+css::uno::Reference< XAccessible > SvtIconChoiceCtrl::CreateAccessible()
+{
+ vcl::Window* pParent = GetAccessibleParentWindow();
+ DBG_ASSERT( pParent, "SvTreeListBox::CreateAccessible - accessible parent not found" );
+
+ css::uno::Reference< XAccessible > xAccessible;
+ if ( pParent )
+ {
+ css::uno::Reference< XAccessible > xAccParent = pParent->GetAccessible();
+ if ( xAccParent.is() )
+ {
+ css::uno::Reference< css::awt::XVclWindowPeer > xHoldAlive(GetComponentInterface());
+ xAccessible = _pImpl->GetAccessibleFactory().createAccessibleIconChoiceCtrl( *this, xAccParent );
+ }
+ }
+ return xAccessible;
+}
+
+struct VerticalTabPageData
+{
+ OUString sId;
+ SvxIconChoiceCtrlEntry* pEntry;
+ VclPtr<vcl::Window> xPage; ///< the TabPage itself
+};
+
+VerticalTabControl::VerticalTabControl(vcl::Window* pParent)
+ : VclHBox(pParent)
+ , m_xChooser(VclPtr<SvtIconChoiceCtrl>::Create(this, WB_3DLOOK | WB_ICON | WB_BORDER |
+ WB_NOCOLUMNHEADER | WB_HIGHLIGHTFRAME |
+ WB_NODRAGSELECTION | WB_TABSTOP | WB_CLIPCHILDREN |
+ WB_ALIGN_LEFT | WB_NOHSCROLL))
+ , m_xBox(VclPtr<VclVBox>::Create(this))
+{
+ SetStyle(GetStyle() | WB_DIALOGCONTROL);
+ SetType(WindowType::VERTICALTABCONTROL);
+ m_xChooser->SetSelectionMode(SelectionMode::Single);
+ m_xChooser->SetClickHdl(LINK(this, VerticalTabControl, ChosePageHdl_Impl));
+ m_xChooser->set_width_request(110);
+ m_xChooser->set_height_request(400);
+ m_xChooser->SetSizePixel(Size(110, 400));
+ m_xBox->set_vexpand(true);
+ m_xBox->set_hexpand(true);
+ m_xBox->set_expand(true);
+ m_xBox->Show();
+ m_xChooser->Show();
+}
+
+VerticalTabControl::~VerticalTabControl()
+{
+ disposeOnce();
+}
+
+void VerticalTabControl::dispose()
+{
+ m_xChooser.disposeAndClear();
+ m_xBox.disposeAndClear();
+ VclHBox::dispose();
+}
+
+IMPL_LINK_NOARG(VerticalTabControl, ChosePageHdl_Impl, SvtIconChoiceCtrl*, void)
+{
+ SvxIconChoiceCtrlEntry *pEntry = m_xChooser->GetSelectedEntry();
+ if (!pEntry)
+ pEntry = m_xChooser->GetCursor();
+
+ VerticalTabPageData* pData = GetPageData(pEntry);
+
+ if (pData->sId != m_sCurrentPageId)
+ SetCurPageId(pData->sId);
+}
+
+void VerticalTabControl::ActivatePage()
+{
+ m_aActivateHdl.Call( this );
+}
+
+bool VerticalTabControl::DeactivatePage()
+{
+ return !m_aDeactivateHdl.IsSet() || m_aDeactivateHdl.Call(this);
+}
+
+VerticalTabPageData* VerticalTabControl::GetPageData(const SvxIconChoiceCtrlEntry* pEntry) const
+{
+ VerticalTabPageData* pRet = nullptr;
+ for (auto & pData : maPageList)
+ {
+ if (pData->pEntry == pEntry)
+ {
+ pRet = pData.get();
+ break;
+ }
+ }
+ return pRet;
+}
+
+VerticalTabPageData* VerticalTabControl::GetPageData(std::u16string_view rId) const
+{
+ VerticalTabPageData* pRet = nullptr;
+ for (auto & pData : maPageList)
+ {
+ if (pData->sId == rId)
+ {
+ pRet = pData.get();
+ break;
+ }
+ }
+ return pRet;
+}
+
+void VerticalTabControl::SetCurPageId(const OUString& rId)
+{
+ OUString sOldPageId = GetCurPageId();
+ if (sOldPageId == rId)
+ return;
+
+ VerticalTabPageData* pOldData = GetPageData(sOldPageId);
+ if (pOldData && pOldData->xPage)
+ {
+ if (!DeactivatePage())
+ return;
+ pOldData->xPage->Hide();
+ }
+
+ m_sCurrentPageId = "";
+
+ VerticalTabPageData* pNewData = GetPageData(rId);
+ if (pNewData && pNewData->xPage)
+ {
+ m_sCurrentPageId = rId;
+ m_xChooser->SetCursor(pNewData->pEntry);
+
+ ActivatePage();
+ pNewData->xPage->Show();
+ }
+ collectUIInformation(get_id(),m_sCurrentPageId);
+}
+
+const OUString & VerticalTabControl::GetPageId(sal_uInt16 nIndex) const
+{
+ return maPageList[nIndex]->sId;
+}
+
+void VerticalTabControl::InsertPage(const rtl::OUString &rIdent, const rtl::OUString& rLabel, const Image& rImage,
+ const rtl::OUString& rTooltip, VclPtr<vcl::Window> xPage, int nPos)
+{
+ SvxIconChoiceCtrlEntry* pEntry = m_xChooser->InsertEntry(rLabel, rImage);
+ pEntry->SetQuickHelpText(rTooltip);
+ m_xChooser->ArrangeIcons();
+ VerticalTabPageData* pNew;
+ if (nPos == -1)
+ {
+ maPageList.emplace_back(new VerticalTabPageData);
+ pNew = maPageList.back().get();
+ }
+ else
+ {
+ maPageList.emplace(maPageList.begin() + nPos, new VerticalTabPageData);
+ pNew = maPageList[nPos].get();
+ }
+ pNew->sId = rIdent;
+ pNew->pEntry = pEntry;
+ pNew->xPage = xPage;
+ Size aOrigPrefSize(m_xBox->get_preferred_size());
+ Size aPagePrefSize(xPage->get_preferred_size());
+ m_xBox->set_width_request(std::max(aOrigPrefSize.Width(), aPagePrefSize.Width()));
+ m_xBox->set_height_request(std::max(aOrigPrefSize.Height(), aPagePrefSize.Height()));
+ pNew->xPage->Hide();
+}
+
+void VerticalTabControl::RemovePage(std::u16string_view rPageId)
+{
+ for (auto it = maPageList.begin(), end = maPageList.end(); it != end; ++it)
+ {
+ VerticalTabPageData* pData = it->get();
+ if (pData->sId == rPageId)
+ {
+ sal_Int32 nEntryListPos = m_xChooser->GetEntryListPos(pData->pEntry);
+ m_xChooser->RemoveEntry(nEntryListPos);
+ m_xChooser->ArrangeIcons();
+ maPageList.erase(it);
+ break;
+ }
+ }
+}
+
+sal_uInt16 VerticalTabControl::GetPagePos(std::u16string_view rPageId) const
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return TAB_PAGE_NOTFOUND;
+ return m_xChooser->GetEntryListPos(pData->pEntry);
+}
+
+VclPtr<vcl::Window> VerticalTabControl::GetPage(std::u16string_view rPageId) const
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return nullptr;
+ return pData->xPage;
+}
+
+OUString VerticalTabControl::GetPageText(std::u16string_view rPageId) const
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return OUString();
+ return pData->pEntry->GetText();
+}
+
+void VerticalTabControl::SetPageText(std::u16string_view rPageId, const OUString& rText)
+{
+ VerticalTabPageData* pData = GetPageData(rPageId);
+ if (!pData)
+ return;
+ pData->pEntry->SetText(rText);
+}
+
+void VerticalTabControl::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ rJsonWriter.put("id", get_id());
+ rJsonWriter.put("type", "tabcontrol");
+ rJsonWriter.put("vertical", true);
+ rJsonWriter.put("selected", GetCurPageId());
+
+ {
+ auto childrenNode = rJsonWriter.startArray("children");
+ for (int i = 0; i < GetPageCount(); i++)
+ {
+ VclPtr<vcl::Window> pChild = GetPage(GetPageId(i));
+
+ if (pChild)
+ {
+ if (!pChild->GetChildCount())
+ continue;
+
+ auto aChildNode = rJsonWriter.startStruct();
+ pChild->DumpAsPropertyTree(rJsonWriter);
+ }
+ }
+ }
+ {
+ auto tabsNode = rJsonWriter.startArray("tabs");
+ for(int i = 0; i < GetPageCount(); i++)
+ {
+ VclPtr<vcl::Window> pChild = GetPage(GetPageId(i));
+
+ if (pChild)
+ {
+ if (!pChild->GetChildCount())
+ continue;
+
+ auto aTabNode = rJsonWriter.startStruct();
+ auto sId = GetPageId(i);
+ rJsonWriter.put("text", GetPageText(sId));
+ rJsonWriter.put("id", sId);
+ rJsonWriter.put("name", GetPageText(sId));
+ }
+ }
+ }
+}
+
+FactoryFunction VerticalTabControl::GetUITestFactory() const
+{
+ return VerticalTabControlUIObject::create;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/listbox.cxx b/vcl/source/control/listbox.cxx
new file mode 100644
index 0000000000..e189c8480f
--- /dev/null
+++ b/vcl/source/control/listbox.cxx
@@ -0,0 +1,1457 @@
+/* -*- 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/builder.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <sal/log.hxx>
+
+#include <svdata.hxx>
+#include <listbox.hxx>
+#include <dndeventdispatcher.hxx>
+#include <comphelper/lok.hxx>
+
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <tools/json_writer.hxx>
+
+ListBox::ListBox(WindowType nType)
+ : Control(nType)
+ , mpImplLB(nullptr)
+{
+ ImplInitListBoxData();
+}
+
+ListBox::ListBox( vcl::Window* pParent, WinBits nStyle ) : Control( WindowType::LISTBOX )
+{
+ ImplInitListBoxData();
+ ImplInit( pParent, nStyle );
+}
+
+ListBox::~ListBox()
+{
+ disposeOnce();
+}
+
+void ListBox::dispose()
+{
+ CallEventListeners( VclEventId::ObjectDying );
+
+ mpImplLB.disposeAndClear();
+ mpFloatWin.disposeAndClear();
+ mpImplWin.disposeAndClear();
+ mpBtn.disposeAndClear();
+
+ Control::dispose();
+}
+
+void ListBox::ImplInitListBoxData()
+{
+ mpFloatWin = nullptr;
+ mpImplWin = nullptr;
+ mpBtn = nullptr;
+ mnDDHeight = 0;
+ mnLineCount = 0;
+ m_nMaxWidthChars = -1;
+ mbDDAutoSize = true;
+}
+
+void ListBox::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ nStyle = ImplInitStyle( nStyle );
+ if ( !(nStyle & WB_NOBORDER) && ( nStyle & WB_DROPDOWN ) )
+ nStyle |= WB_BORDER;
+
+ Control::ImplInit( pParent, nStyle, nullptr );
+
+ css::uno::Reference< css::datatransfer::dnd::XDropTargetListener> xDrop = new DNDEventDispatcher(this);
+
+ if( nStyle & WB_DROPDOWN )
+ {
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ GetBorder( nLeft, nTop, nRight, nBottom );
+ mnDDHeight = static_cast<sal_uInt16>(GetTextHeight() + nTop + nBottom + 4);
+
+ if( IsNativeWidgetEnabled() &&
+ IsNativeControlSupported( ControlType::Listbox, ControlPart::Entire ) )
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), Size( 20, mnDDHeight ) );
+ tools::Rectangle aBoundingRgn( aCtrlRegion );
+ tools::Rectangle aContentRgn( aCtrlRegion );
+ if( GetNativeControlRegion( ControlType::Listbox, ControlPart::Entire, aCtrlRegion,
+ ControlState::ENABLED, aControlValue,
+ aBoundingRgn, aContentRgn ) )
+ {
+ sal_Int32 nHeight = aBoundingRgn.GetHeight();
+ if( nHeight > mnDDHeight )
+ mnDDHeight = static_cast<sal_uInt16>(nHeight);
+ }
+ }
+
+ mpFloatWin = VclPtr<ImplListBoxFloatingWindow>::Create( this );
+ if (!IsNativeControlSupported(ControlType::Pushbutton, ControlPart::Focus))
+ mpFloatWin->RequestDoubleBuffering(true);
+ mpFloatWin->SetAutoWidth( true );
+ mpFloatWin->SetPopupModeEndHdl( LINK( this, ListBox, ImplPopupModeEndHdl ) );
+ mpFloatWin->GetDropTarget()->addDropTargetListener(xDrop);
+
+ mpImplWin = VclPtr<ImplWin>::Create( this, (nStyle & (WB_LEFT|WB_RIGHT|WB_CENTER))|WB_NOBORDER );
+ mpImplWin->SetMBDownHdl( LINK( this, ListBox, ImplClickBtnHdl ) );
+ mpImplWin->Show();
+ mpImplWin->GetDropTarget()->addDropTargetListener(xDrop);
+ mpImplWin->SetEdgeBlending(false);
+
+ mpBtn = VclPtr<ImplBtn>::Create( this, WB_NOLIGHTBORDER | WB_RECTSTYLE );
+ ImplInitDropDownButton( mpBtn );
+ mpBtn->SetMBDownHdl( LINK( this, ListBox, ImplClickBtnHdl ) );
+ mpBtn->Show();
+ mpBtn->GetDropTarget()->addDropTargetListener(xDrop);
+ }
+
+ vcl::Window* pLBParent = this;
+ if ( mpFloatWin )
+ pLBParent = mpFloatWin;
+ mpImplLB = VclPtr<ImplListBox>::Create( pLBParent, nStyle&(~WB_BORDER) );
+ mpImplLB->SetSelectHdl( LINK( this, ListBox, ImplSelectHdl ) );
+ mpImplLB->SetScrollHdl( LINK( this, ListBox, ImplScrollHdl ) );
+ mpImplLB->SetCancelHdl( LINK( this, ListBox, ImplCancelHdl ) );
+ mpImplLB->SetDoubleClickHdl( LINK( this, ListBox, ImplDoubleClickHdl ) );
+ mpImplLB->SetFocusHdl( LINK( this, ListBox, ImplFocusHdl ) );
+ mpImplLB->SetListItemSelectHdl( LINK( this, ListBox, ImplListItemSelectHdl ) );
+ mpImplLB->SetPosPixel( Point() );
+ mpImplLB->SetEdgeBlending(false);
+ mpImplLB->Show();
+
+ mpImplLB->GetDropTarget()->addDropTargetListener(xDrop);
+
+ if ( mpFloatWin )
+ {
+ mpFloatWin->SetImplListBox( mpImplLB );
+ mpImplLB->SetSelectionChangedHdl( LINK( this, ListBox, ImplSelectionChangedHdl ) );
+ }
+ else
+ mpImplLB->GetMainWindow()->AllowGrabFocus( true );
+
+ SetCompoundControl( true );
+}
+
+WinBits ListBox::ImplInitStyle( WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ return nStyle;
+}
+
+IMPL_LINK_NOARG(ListBox, ImplSelectHdl, LinkParamNone*, void)
+{
+ bool bPopup = IsInDropDown();
+ if( IsDropDownBox() )
+ {
+ if( !mpImplLB->IsTravelSelect() )
+ {
+ mpFloatWin->EndPopupMode();
+ mpImplWin->GrabFocus();
+ }
+
+ mpImplWin->SetItemPos( GetSelectedEntryPos() );
+ mpImplWin->SetString( GetSelectedEntry() );
+ if( mpImplLB->GetEntryList().HasImages() )
+ {
+ Image aImage = mpImplLB->GetEntryList().GetEntryImage( GetSelectedEntryPos() );
+ mpImplWin->SetImage( aImage );
+ }
+ mpImplWin->Invalidate();
+ }
+
+ if ( ( !IsTravelSelect() || mpImplLB->IsSelectionChanged() ) || ( bPopup && !IsMultiSelectionEnabled() ) )
+ Select();
+}
+
+IMPL_LINK( ListBox, ImplFocusHdl, sal_Int32, nPos, void )
+{
+ CallEventListeners( VclEventId::ListboxFocus, reinterpret_cast<void*>(nPos) );
+}
+
+IMPL_LINK_NOARG( ListBox, ImplListItemSelectHdl, LinkParamNone*, void )
+{
+ CallEventListeners( VclEventId::DropdownSelect );
+}
+
+IMPL_LINK_NOARG(ListBox, ImplScrollHdl, ImplListBox*, void)
+{
+ CallEventListeners( VclEventId::ListboxScrolled );
+}
+
+IMPL_LINK_NOARG(ListBox, ImplCancelHdl, LinkParamNone*, void)
+{
+ if( IsInDropDown() )
+ mpFloatWin->EndPopupMode();
+}
+
+IMPL_LINK( ListBox, ImplSelectionChangedHdl, sal_Int32, nChanged, void )
+{
+ if ( mpImplLB->IsTrackingSelect() )
+ return;
+
+ const ImplEntryList& rEntryList = mpImplLB->GetEntryList();
+ if ( rEntryList.IsEntryPosSelected( nChanged ) )
+ {
+ // FIXME? This should've been turned into an ImplPaintEntry some time ago...
+ if ( nChanged < rEntryList.GetMRUCount() )
+ nChanged = rEntryList.FindEntry( rEntryList.GetEntryText( nChanged ) );
+ mpImplWin->SetItemPos( nChanged );
+ mpImplWin->SetString( rEntryList.GetEntryText( nChanged ) );
+ if( rEntryList.HasImages() )
+ {
+ Image aImage = rEntryList.GetEntryImage( nChanged );
+ mpImplWin->SetImage( aImage );
+ }
+ }
+ else
+ {
+ mpImplWin->SetItemPos( LISTBOX_ENTRY_NOTFOUND );
+ mpImplWin->SetString( OUString() );
+ Image aImage;
+ mpImplWin->SetImage( aImage );
+ }
+ mpImplWin->Invalidate();
+}
+
+IMPL_LINK_NOARG(ListBox, ImplDoubleClickHdl, ImplListBoxWindow*, void)
+{
+ DoubleClick();
+}
+
+IMPL_LINK_NOARG(ListBox, ImplClickBtnHdl, void*, void)
+{
+ if( mpFloatWin->IsInPopupMode() )
+ return;
+
+ CallEventListeners( VclEventId::DropdownPreOpen );
+ mpImplWin->GrabFocus();
+ mpBtn->SetPressed( true );
+ mpFloatWin->StartFloat( true );
+ CallEventListeners( VclEventId::DropdownOpen );
+
+ ImplClearLayoutData();
+ if( mpImplLB )
+ mpImplLB->GetMainWindow()->ImplClearLayoutData();
+ if( mpImplWin )
+ mpImplWin->ImplClearLayoutData();
+}
+
+IMPL_LINK_NOARG(ListBox, ImplPopupModeEndHdl, FloatingWindow*, void)
+{
+ if( mpFloatWin->IsPopupModeCanceled() )
+ {
+ if ( ( mpFloatWin->GetPopupModeStartSaveSelection() != LISTBOX_ENTRY_NOTFOUND )
+ && !IsEntryPosSelected( mpFloatWin->GetPopupModeStartSaveSelection() ) )
+ {
+ mpImplLB->SelectEntry( mpFloatWin->GetPopupModeStartSaveSelection(), true );
+ bool bTravelSelect = mpImplLB->IsTravelSelect();
+ mpImplLB->SetTravelSelect( true );
+
+ VclPtr<vcl::Window> xWindow = this;
+ Select();
+ if ( xWindow->isDisposed() )
+ return;
+
+ mpImplLB->SetTravelSelect( bTravelSelect );
+ }
+ }
+
+ ImplClearLayoutData();
+ if( mpImplLB )
+ mpImplLB->GetMainWindow()->ImplClearLayoutData();
+ if( mpImplWin )
+ mpImplWin->ImplClearLayoutData();
+
+ mpBtn->SetPressed( false );
+ CallEventListeners( VclEventId::DropdownClose );
+}
+
+void ListBox::ToggleDropDown()
+{
+ if( !IsDropDownBox() )
+ return;
+
+ if( mpFloatWin->IsInPopupMode() )
+ mpFloatWin->EndPopupMode();
+ else
+ {
+ CallEventListeners( VclEventId::DropdownPreOpen );
+ mpImplWin->GrabFocus();
+ mpBtn->SetPressed( true );
+ mpFloatWin->StartFloat( true );
+ CallEventListeners( VclEventId::DropdownOpen );
+ }
+}
+
+void ListBox::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetBackground();
+}
+
+void ListBox::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags )
+{
+ mpImplLB->GetMainWindow()->ApplySettings(*pDev);
+
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ vcl::Font aFont = mpImplLB->GetMainWindow()->GetDrawPixelFont( pDev );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ pDev->SetFont( aFont );
+ pDev->SetTextFillColor();
+
+ // Border/Background
+ pDev->SetLineColor();
+ pDev->SetFillColor();
+ bool bBorder = (GetStyle() & WB_BORDER);
+ bool bBackground = IsControlBackground();
+ if ( bBorder || bBackground )
+ {
+ tools::Rectangle aRect( aPos, aSize );
+ if ( bBorder )
+ {
+ ImplDrawFrame( pDev, aRect );
+ }
+ if ( bBackground )
+ {
+ pDev->SetFillColor( GetControlBackground() );
+ pDev->DrawRect( aRect );
+ }
+ }
+
+ // Content
+ if ( nFlags & SystemTextColorFlags::Mono )
+ {
+ pDev->SetTextColor( COL_BLACK );
+ }
+ else
+ {
+ if ( !IsEnabled() )
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ pDev->SetTextColor( rStyleSettings.GetDisableColor() );
+ }
+ else
+ {
+ pDev->SetTextColor( GetTextColor() );
+ }
+ }
+
+ const tools::Long nOnePixel = GetDrawPixel( pDev, 1 );
+ const tools::Long nOffX = 3*nOnePixel;
+ DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
+ tools::Rectangle aTextRect( aPos, aSize );
+
+ if ( GetStyle() & WB_CENTER )
+ nTextStyle |= DrawTextFlags::Center;
+ else if ( GetStyle() & WB_RIGHT )
+ nTextStyle |= DrawTextFlags::Right;
+ else
+ nTextStyle |= DrawTextFlags::Left;
+
+ aTextRect.AdjustLeft(nOffX );
+ aTextRect.AdjustRight( -nOffX );
+
+ if ( IsDropDownBox() )
+ {
+ OUString aText = GetSelectedEntry();
+ tools::Long nTextHeight = pDev->GetTextHeight();
+ tools::Long nTextWidth = pDev->GetTextWidth( aText );
+ tools::Long nOffY = (aSize.Height()-nTextHeight) / 2;
+
+ // Clipping?
+ if ( (nOffY < 0) ||
+ ((nOffY+nTextHeight) > aSize.Height()) ||
+ ((nOffX+nTextWidth) > aSize.Width()) )
+ {
+ tools::Rectangle aClip( aPos, aSize );
+ if ( nTextHeight > aSize.Height() )
+ aClip.AdjustBottom(nTextHeight-aSize.Height()+1 ); // So that HP Printers don't optimize this away
+ pDev->IntersectClipRegion( aClip );
+ }
+
+ pDev->DrawText( aTextRect, aText, nTextStyle );
+ }
+ else
+ {
+ tools::Long nTextHeight = pDev->GetTextHeight();
+ sal_uInt16 nLines = ( nTextHeight > 0 ) ? static_cast<sal_uInt16>(aSize.Height() / nTextHeight) : 1;
+ tools::Rectangle aClip( aPos, aSize );
+
+ pDev->IntersectClipRegion( aClip );
+
+ if ( !nLines )
+ nLines = 1;
+
+ for ( sal_uInt16 n = 0; n < nLines; n++ )
+ {
+ sal_Int32 nEntry = n+mpImplLB->GetTopEntry();
+ bool bSelected = mpImplLB->GetEntryList().IsEntryPosSelected( nEntry );
+ if ( bSelected )
+ {
+ pDev->SetFillColor( COL_BLACK );
+ pDev->DrawRect( tools::Rectangle( Point( aPos.X(), aPos.Y() + n*nTextHeight ),
+ Point( aPos.X() + aSize.Width(), aPos.Y() + (n+1)*nTextHeight + 2*nOnePixel ) ) );
+ pDev->SetFillColor();
+ pDev->SetTextColor( COL_WHITE );
+ }
+
+ aTextRect.SetTop( aPos.Y() + n*nTextHeight );
+ aTextRect.SetBottom( aTextRect.Top() + nTextHeight );
+
+ pDev->DrawText( aTextRect, mpImplLB->GetEntryList().GetEntryText( nEntry ), nTextStyle );
+
+ if ( bSelected )
+ pDev->SetTextColor( COL_BLACK );
+ }
+ }
+
+ pDev->Pop();
+}
+
+void ListBox::GetFocus()
+{
+ if ( mpImplLB )
+ {
+ if( IsDropDownBox() )
+ mpImplWin->GrabFocus();
+ else
+ mpImplLB->GrabFocus();
+ }
+
+ Control::GetFocus();
+}
+
+void ListBox::LoseFocus()
+{
+ if( IsDropDownBox() )
+ {
+ if (mpImplWin)
+ mpImplWin->HideFocus();
+ }
+ else
+ {
+ if (mpImplLB)
+ mpImplLB->HideFocus();
+ }
+
+ Control::LoseFocus();
+}
+
+void ListBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( !((rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))) )
+ return;
+
+ SetBackground(); // Due to a hack in Window::UpdateSettings the background must be reset
+ // otherwise it will overpaint NWF drawn listboxes
+ Resize();
+ mpImplLB->Resize(); // Is not called by ListBox::Resize() if the ImplLB does not change
+
+ if ( mpImplWin )
+ {
+ mpImplWin->GetOutDev()->SetSettings( GetSettings() ); // If not yet set...
+ mpImplWin->ApplySettings(*mpImplWin->GetOutDev());
+
+ mpBtn->GetOutDev()->SetSettings( GetSettings() );
+ ImplInitDropDownButton( mpBtn );
+ }
+
+ if ( IsDropDownBox() )
+ Invalidate();
+}
+
+void ListBox::EnableAutoSize( bool bAuto )
+{
+ mbDDAutoSize = bAuto;
+ if ( mpFloatWin )
+ {
+ if ( bAuto && !mpFloatWin->GetDropDownLineCount() )
+ {
+ // use GetListBoxMaximumLineCount here; before, was on fixed number of five
+ AdaptDropDownLineCountToMaximum();
+ }
+ else if ( !bAuto )
+ {
+ mpFloatWin->SetDropDownLineCount( 0 );
+ }
+ }
+}
+
+void ListBox::SetDropDownLineCount( sal_uInt16 nLines )
+{
+ mnLineCount = nLines;
+ if ( mpFloatWin )
+ mpFloatWin->SetDropDownLineCount( mnLineCount );
+}
+
+void ListBox::AdaptDropDownLineCountToMaximum()
+{
+ // Adapt to maximum allowed number.
+ // Limit for LOK as we can't render outside of the dialog canvas.
+ if (comphelper::LibreOfficeKit::isActive())
+ SetDropDownLineCount(11);
+ else
+ SetDropDownLineCount(GetSettings().GetStyleSettings().GetListBoxMaximumLineCount());
+}
+
+sal_uInt16 ListBox::GetDropDownLineCount() const
+{
+ if ( mpFloatWin )
+ return mpFloatWin->GetDropDownLineCount();
+ return mnLineCount;
+}
+
+void ListBox::setPosSizePixel( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, PosSizeFlags nFlags )
+{
+ if( IsDropDownBox() && ( nFlags & PosSizeFlags::Size ) )
+ {
+ Size aPrefSz = mpFloatWin->GetPrefSize();
+ if ( ( nFlags & PosSizeFlags::Height ) && ( nHeight >= 2*mnDDHeight ) )
+ aPrefSz.setHeight( nHeight-mnDDHeight );
+ if ( nFlags & PosSizeFlags::Width )
+ aPrefSz.setWidth( nWidth );
+ mpFloatWin->SetPrefSize( aPrefSz );
+
+ if (IsAutoSizeEnabled())
+ nHeight = mnDDHeight;
+ }
+
+ Control::setPosSizePixel( nX, nY, nWidth, nHeight, nFlags );
+}
+
+void ListBox::Resize()
+{
+ Size aOutSz = GetOutputSizePixel();
+ if( IsDropDownBox() )
+ {
+ // Initialize the dropdown button size with the standard scrollbar width
+ tools::Long nSBWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
+ tools::Long nBottom = aOutSz.Height();
+
+ // Note: in case of no border, pBorder will actually be this
+ vcl::Window *pBorder = GetWindow( GetWindowType::Border );
+ ImplControlValue aControlValue;
+ Point aPoint;
+ tools::Rectangle aContent, aBound;
+
+ // Use the full extent of the control
+ tools::Rectangle aArea( aPoint, pBorder->GetOutputSizePixel() );
+
+ if ( GetNativeControlRegion( ControlType::Listbox, ControlPart::ButtonDown,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ // Convert back from border space to local coordinates
+ aPoint = pBorder->ScreenToOutputPixel( OutputToScreenPixel( aPoint ) );
+ aContent.Move( -aPoint.X(), -aPoint.Y() );
+
+ // Use the themes drop down size for the button
+ aOutSz.setWidth( aContent.Left() );
+ mpBtn->setPosSizePixel( aContent.Left(), 0, aContent.GetWidth(), nBottom );
+
+ // Adjust the size of the edit field
+ if ( GetNativeControlRegion( ControlType::Listbox, ControlPart::SubEdit,
+ aArea, ControlState::NONE, aControlValue, aBound, aContent) )
+ {
+ // Convert back from border space to local coordinates
+ aContent.Move( -aPoint.X(), -aPoint.Y() );
+
+ // Use the themes drop down size
+ if( ! (GetStyle() & WB_BORDER) && ImplGetSVData()->maNWFData.mbNoFocusRects )
+ {
+ // No border but focus ring behavior -> we have a problem; the
+ // native rect relies on the border to draw the focus
+ // let's do the best we can and center vertically, so it doesn't look
+ // completely wrong.
+ Size aSz( GetOutputSizePixel() );
+ tools::Long nDiff = aContent.Top() - (aSz.Height() - aContent.GetHeight())/2;
+ aContent.AdjustTop( -nDiff );
+ aContent.AdjustBottom( -nDiff );
+ }
+ mpImplWin->SetPosSizePixel( aContent.TopLeft(), aContent.GetSize() );
+ }
+ else
+ mpImplWin->SetSizePixel( aOutSz );
+ }
+ else
+ {
+ nSBWidth = CalcZoom( nSBWidth );
+ mpImplWin->setPosSizePixel( 0, 0, aOutSz.Width() - nSBWidth, aOutSz.Height() );
+ mpBtn->setPosSizePixel( aOutSz.Width() - nSBWidth, 0, nSBWidth, aOutSz.Height() );
+ }
+ }
+ else
+ {
+ mpImplLB->SetSizePixel( aOutSz );
+ }
+
+ // Retain FloatingWindow size even when it's invisible, as we still process KEY_PGUP/DOWN ...
+ if ( mpFloatWin )
+ mpFloatWin->SetSizePixel( mpFloatWin->CalcFloatSize() );
+
+ Control::Resize();
+}
+
+void ListBox::FillLayoutData() const
+{
+ mxLayoutData.emplace();
+ const ImplListBoxWindow* rMainWin = mpImplLB->GetMainWindow();
+ if( mpFloatWin )
+ {
+ // Dropdown mode
+ AppendLayoutData( *mpImplWin );
+ mpImplWin->SetLayoutDataParent( this );
+ if( mpFloatWin->IsReallyVisible() )
+ {
+ AppendLayoutData( *rMainWin );
+ rMainWin->SetLayoutDataParent( this );
+ }
+ }
+ else
+ {
+ AppendLayoutData( *rMainWin );
+ rMainWin->SetLayoutDataParent( this );
+ }
+}
+
+tools::Long ListBox::GetIndexForPoint( const Point& rPoint, sal_Int32& rPos ) const
+{
+ if( !HasLayoutData() )
+ FillLayoutData();
+
+ // Check whether rPoint fits at all
+ tools::Long nIndex = Control::GetIndexForPoint( rPoint );
+ if( nIndex != -1 )
+ {
+ // Point must be either in main list window
+ // or in impl window (dropdown case)
+ ImplListBoxWindow* rMain = mpImplLB->GetMainWindow();
+
+ // Convert coordinates to ImplListBoxWindow pixel coordinate space
+ Point aConvPoint = LogicToPixel( rPoint );
+ AbsoluteScreenPixelPoint aConvPointAbs = OutputToAbsoluteScreenPixel( aConvPoint );
+ aConvPoint = rMain->AbsoluteScreenToOutputPixel( aConvPointAbs );
+ aConvPoint = rMain->PixelToLogic( aConvPoint );
+
+ // Try to find entry
+ sal_Int32 nEntry = rMain->GetEntryPosForPoint( aConvPoint );
+ if( nEntry == LISTBOX_ENTRY_NOTFOUND )
+ {
+ // Not found, maybe dropdown case
+ if( mpImplWin && mpImplWin->IsReallyVisible() )
+ {
+ // Convert to impl window pixel coordinates
+ aConvPoint = LogicToPixel( rPoint );
+ aConvPointAbs = OutputToAbsoluteScreenPixel( aConvPoint );
+ aConvPoint = mpImplWin->AbsoluteScreenToOutputPixel( aConvPointAbs );
+
+ // Check whether converted point is inside impl window
+ Size aImplWinSize = mpImplWin->GetOutputSizePixel();
+ if( aConvPoint.X() >= 0 && aConvPoint.Y() >= 0 && aConvPoint.X() < aImplWinSize.Width() && aConvPoint.Y() < aImplWinSize.Height() )
+ {
+ // Inside the impl window, the position is the current item pos
+ rPos = mpImplWin->GetItemPos();
+ }
+ else
+ nIndex = -1;
+ }
+ else
+ nIndex = -1;
+ }
+ else
+ rPos = nEntry;
+
+ SAL_WARN_IF( nIndex == -1, "vcl", "found index for point, but relative index failed" );
+ }
+
+ // Get line relative index
+ if( nIndex != -1 )
+ nIndex = ToRelativeLineIndex( nIndex );
+
+ return nIndex;
+}
+
+void ListBox::StateChanged( StateChangedType nType )
+{
+ if( nType == StateChangedType::ReadOnly )
+ {
+ if( mpImplWin )
+ mpImplWin->Enable( !IsReadOnly() );
+ if( mpBtn )
+ mpBtn->Enable( !IsReadOnly() );
+ }
+ else if( nType == StateChangedType::Enable )
+ {
+ mpImplLB->Enable( IsEnabled() );
+ if( mpImplWin )
+ {
+ mpImplWin->Enable( IsEnabled() );
+ if ( IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
+ && ! IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown) )
+ {
+ GetWindow( GetWindowType::Border )->Invalidate( InvalidateFlags::NoErase );
+ }
+ else
+ mpImplWin->Invalidate();
+ }
+ if( mpBtn )
+ mpBtn->Enable( IsEnabled() );
+ }
+ else if( nType == StateChangedType::UpdateMode )
+ {
+ mpImplLB->SetUpdateMode( IsUpdateMode() );
+ }
+ else if ( nType == StateChangedType::Zoom )
+ {
+ mpImplLB->SetZoom( GetZoom() );
+ if ( mpImplWin )
+ {
+ mpImplWin->SetZoom( GetZoom() );
+ mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() );
+ mpImplWin->Invalidate();
+ }
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlFont )
+ {
+ mpImplLB->SetControlFont( GetControlFont() );
+ if ( mpImplWin )
+ {
+ mpImplWin->SetControlFont( GetControlFont() );
+ mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() );
+ mpImplWin->Invalidate();
+ }
+ Resize();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ mpImplLB->SetControlForeground( GetControlForeground() );
+ if ( mpImplWin )
+ {
+ mpImplWin->SetControlForeground( GetControlForeground() );
+ mpImplWin->SetTextColor( GetControlForeground() );
+ mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() );
+ mpImplWin->Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ mpImplLB->SetControlBackground( GetControlBackground() );
+ if ( mpImplWin )
+ {
+
+ mpImplWin->SetBackground( GetControlBackground() );
+ mpImplWin->SetControlBackground( GetControlBackground() );
+ mpImplWin->SetFont( mpImplLB->GetMainWindow()->GetFont() );
+ mpImplWin->Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ SetStyle( ImplInitStyle( GetStyle() ) );
+ mpImplLB->GetMainWindow()->EnableSort( ( GetStyle() & WB_SORT ) != 0 );
+ bool bSimpleMode = ( GetStyle() & WB_SIMPLEMODE ) != 0;
+ mpImplLB->SetMultiSelectionSimpleMode( bSimpleMode );
+ }
+ else if( nType == StateChangedType::Mirroring )
+ {
+ if( mpBtn )
+ {
+ mpBtn->EnableRTL( IsRTLEnabled() );
+ ImplInitDropDownButton( mpBtn );
+ }
+ mpImplLB->EnableRTL( IsRTLEnabled() );
+ if( mpImplWin )
+ mpImplWin->EnableRTL( IsRTLEnabled() );
+ Resize();
+ }
+
+ Control::StateChanged( nType );
+}
+
+bool ListBox::PreNotify( NotifyEvent& rNEvt )
+{
+ bool bDone = false;
+ if ( mpImplLB )
+ {
+ if( ( rNEvt.GetType() == NotifyEventType::KEYINPUT ) && ( rNEvt.GetWindow() == mpImplWin ) )
+ {
+ KeyEvent aKeyEvt = *rNEvt.GetKeyEvent();
+ switch( aKeyEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_DOWN:
+ {
+ if( mpFloatWin && !mpFloatWin->IsInPopupMode() &&
+ aKeyEvt.GetKeyCode().IsMod2() )
+ {
+ CallEventListeners( VclEventId::DropdownPreOpen );
+ mpBtn->SetPressed( true );
+ mpFloatWin->StartFloat( false );
+ CallEventListeners( VclEventId::DropdownOpen );
+ bDone = true;
+ }
+ else
+ {
+ bDone = mpImplLB->ProcessKeyInput( aKeyEvt );
+ }
+ }
+ break;
+ case KEY_UP:
+ {
+ if( mpFloatWin && mpFloatWin->IsInPopupMode() &&
+ aKeyEvt.GetKeyCode().IsMod2() )
+ {
+ mpFloatWin->EndPopupMode();
+ bDone = true;
+ }
+ else
+ {
+ bDone = mpImplLB->ProcessKeyInput( aKeyEvt );
+ }
+ }
+ break;
+ case KEY_RETURN:
+ {
+ if( IsInDropDown() )
+ {
+ mpImplLB->ProcessKeyInput( aKeyEvt );
+ bDone = true;
+ }
+ }
+ break;
+
+ default:
+ {
+ bDone = mpImplLB->ProcessKeyInput( aKeyEvt );
+ }
+ }
+ }
+ else if ( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ if ( IsInDropDown() && !HasChildPathFocus( true ) )
+ mpFloatWin->EndPopupMode();
+ }
+ else if ( (rNEvt.GetType() == NotifyEventType::COMMAND) &&
+ (rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) &&
+ (rNEvt.GetWindow() == mpImplWin) )
+ {
+ const Point& rMousePos = rNEvt.GetCommandEvent()->GetMousePosPixel();
+ const tools::Rectangle aWinRect(mpImplWin->GetPosPixel(), mpImplWin->GetSizePixel());
+ const bool bMousePositionedOverWin = aWinRect.Contains(rMousePos);
+
+ MouseWheelBehaviour nWheelBehavior( GetSettings().GetMouseSettings().GetWheelBehavior() );
+ if (bMousePositionedOverWin
+ && ((nWheelBehavior == MouseWheelBehaviour::ALWAYS)
+ || ((nWheelBehavior == MouseWheelBehaviour::FocusOnly) && HasChildPathFocus())))
+ {
+ bDone = mpImplLB->HandleWheelAsCursorTravel(*rNEvt.GetCommandEvent(), *this);
+ }
+ else
+ {
+ bDone = false; // Don't consume this event, let the default handling take it (i.e. scroll the context)
+ }
+ }
+ }
+
+ if (rNEvt.GetType() == NotifyEventType::MOUSEMOVE)
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if (pMouseEvt && (pMouseEvt->IsEnterWindow() || pMouseEvt->IsLeaveWindow()))
+ {
+ // trigger redraw as mouse over state has changed
+ if (IsNativeControlSupported(ControlType::Listbox, ControlPart::Entire)
+ && !IsNativeControlSupported(ControlType::Listbox, ControlPart::ButtonDown))
+ {
+ GetWindow(GetWindowType::Border)->Invalidate(InvalidateFlags::NoErase);
+ }
+ }
+ }
+
+ return bDone || Control::PreNotify( rNEvt );
+}
+
+void ListBox::Select()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ListboxSelect, [this] () { maSelectHdl.Call(*this); } );
+}
+
+void ListBox::DoubleClick()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ListboxDoubleClick, {} );
+}
+
+void ListBox::Clear()
+{
+ if (!mpImplLB)
+ return;
+ mpImplLB->Clear();
+ if( IsDropDownBox() )
+ {
+ mpImplWin->SetItemPos( LISTBOX_ENTRY_NOTFOUND );
+ mpImplWin->SetString( OUString() );
+ Image aImage;
+ mpImplWin->SetImage( aImage );
+ mpImplWin->Invalidate();
+ }
+ CallEventListeners( VclEventId::ListboxItemRemoved, reinterpret_cast<void*>(-1) );
+}
+
+void ListBox::SetNoSelection()
+{
+ mpImplLB->SetNoSelection();
+ if( IsDropDownBox() )
+ {
+ mpImplWin->SetItemPos( LISTBOX_ENTRY_NOTFOUND );
+ mpImplWin->SetString( OUString() );
+ Image aImage;
+ mpImplWin->SetImage( aImage );
+ mpImplWin->Invalidate();
+ }
+}
+
+sal_Int32 ListBox::InsertEntry( const OUString& rStr, sal_Int32 nPos )
+{
+ sal_Int32 nRealPos = mpImplLB->InsertEntry( nPos + mpImplLB->GetEntryList().GetMRUCount(), rStr );
+ nRealPos = sal::static_int_cast<sal_Int32>(nRealPos - mpImplLB->GetEntryList().GetMRUCount());
+ CallEventListeners( VclEventId::ListboxItemAdded, reinterpret_cast<void*>(nRealPos) );
+ return nRealPos;
+}
+
+sal_Int32 ListBox::InsertEntry( const OUString& rStr, const Image& rImage, sal_Int32 nPos )
+{
+ sal_Int32 nRealPos = mpImplLB->InsertEntry( nPos + mpImplLB->GetEntryList().GetMRUCount(), rStr, rImage );
+ nRealPos = sal::static_int_cast<sal_Int32>(nRealPos - mpImplLB->GetEntryList().GetMRUCount());
+ CallEventListeners( VclEventId::ListboxItemAdded, reinterpret_cast<void*>(nRealPos) );
+ return nRealPos;
+}
+
+void ListBox::RemoveEntry( sal_Int32 nPos )
+{
+ mpImplLB->RemoveEntry( nPos + mpImplLB->GetEntryList().GetMRUCount() );
+ CallEventListeners( VclEventId::ListboxItemRemoved, reinterpret_cast<void*>(nPos) );
+}
+
+Image ListBox::GetEntryImage( sal_Int32 nPos ) const
+{
+ if ( mpImplLB && mpImplLB->GetEntryList().HasEntryImage( nPos ) )
+ return mpImplLB->GetEntryList().GetEntryImage( nPos );
+ return Image();
+}
+
+sal_Int32 ListBox::GetEntryPos( std::u16string_view rStr ) const
+{
+ if (!mpImplLB)
+ return LISTBOX_ENTRY_NOTFOUND;
+ sal_Int32 nPos = mpImplLB->GetEntryList().FindEntry( rStr );
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ nPos = nPos - mpImplLB->GetEntryList().GetMRUCount();
+ return nPos;
+}
+
+OUString ListBox::GetEntry( sal_Int32 nPos ) const
+{
+ if (!mpImplLB)
+ return OUString();
+ return mpImplLB->GetEntryList().GetEntryText( nPos + mpImplLB->GetEntryList().GetMRUCount() );
+}
+
+sal_Int32 ListBox::GetEntryCount() const
+{
+ if (!mpImplLB)
+ return 0;
+ return mpImplLB->GetEntryList().GetEntryCount() - mpImplLB->GetEntryList().GetMRUCount();
+}
+
+OUString ListBox::GetSelectedEntry(sal_Int32 nIndex) const
+{
+ return GetEntry( GetSelectedEntryPos( nIndex ) );
+}
+
+sal_Int32 ListBox::GetSelectedEntryCount() const
+{
+ if (!mpImplLB)
+ return 0;
+ return mpImplLB->GetEntryList().GetSelectedEntryCount();
+}
+
+sal_Int32 ListBox::GetSelectedEntryPos( sal_Int32 nIndex ) const
+{
+ if (!mpImplLB)
+ return LISTBOX_ENTRY_NOTFOUND;
+
+ sal_Int32 nPos = mpImplLB->GetEntryList().GetSelectedEntryPos( nIndex );
+ if ( nPos != LISTBOX_ENTRY_NOTFOUND )
+ {
+ if ( nPos < mpImplLB->GetEntryList().GetMRUCount() )
+ nPos = mpImplLB->GetEntryList().FindEntry( mpImplLB->GetEntryList().GetEntryText( nPos ) );
+ nPos = nPos - mpImplLB->GetEntryList().GetMRUCount();
+ }
+ return nPos;
+}
+
+bool ListBox::IsEntryPosSelected( sal_Int32 nPos ) const
+{
+ return mpImplLB->GetEntryList().IsEntryPosSelected( nPos + mpImplLB->GetEntryList().GetMRUCount() );
+}
+
+void ListBox::SelectEntry( std::u16string_view rStr, bool bSelect )
+{
+ SelectEntryPos( GetEntryPos( rStr ), bSelect );
+}
+
+void ListBox::SelectEntryPos( sal_Int32 nPos, bool bSelect )
+{
+ if (!mpImplLB)
+ return;
+
+ if ( 0 <= nPos && nPos < mpImplLB->GetEntryList().GetEntryCount() )
+ {
+ sal_Int32 nCurrentPos = mpImplLB->GetCurrentPos();
+ mpImplLB->SelectEntry( nPos + mpImplLB->GetEntryList().GetMRUCount(), bSelect );
+ //Only when bSelect == true, send both Selection & Focus events
+ if (nCurrentPos != nPos && bSelect)
+ {
+ CallEventListeners( VclEventId::ListboxSelect, reinterpret_cast<void*>(nPos));
+ if (HasFocus())
+ CallEventListeners( VclEventId::ListboxFocus, reinterpret_cast<void*>(nPos));
+ }
+ }
+}
+
+void ListBox::SelectEntriesPos( const std::vector<sal_Int32>& rPositions, bool bSelect )
+{
+ if (!mpImplLB)
+ return;
+
+ bool bCallListeners = false;
+
+ const sal_Int32 nCurrentPos = mpImplLB->GetCurrentPos();
+ const auto nEntryCount = mpImplLB->GetEntryList().GetEntryCount();
+ const auto nMRUCount = mpImplLB->GetEntryList().GetMRUCount();
+
+ for (auto nPos : rPositions)
+ {
+ if (0 <= nPos && nPos < nEntryCount)
+ {
+ mpImplLB->SelectEntry(nPos + nMRUCount, bSelect);
+ if (nCurrentPos != nPos && bSelect)
+ bCallListeners = true;
+ }
+ }
+
+ //Only when bSelect == true, send both Selection & Focus events
+ if (bCallListeners)
+ {
+ CallEventListeners(VclEventId::ListboxSelect);
+ if (HasFocus())
+ CallEventListeners(VclEventId::ListboxFocus);
+ }
+}
+
+void ListBox::SetEntryData( sal_Int32 nPos, void* pNewData )
+{
+ mpImplLB->SetEntryData( nPos + mpImplLB->GetEntryList().GetMRUCount(), pNewData );
+}
+
+void* ListBox::GetEntryData( sal_Int32 nPos ) const
+{
+ return mpImplLB->GetEntryList().GetEntryData( nPos + mpImplLB->GetEntryList().GetMRUCount() );
+}
+
+void ListBox::SetEntryFlags( sal_Int32 nPos, ListBoxEntryFlags nFlags )
+{
+ mpImplLB->SetEntryFlags( nPos + mpImplLB->GetEntryList().GetMRUCount(), nFlags );
+}
+
+void ListBox::SetTopEntry( sal_Int32 nPos )
+{
+ mpImplLB->SetTopEntry( nPos + mpImplLB->GetEntryList().GetMRUCount() );
+}
+
+sal_Int32 ListBox::GetTopEntry() const
+{
+ sal_Int32 nPos = GetEntryCount() ? mpImplLB->GetTopEntry() : LISTBOX_ENTRY_NOTFOUND;
+ if ( nPos < mpImplLB->GetEntryList().GetMRUCount() )
+ nPos = 0;
+ return nPos;
+}
+
+bool ListBox::IsTravelSelect() const
+{
+ return mpImplLB->IsTravelSelect();
+}
+
+bool ListBox::IsInDropDown() const
+{
+ // when the dropdown is dismissed, first mbInPopupMode is set to false, and on the next event iteration then
+ // mbPopupMode is set to false
+ return mpFloatWin && mpFloatWin->IsInPopupMode() && mpFloatWin->ImplIsInPrivatePopupMode();
+}
+
+tools::Rectangle ListBox::GetBoundingRectangle( sal_Int32 nItem ) const
+{
+ tools::Rectangle aRect = mpImplLB->GetMainWindow()->GetBoundingRectangle( nItem );
+ tools::Rectangle aOffset = mpImplLB->GetMainWindow()->GetWindowExtentsRelative( *static_cast<vcl::Window*>(const_cast<ListBox *>(this)) );
+ aRect.Move( aOffset.Left(), aOffset.Top() );
+ return aRect;
+}
+
+void ListBox::EnableMultiSelection( bool bMulti )
+{
+ mpImplLB->EnableMultiSelection( bMulti );
+
+ // WB_SIMPLEMODE:
+ // The MultiListBox behaves just like a normal ListBox
+ // MultiSelection is possible via corresponding additional keys
+ bool bSimpleMode = ( GetStyle() & WB_SIMPLEMODE ) != 0;
+ mpImplLB->SetMultiSelectionSimpleMode( bSimpleMode );
+
+ // In a MultiSelection, we can't see us travelling without focus
+ if ( mpFloatWin )
+ mpImplLB->GetMainWindow()->AllowGrabFocus( bMulti );
+}
+
+bool ListBox::IsMultiSelectionEnabled() const
+{
+ return mpImplLB->IsMultiSelectionEnabled();
+}
+
+void ListBox::SetHighlightColor(const Color& rColor)
+{
+ AllSettings aSettings(GetSettings());
+ StyleSettings aStyle(aSettings.GetStyleSettings());
+ aStyle.SetHighlightColor(rColor);
+ aSettings.SetStyleSettings(aStyle);
+ SetSettings(aSettings);
+
+ mpImplLB->SetHighlightColor(rColor);
+}
+
+void ListBox::SetHighlightTextColor(const Color& rColor)
+{
+ AllSettings aSettings(GetSettings());
+ StyleSettings aStyle(aSettings.GetStyleSettings());
+ aStyle.SetHighlightTextColor(rColor);
+ aSettings.SetStyleSettings(aStyle);
+ SetSettings(aSettings);
+
+ mpImplLB->SetHighlightTextColor(rColor);
+}
+
+Size ListBox::CalcMinimumSize() const
+{
+ Size aSz;
+
+ if (!mpImplLB)
+ return aSz;
+
+ aSz = CalcSubEditSize();
+
+ bool bAddScrollWidth = false;
+
+ if (IsDropDownBox())
+ {
+ aSz.AdjustHeight(4 ); // add a space between entry and border
+ aSz.AdjustWidth(4 ); // add a little breathing space
+ bAddScrollWidth = true;
+ }
+ else
+ bAddScrollWidth = (GetStyle() & WB_VSCROLL) == WB_VSCROLL;
+
+ if (bAddScrollWidth)
+ {
+ // Try native borders; scrollbar size may not be a good indicator
+ // See how large the edit area inside is to estimate what is needed for the dropdown
+ ImplControlValue aControlValue;
+ tools::Rectangle aContent, aBound;
+ Size aTestSize( 100, 20 );
+ tools::Rectangle aArea( Point(), aTestSize );
+ if( GetNativeControlRegion( ControlType::Listbox, ControlPart::SubEdit, aArea, ControlState::NONE,
+ aControlValue, aBound, aContent) )
+ {
+ // use the themes drop down size
+ aSz.AdjustWidth(aTestSize.Width() - aContent.GetWidth() );
+ }
+ else
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ }
+
+ aSz = CalcWindowSize( aSz );
+
+ if (IsDropDownBox()) // Check minimum height of dropdown box
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aRect( Point( 0, 0 ), aSz );
+ tools::Rectangle aContent, aBound;
+ if( GetNativeControlRegion( ControlType::Listbox, ControlPart::Entire, aRect, ControlState::NONE,
+ aControlValue, aBound, aContent) )
+ {
+ if( aBound.GetHeight() > aSz.Height() )
+ aSz.setHeight( aBound.GetHeight() );
+ }
+ }
+
+ return aSz;
+}
+
+Size ListBox::CalcSubEditSize() const
+{
+ Size aSz;
+
+ if (!mpImplLB)
+ return aSz;
+
+ if ( !IsDropDownBox() )
+ aSz = mpImplLB->CalcSize (mnLineCount ? mnLineCount : mpImplLB->GetEntryList().GetEntryCount());
+ else
+ {
+ aSz.setHeight( mpImplLB->GetEntryHeight() );
+ // Size to maximum entry width
+ aSz.setWidth( mpImplLB->GetMaxEntryWidth() );
+
+ if (m_nMaxWidthChars != -1)
+ {
+ tools::Long nMaxWidth = m_nMaxWidthChars * approximate_char_width();
+ aSz.setWidth( std::min(aSz.Width(), nMaxWidth) );
+ }
+
+ // Do not create ultrathin ListBoxes, it doesn't look good
+ if( aSz.Width() < GetSettings().GetStyleSettings().GetScrollBarSize() )
+ aSz.setWidth( GetSettings().GetStyleSettings().GetScrollBarSize() );
+ }
+
+ return aSz;
+}
+
+Size ListBox::GetOptimalSize() const
+{
+ return CalcMinimumSize();
+}
+
+Size ListBox::CalcAdjustedSize( const Size& rPrefSize ) const
+{
+ Size aSz = rPrefSize;
+ sal_Int32 nLeft, nTop, nRight, nBottom;
+ static_cast<vcl::Window*>(const_cast<ListBox *>(this))->GetBorder( nLeft, nTop, nRight, nBottom );
+ aSz.AdjustHeight( -(nTop+nBottom) );
+ if ( !IsDropDownBox() )
+ {
+ tools::Long nEntryHeight = CalcBlockSize( 1, 1 ).Height();
+ tools::Long nLines = aSz.Height() / nEntryHeight;
+ if ( nLines < 1 )
+ nLines = 1;
+ aSz.setHeight( nLines * nEntryHeight );
+ }
+ else
+ {
+ aSz.setHeight( mnDDHeight );
+ }
+ aSz.AdjustHeight(nTop+nBottom );
+
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+Size ListBox::CalcBlockSize( sal_uInt16 nColumns, sal_uInt16 nLines ) const
+{
+ // ScrollBars are shown if needed
+ Size aMinSz = CalcMinimumSize();
+ // aMinSz = ImplCalcOutSz( aMinSz );
+
+ Size aSz;
+
+ // Height
+ if ( nLines )
+ {
+ if ( !IsDropDownBox() )
+ aSz.setHeight( mpImplLB->CalcSize( nLines ).Height() );
+ else
+ aSz.setHeight( mnDDHeight );
+ }
+ else
+ aSz.setHeight( aMinSz.Height() );
+
+ // Width
+ if ( nColumns )
+ aSz.setWidth( nColumns * GetTextWidth( OUString('X') ) );
+ else
+ aSz.setWidth( aMinSz.Width() );
+
+ if ( IsDropDownBox() )
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
+
+ if ( !IsDropDownBox() )
+ {
+ if ( aSz.Width() < aMinSz.Width() )
+ aSz.AdjustHeight(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ if ( aSz.Height() < aMinSz.Height() )
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ }
+
+ aSz = CalcWindowSize( aSz );
+ return aSz;
+}
+
+void ListBox::GetMaxVisColumnsAndLines( sal_uInt16& rnCols, sal_uInt16& rnLines ) const
+{
+ float nCharWidth = approximate_char_width();
+ if ( !IsDropDownBox() )
+ {
+ Size aOutSz = mpImplLB->GetMainWindow()->GetOutputSizePixel();
+ rnCols = static_cast<sal_uInt16>(aOutSz.Width()/nCharWidth);
+ rnLines = static_cast<sal_uInt16>(aOutSz.Height()/mpImplLB->GetEntryHeightWithMargin());
+ }
+ else
+ {
+ Size aOutSz = mpImplWin->GetOutputSizePixel();
+ rnCols = static_cast<sal_uInt16>(aOutSz.Width()/nCharWidth);
+ rnLines = 1;
+ }
+}
+
+void ListBox::SetReadOnly( bool bReadOnly )
+{
+ if ( mpImplLB->IsReadOnly() != bReadOnly )
+ {
+ mpImplLB->SetReadOnly( bReadOnly );
+ CompatStateChanged( StateChangedType::ReadOnly );
+ }
+}
+
+bool ListBox::IsReadOnly() const
+{
+ return mpImplLB->IsReadOnly();
+}
+
+void ListBox::SetSeparatorPos( sal_Int32 n )
+{
+ mpImplLB->SetSeparatorPos( n );
+}
+
+sal_Int32 ListBox::GetSeparatorPos() const
+{
+ return mpImplLB->GetSeparatorPos();
+}
+
+void ListBox::AddSeparator( sal_Int32 n )
+{
+ mpImplLB->AddSeparator( n );
+}
+
+sal_uInt16 ListBox::GetDisplayLineCount() const
+{
+ return mpImplLB->GetDisplayLineCount();
+}
+
+tools::Rectangle ListBox::GetDropDownPosSizePixel() const
+{
+ return mpFloatWin ? mpFloatWin->GetWindowExtentsRelative(*this) : tools::Rectangle();
+}
+
+const Wallpaper& ListBox::GetDisplayBackground() const
+{
+ // !!! Recursion does not occur because the ImplListBox is initialized by default
+ // to a non-transparent color in Window::ImplInitData
+ return mpImplLB->GetDisplayBackground();
+}
+
+void ListBox::setMaxWidthChars(sal_Int32 nWidth)
+{
+ if (nWidth != m_nMaxWidthChars)
+ {
+ m_nMaxWidthChars = nWidth;
+ queue_resize();
+ }
+}
+
+bool ListBox::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "active")
+ SelectEntryPos(rValue.toInt32());
+ else if (rKey == "max-width-chars")
+ setMaxWidthChars(rValue.toInt32());
+ else if (rKey == "can-focus")
+ {
+ // as far as I can see in Gtk, setting a ComboBox as can.focus means
+ // the focus gets stuck in it, so try here to behave like gtk does
+ // with the settings that work, i.e. can.focus of false doesn't
+ // set the hard WB_NOTABSTOP
+ WinBits nBits = GetStyle();
+ nBits &= ~(WB_TABSTOP|WB_NOTABSTOP);
+ if (toBool(rValue))
+ nBits |= WB_TABSTOP;
+ SetStyle(nBits);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+FactoryFunction ListBox::GetUITestFactory() const
+{
+ return ListBoxUIObject::create;
+}
+
+void ListBox::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ Control::DumpAsPropertyTree(rJsonWriter);
+
+ {
+ auto entriesNode = rJsonWriter.startArray("entries");
+ for (int i = 0; i < GetEntryCount(); ++i)
+ {
+ rJsonWriter.putSimpleValue(GetEntry(i));
+ }
+ }
+
+ rJsonWriter.put("selectedCount", GetSelectedEntryCount());
+
+ {
+ auto entriesNode = rJsonWriter.startArray("selectedEntries");
+ for (int i = 0; i < GetSelectedEntryCount(); ++i)
+ {
+ rJsonWriter.putSimpleValue(OUString::number(GetSelectedEntryPos(i)));
+ }
+ }
+}
+
+MultiListBox::MultiListBox( vcl::Window* pParent, WinBits nStyle ) :
+ ListBox( WindowType::MULTILISTBOX )
+{
+ ImplInit( pParent, nStyle );
+ EnableMultiSelection( true );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/longcurr.cxx b/vcl/source/control/longcurr.cxx
new file mode 100644
index 0000000000..1d942d1e56
--- /dev/null
+++ b/vcl/source/control/longcurr.cxx
@@ -0,0 +1,429 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <comphelper/string.hxx>
+#include <tools/bigint.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/toolkit/longcurr.hxx>
+#include <vcl/weldutils.hxx>
+
+#include <unotools/localedatawrapper.hxx>
+
+using namespace ::comphelper;
+
+namespace
+{
+
+BigInt ImplPower10( sal_uInt16 n )
+{
+ sal_uInt16 i;
+ BigInt nValue = 1;
+
+ for ( i=0; i < n; i++ )
+ nValue *= 10;
+
+ return nValue;
+}
+
+OUString ImplGetCurr( const LocaleDataWrapper& rLocaleDataWrapper, const BigInt &rNumber, sal_uInt16 nDigits, std::u16string_view rCurrSymbol, bool bShowThousandSep )
+{
+ SAL_WARN_IF( nDigits >= 10, "vcl", "LongCurrency may only have 9 decimal places" );
+
+ if ( rNumber.IsZero() || static_cast<tools::Long>(rNumber) )
+ return rLocaleDataWrapper.getCurr( static_cast<tools::Long>(rNumber), nDigits, rCurrSymbol, bShowThousandSep );
+
+ BigInt aTmp( ImplPower10( nDigits ) );
+ BigInt aInteger( rNumber );
+ aInteger.Abs();
+ aInteger /= aTmp;
+ BigInt aFraction( rNumber );
+ aFraction.Abs();
+ aFraction %= aTmp;
+ if ( !aInteger.IsZero() )
+ {
+ aFraction += aTmp;
+ aTmp = 1000000000;
+ }
+ if ( rNumber.IsNeg() )
+ aFraction *= -1;
+
+ OUStringBuffer aTemplate(rLocaleDataWrapper.getCurr( static_cast<tools::Long>(aFraction), nDigits, rCurrSymbol, bShowThousandSep ));
+ while( !aInteger.IsZero() )
+ {
+ aFraction = aInteger;
+ aFraction %= aTmp;
+ aInteger /= aTmp;
+ if( !aInteger.IsZero() )
+ aFraction += aTmp;
+
+ OUString aFractionStr = rLocaleDataWrapper.getNum( static_cast<tools::Long>(aFraction), 0 );
+
+ sal_Int32 nSPos = aTemplate.indexOf( '1' );
+ if (nSPos == -1)
+ break;
+ if ( aFractionStr.getLength() == 1 )
+ aTemplate[ nSPos ] = aFractionStr[0];
+ else
+ {
+ aTemplate.remove( nSPos, 1 );
+ aTemplate.insert( nSPos, aFractionStr );
+ }
+ }
+
+ return aTemplate.makeStringAndClear();
+}
+
+bool ImplCurrencyGetValue( const OUString& rStr, BigInt& rValue,
+ sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper )
+{
+ OUString aStr = rStr;
+ OUStringBuffer aStr1;
+ OUStringBuffer aStr2;
+ sal_Int32 nDecPos;
+ bool bNegative = false;
+
+ // On empty string
+ if ( rStr.isEmpty() )
+ return false;
+
+ // Trim leading and trailing spaces
+ aStr = string::strip(aStr, ' ');
+
+ // Find decimal sign's position
+ nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSep() );
+ if (nDecPos < 0 && !rLocaleDataWrapper.getNumDecimalSepAlt().isEmpty())
+ nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSepAlt() );
+
+ if ( nDecPos != -1 )
+ {
+ aStr1 = aStr.subView( 0, nDecPos );
+ aStr2.append(aStr.subView(nDecPos+1));
+ }
+ else
+ aStr1 = aStr;
+
+ // Negative?
+ if ( (aStr[ 0 ] == '(') && (aStr[ aStr.getLength()-1 ] == ')') )
+ bNegative = true;
+ if ( !bNegative )
+ {
+ for (sal_Int32 i=0; i < aStr.getLength(); i++ )
+ {
+ if ( (aStr[ i ] >= '0') && (aStr[ i ] <= '9') )
+ break;
+ else if ( aStr[ i ] == '-' )
+ {
+ bNegative = true;
+ break;
+ }
+ }
+ }
+ if ( !bNegative && !aStr.isEmpty() )
+ {
+ sal_uInt16 nFormat = rLocaleDataWrapper.getCurrNegativeFormat();
+ if ( (nFormat == 3) || (nFormat == 6) ||
+ (nFormat == 7) || (nFormat == 10) )
+ {
+ for (sal_Int32 i = aStr.getLength()-1; i > 0; i++ )
+ {
+ if ( (aStr[ i ] >= '0') && (aStr[ i ] <= '9') )
+ break;
+ else if ( aStr[ i ] == '-' )
+ {
+ bNegative = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // delete unwanted characters
+ for (sal_Int32 i=0; i < aStr1.getLength(); )
+ {
+ if ( (aStr1[ i ] >= '0') && (aStr1[ i ] <= '9') )
+ i++;
+ else
+ aStr1.remove( i, 1 );
+ }
+ for (sal_Int32 i=0; i < aStr2.getLength(); )
+ {
+ if ((aStr2[i] >= '0') && (aStr2[i] <= '9'))
+ ++i;
+ else
+ aStr2.remove(i, 1);
+ }
+
+ if ( aStr1.isEmpty() && aStr2.isEmpty())
+ return false;
+
+ if ( aStr1.isEmpty() )
+ aStr1 = "0";
+ if ( bNegative )
+ aStr1.insert( 0, '-');
+
+ // Cut down decimal part and round while doing so
+ bool bRound = false;
+ if (aStr2.getLength() > nDecDigits)
+ {
+ if (aStr2[nDecDigits] >= '5')
+ bRound = true;
+ string::truncateToLength(aStr2, nDecDigits);
+ }
+ string::padToLength(aStr2, nDecDigits, '0');
+
+ aStr1.append(aStr2);
+ aStr = aStr1.makeStringAndClear();
+
+ // check range
+ BigInt nValue( aStr );
+ if ( bRound )
+ {
+ if ( !bNegative )
+ nValue+=1;
+ else
+ nValue-=1;
+ }
+
+ rValue = nValue;
+
+ return true;
+}
+
+} // namespace
+
+static bool ImplLongCurrencyGetValue( const OUString& rStr, BigInt& rValue,
+ sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper )
+{
+ return ImplCurrencyGetValue( rStr, rValue, nDecDigits, rLocaleDataWrapper );
+}
+
+namespace weld
+{
+ IMPL_LINK_NOARG(LongCurrencyFormatter, FormatOutputHdl, LinkParamNone*, bool)
+ {
+ const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
+ const OUString& rCurrencySymbol = !m_aCurrencySymbol.isEmpty() ? m_aCurrencySymbol : rLocaleDataWrapper.getCurrSymbol();
+ double fValue = GetValue() * weld::SpinButton::Power10(GetDecimalDigits());
+ OUString aText = ImplGetCurr(rLocaleDataWrapper, fValue, GetDecimalDigits(), rCurrencySymbol, m_bThousandSep);
+ ImplSetTextImpl(aText, nullptr);
+ return true;
+ }
+
+ IMPL_LINK(LongCurrencyFormatter, ParseInputHdl, sal_Int64*, result, TriState)
+ {
+ const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
+
+ BigInt value;
+ bool bRet = ImplLongCurrencyGetValue(GetEntryText(), value, GetDecimalDigits(), rLocaleDataWrapper);
+
+ if (bRet)
+ *result = double(value);
+
+ return bRet ? TRISTATE_TRUE : TRISTATE_FALSE;
+ }
+}
+
+bool ImplLongCurrencyReformat( const OUString& rStr, BigInt const & nMin, BigInt const & nMax,
+ sal_uInt16 nDecDigits,
+ const LocaleDataWrapper& rLocaleDataWrapper, OUString& rOutStr,
+ LongCurrencyFormatter const & rFormatter )
+{
+ BigInt nValue;
+ if ( !ImplCurrencyGetValue( rStr, nValue, nDecDigits, rLocaleDataWrapper ) )
+ return true;
+ else
+ {
+ BigInt nTempVal = nValue;
+ if ( nTempVal > nMax )
+ nTempVal = nMax;
+ else if ( nTempVal < nMin )
+ nTempVal = nMin;
+
+ rOutStr = ImplGetCurr( rLocaleDataWrapper, nTempVal, nDecDigits, rFormatter.GetCurrencySymbol(), /*IsUseThousandSep*/true );
+ return true;
+ }
+}
+
+void LongCurrencyFormatter::ImpInit()
+{
+ mnLastValue = 0;
+ mnMin = 0;
+ mnMax = 0x7FFFFFFF;
+ mnMax *= 0x7FFFFFFF;
+ mnDecimalDigits = 0;
+ SetDecimalDigits( 0 );
+}
+
+LongCurrencyFormatter::LongCurrencyFormatter(Edit* pEdit)
+ : FormatterBase(pEdit)
+{
+ ImpInit();
+}
+
+LongCurrencyFormatter::~LongCurrencyFormatter()
+{
+}
+
+OUString const & LongCurrencyFormatter::GetCurrencySymbol() const
+{
+ return GetLocaleDataWrapper().getCurrSymbol();
+}
+
+void LongCurrencyFormatter::SetValue(const BigInt& rNewValue)
+{
+ SetUserValue(rNewValue);
+ SetEmptyFieldValueData( false );
+}
+
+void LongCurrencyFormatter::SetUserValue( BigInt nNewValue )
+{
+ if ( nNewValue > mnMax )
+ nNewValue = mnMax;
+ else if ( nNewValue < mnMin )
+ nNewValue = mnMin;
+ mnLastValue = nNewValue;
+
+ if ( !GetField() )
+ return;
+
+ OUString aStr = ImplGetCurr( GetLocaleDataWrapper(), nNewValue, GetDecimalDigits(), GetCurrencySymbol(), /*UseThousandSep*/true );
+ if ( GetField()->HasFocus() )
+ {
+ Selection aSelection = GetField()->GetSelection();
+ GetField()->SetText( aStr );
+ GetField()->SetSelection( aSelection );
+ }
+ else
+ GetField()->SetText( aStr );
+ MarkToBeReformatted( false );
+}
+
+BigInt LongCurrencyFormatter::GetValue() const
+{
+ if ( !GetField() )
+ return 0;
+
+ BigInt nTempValue;
+ if ( ImplLongCurrencyGetValue( GetField()->GetText(), nTempValue, GetDecimalDigits(), GetLocaleDataWrapper() ) )
+ {
+ if ( nTempValue > mnMax )
+ nTempValue = mnMax;
+ else if ( nTempValue < mnMin )
+ nTempValue = mnMin;
+ return nTempValue;
+ }
+ else
+ return mnLastValue;
+}
+
+void LongCurrencyFormatter::Reformat()
+{
+ if ( !GetField() )
+ return;
+
+ if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() )
+ return;
+
+ OUString aStr;
+ bool bOK = ImplLongCurrencyReformat( GetField()->GetText(), mnMin, mnMax,
+ GetDecimalDigits(), GetLocaleDataWrapper(), aStr, *this );
+ if ( !bOK )
+ return;
+
+ if ( !aStr.isEmpty() )
+ {
+ GetField()->SetText( aStr );
+ MarkToBeReformatted( false );
+ ImplLongCurrencyGetValue( aStr, mnLastValue, GetDecimalDigits(), GetLocaleDataWrapper() );
+ }
+ else
+ SetValue( mnLastValue );
+}
+
+void LongCurrencyFormatter::ReformatAll()
+{
+ Reformat();
+}
+
+void LongCurrencyFormatter::SetDecimalDigits( sal_uInt16 nDigits )
+{
+ if ( nDigits > 9 )
+ nDigits = 9;
+
+ mnDecimalDigits = nDigits;
+ ReformatAll();
+}
+
+
+
+LongCurrencyBox::LongCurrencyBox(vcl::Window* pParent, WinBits nWinStyle)
+ : ComboBox(pParent, nWinStyle)
+ , LongCurrencyFormatter(this)
+{
+ Reformat();
+}
+
+bool LongCurrencyBox::EventNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == NotifyEventType::GETFOCUS )
+ {
+ MarkToBeReformatted( false );
+ }
+ else if( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
+ {
+ if ( MustBeReformatted() )
+ {
+ Reformat();
+ ComboBox::Modify();
+ }
+ }
+ return ComboBox::EventNotify( rNEvt );
+}
+
+void LongCurrencyBox::Modify()
+{
+ MarkToBeReformatted( true );
+ ComboBox::Modify();
+}
+
+void LongCurrencyBox::ReformatAll()
+{
+ OUString aStr;
+ SetUpdateMode( false );
+ const sal_Int32 nEntryCount = GetEntryCount();
+ for ( sal_Int32 i=0; i < nEntryCount; ++i )
+ {
+ ImplLongCurrencyReformat( GetEntry( i ), mnMin, mnMax,
+ GetDecimalDigits(), GetLocaleDataWrapper(),
+ aStr, *this );
+ RemoveEntryAt(i);
+ InsertEntry( aStr, i );
+ }
+ LongCurrencyFormatter::Reformat();
+ SetUpdateMode( true );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/managedmenubutton.cxx b/vcl/source/control/managedmenubutton.cxx
new file mode 100644
index 0000000000..7545dba9b3
--- /dev/null
+++ b/vcl/source/control/managedmenubutton.cxx
@@ -0,0 +1,100 @@
+/* -*- 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/.
+ */
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+
+#include <managedmenubutton.hxx>
+#include <vcl/menu.hxx>
+
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/frame/theDesktop.hpp>
+#include <com/sun/star/frame/thePopupMenuControllerFactory.hpp>
+
+ManagedMenuButton::ManagedMenuButton(vcl::Window* pParent, WinBits nStyle)
+ : MenuButton(pParent, nStyle)
+{
+ SetImageAlign(ImageAlign::Left);
+}
+
+ManagedMenuButton::~ManagedMenuButton()
+{
+ disposeOnce();
+}
+
+void ManagedMenuButton::dispose()
+{
+ css::uno::Reference<css::lang::XComponent> xComponent(m_xPopupController, css::uno::UNO_QUERY);
+ if (xComponent.is())
+ xComponent->dispose();
+
+ m_xPopupMenu.clear();
+ m_xPopupController.clear();
+ MenuButton::dispose();
+}
+
+void ManagedMenuButton::PrepareExecute()
+{
+ if (!GetPopupMenu())
+ SetPopupMenu(VclPtr<PopupMenu>::Create());
+
+ MenuButton::PrepareExecute();
+
+ if (m_xPopupController.is())
+ {
+ m_xPopupController->updatePopupMenu();
+ return;
+ }
+
+ if (!m_xPopupMenu.is())
+ m_xPopupMenu = GetPopupMenu()->CreateMenuInterface();
+
+ // FIXME: get the frame from the parent VclBuilder.
+ css::uno::Reference<css::uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
+ css::uno::Reference<css::frame::XDesktop2> xDesktop(css::frame::theDesktop::get(xContext));
+ css::uno::Reference<css::frame::XFrame> xFrame(xDesktop->getActiveFrame());
+ if (!xFrame.is())
+ return;
+
+ OUString aModuleName;
+ try
+ {
+ css::uno::Reference<css::frame::XModuleManager> xModuleManager(css::frame::ModuleManager::create(xContext));
+ aModuleName = xModuleManager->identify(xFrame);
+ }
+ catch( const css::uno::Exception& )
+ {}
+
+ css::uno::Sequence<css::uno::Any> aArgs {
+ css::uno::Any(comphelper::makePropertyValue("ModuleIdentifier", aModuleName)),
+ css::uno::Any(comphelper::makePropertyValue("Frame", css::uno::Any(xFrame))),
+ css::uno::Any(comphelper::makePropertyValue("InToolbar", css::uno::Any(true)))
+ };
+
+ const OUString aCommand(GetCommand());
+ if (!aCommand.isEmpty() && GetPopupMenu()->GetItemCount() == 0)
+ {
+ css::uno::Reference<css::frame::XUIControllerFactory> xPopupMenuControllerFactory =
+ css::frame::thePopupMenuControllerFactory::get(xContext);
+
+ if (xPopupMenuControllerFactory->hasController(aCommand, aModuleName))
+ m_xPopupController.set(xPopupMenuControllerFactory->createInstanceWithArgumentsAndContext(
+ aCommand, aArgs, xContext), css::uno::UNO_QUERY);
+ }
+
+ // No registered controller found, use one the can handle arbitrary menus (e.g. defined in .ui file).
+ if (!m_xPopupController.is())
+ m_xPopupController.set(xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
+ "com.sun.star.comp.framework.ResourceMenuController", aArgs, xContext), css::uno::UNO_QUERY);
+
+ if (m_xPopupController.is())
+ m_xPopupController->setPopupMenu(m_xPopupMenu);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/control/menubtn.cxx b/vcl/source/control/menubtn.cxx
new file mode 100644
index 0000000000..186340d8e0
--- /dev/null
+++ b/vcl/source/control/menubtn.cxx
@@ -0,0 +1,309 @@
+/* -*- 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/event.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/toolkit/menubtn.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/uitest/eventdescription.hxx>
+#include <menutogglebutton.hxx>
+#include <tools/json_writer.hxx>
+
+namespace
+{
+void collectUIInformation( const OUString& aID, const OUString& aevent , const OUString& akey , const OUString& avalue)
+{
+ EventDescription aDescription;
+ aDescription.aID = aID;
+ aDescription.aParameters = {{ akey , avalue}};
+ aDescription.aAction = aevent;
+ aDescription.aParent = "MainWindow";
+ aDescription.aKeyWord = "MenuButton";
+ UITestLogger::getInstance().logEvent(aDescription);
+}
+}
+
+void MenuButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+
+ PushButton::ImplInit( pParent, nStyle );
+ EnableRTL( AllSettings::GetLayoutRTL() );
+}
+
+void MenuButton::ExecuteMenu()
+{
+ mbStartingMenu = true;
+
+ PrepareExecute();
+
+ if (!mpMenu && !mpFloatingWindow)
+ {
+ mbStartingMenu = false;
+ return;
+ }
+
+ Size aSize = GetSizePixel();
+ SetPressed( true );
+ EndSelection();
+ if (mpMenu)
+ {
+ Point aPos(0, 1);
+ tools::Rectangle aRect(aPos, aSize );
+ mpMenu->Execute(this, aRect, PopupMenuFlags::ExecuteDown);
+
+ if (isDisposed())
+ return;
+
+ mnCurItemId = mpMenu->GetCurItemId();
+ msCurItemIdent = mpMenu->GetCurItemIdent();
+ }
+ else
+ {
+ Point aPos(GetParent()->OutputToScreenPixel(GetPosPixel()));
+ tools::Rectangle aRect(aPos, aSize );
+ FloatWinPopupFlags nFlags = FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus;
+ if (mpFloatingWindow->GetType() == WindowType::FLOATINGWINDOW)
+ static_cast<FloatingWindow*>(mpFloatingWindow.get())->StartPopupMode(aRect, nFlags);
+ else
+ {
+ mpFloatingWindow->EnableDocking();
+ vcl::Window::GetDockingManager()->StartPopupMode(mpFloatingWindow, aRect, nFlags);
+ }
+ }
+
+ Activate();
+
+ mbStartingMenu = false;
+
+ SetPressed(false);
+ OUString aID = get_id(); // tdf#136678 take a copy if we are destroyed by Select callback
+ if (mnCurItemId)
+ {
+ Select();
+ mnCurItemId = 0;
+ msCurItemIdent.clear();
+ }
+ collectUIInformation(aID,"OPENLIST","","");
+}
+
+void MenuButton::CancelMenu()
+{
+ if (!mpMenu && !mpFloatingWindow)
+ return;
+
+ if (mpMenu)
+ {
+ mpMenu->EndExecute();
+ }
+ else
+ {
+ if (mpFloatingWindow->GetType() == WindowType::FLOATINGWINDOW)
+ static_cast<FloatingWindow*>(mpFloatingWindow.get())->EndPopupMode();
+ else
+ vcl::Window::GetDockingManager()->EndPopupMode(mpFloatingWindow);
+ }
+ collectUIInformation(get_id(),"CLOSELIST","","");
+}
+
+bool MenuButton::InPopupMode() const
+{
+ if (mbStartingMenu)
+ return true;
+
+ if (!mpMenu && !mpFloatingWindow)
+ return false;
+
+ if (mpMenu)
+ return PopupMenu::GetActivePopupMenu() == mpMenu;
+ else
+ {
+ if (mpFloatingWindow->GetType() == WindowType::FLOATINGWINDOW)
+ return static_cast<const FloatingWindow*>(mpFloatingWindow.get())->IsInPopupMode();
+ else
+ return vcl::Window::GetDockingManager()->IsInPopupMode(mpFloatingWindow);
+ }
+}
+
+MenuButton::MenuButton( vcl::Window* pParent, WinBits nWinBits )
+ : PushButton(WindowType::MENUBUTTON)
+ , mnCurItemId(0)
+ , mbDelayMenu(false)
+ , mbStartingMenu(false)
+{
+ mnDDStyle = PushButtonDropdownStyle::MenuButton;
+ ImplInit(pParent, nWinBits);
+}
+
+MenuButton::~MenuButton()
+{
+ disposeOnce();
+}
+
+void MenuButton::dispose()
+{
+ mpMenuTimer.reset();
+ mpFloatingWindow.clear();
+ mpMenu.clear();
+ PushButton::dispose();
+}
+
+IMPL_LINK_NOARG(MenuButton, ImplMenuTimeoutHdl, Timer *, void)
+{
+ // See if Button Tracking is still active, as it could've been cancelled earlier
+ if ( IsTracking() )
+ {
+ if ( !(GetStyle() & WB_NOPOINTERFOCUS) )
+ GrabFocus();
+ ExecuteMenu();
+ }
+}
+
+void MenuButton::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ bool bExecute = true;
+ if (mbDelayMenu)
+ {
+ // If the separated dropdown symbol is not hit, delay the popup execution
+ if( rMEvt.GetPosPixel().X() <= ImplGetSeparatorX() )
+ {
+ if ( !mpMenuTimer )
+ {
+ mpMenuTimer.reset(new Timer("MenuTimer"));
+ mpMenuTimer->SetInvokeHandler( LINK( this, MenuButton, ImplMenuTimeoutHdl ) );
+ }
+
+ mpMenuTimer->SetTimeout( MouseSettings::GetActionDelay() );
+ mpMenuTimer->Start();
+
+ PushButton::MouseButtonDown( rMEvt );
+ bExecute = false;
+ }
+ }
+ if( bExecute )
+ {
+ if ( PushButton::ImplHitTestPushButton( this, rMEvt.GetPosPixel() ) )
+ {
+ if ( !(GetStyle() & WB_NOPOINTERFOCUS) )
+ GrabFocus();
+ ExecuteMenu();
+ }
+ }
+}
+
+void MenuButton::KeyInput( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+ sal_uInt16 nCode = aKeyCode.GetCode();
+ if ( (nCode == KEY_DOWN) && aKeyCode.IsMod2() )
+ ExecuteMenu();
+ else if ( !mbDelayMenu &&
+ !aKeyCode.GetModifier() &&
+ ((nCode == KEY_RETURN) || (nCode == KEY_SPACE)) )
+ ExecuteMenu();
+ else
+ PushButton::KeyInput( rKEvt );
+}
+
+void MenuButton::Activate()
+{
+ maActivateHdl.Call( this );
+}
+
+void MenuButton::Select()
+{
+ if (mnCurItemId)
+ collectUIInformation(get_id(),"OPENFROMLIST","POS",OUString::number(mnCurItemId));
+
+ maSelectHdl.Call( this );
+}
+
+void MenuButton::SetPopupMenu(PopupMenu* pNewMenu)
+{
+ if (pNewMenu == mpMenu)
+ return;
+
+ mpMenu = pNewMenu;
+}
+
+void MenuButton::SetPopover(Window* pWindow)
+{
+ if (pWindow == mpFloatingWindow)
+ return;
+
+ mpFloatingWindow = pWindow;
+}
+
+
+FactoryFunction MenuButton::GetUITestFactory() const
+{
+ return MenuButtonUIObject::create;
+}
+
+void MenuButton::SetCurItemId(){
+ mnCurItemId = mpMenu->GetCurItemId();
+ msCurItemIdent = mpMenu->GetCurItemIdent();
+}
+
+void MenuButton::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ PushButton::DumpAsPropertyTree(rJsonWriter);
+
+ if (mpMenu)
+ {
+ auto aMenuNode = rJsonWriter.startArray("menu");
+ for (int i = 0; i < mpMenu->GetItemCount(); i++)
+ {
+ auto aEntryNode = rJsonWriter.startStruct();
+ auto sId = mpMenu->GetItemId(i);
+ rJsonWriter.put("id", mpMenu->GetItemIdent(sId));
+ rJsonWriter.put("text", mpMenu->GetItemText(sId));
+ }
+ }
+}
+
+//class MenuToggleButton ----------------------------------------------------
+
+MenuToggleButton::MenuToggleButton( vcl::Window* pParent, WinBits nWinBits )
+ : MenuButton( pParent, nWinBits )
+{
+}
+
+MenuToggleButton::~MenuToggleButton()
+{
+ disposeOnce();
+}
+
+void MenuToggleButton::SetActive( bool bSel )
+{
+ mbIsActive = bSel;
+}
+
+bool MenuToggleButton::GetActive() const
+{
+ return mbIsActive;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/notebookbar.cxx b/vcl/source/control/notebookbar.cxx
new file mode 100644
index 0000000000..db336b1fe8
--- /dev/null
+++ b/vcl/source/control/notebookbar.cxx
@@ -0,0 +1,375 @@
+/* -*- 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 <utility>
+
+#include <vcl/layout.hxx>
+#include <vcl/notebookbar/notebookbar.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/taskpanelist.hxx>
+#include <vcl/NotebookbarContextControl.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/processfactory.hxx>
+#include <rtl/bootstrap.hxx>
+#include <osl/file.hxx>
+#include <config_folders.h>
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/frame/FrameAction.hpp>
+#include <com/sun/star/ui/ContextChangeEventMultiplexer.hpp>
+#include <comphelper/lok.hxx>
+
+static OUString getCustomizedUIRootDir()
+{
+ OUString sShareLayer("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE(
+ "bootstrap") ":UserInstallation}/user/config/soffice.cfg/");
+ rtl::Bootstrap::expandMacros(sShareLayer);
+ return sShareLayer;
+}
+
+static bool doesFileExist(std::u16string_view sUIDir, std::u16string_view sUIFile)
+{
+ OUString sUri = OUString::Concat(sUIDir) + sUIFile;
+ osl::File file(sUri);
+ return( file.open(0) == osl::FileBase::E_None );
+}
+
+/**
+ * split from the main class since it needs different ref-counting mana
+ */
+class NotebookBarContextChangeEventListener : public ::cppu::WeakImplHelper<css::ui::XContextChangeEventListener, css::frame::XFrameActionListener>
+{
+ bool mbActive;
+ VclPtr<NotebookBar> mpParent;
+ css::uno::Reference<css::frame::XFrame> mxFrame;
+public:
+ NotebookBarContextChangeEventListener(NotebookBar *p, css::uno::Reference<css::frame::XFrame> xFrame) :
+ mbActive(false),
+ mpParent(p),
+ mxFrame(std::move(xFrame))
+ {}
+
+ void setupFrameListener(bool bListen);
+ void setupListener(bool bListen);
+
+ // XContextChangeEventListener
+ virtual void SAL_CALL notifyContextChangeEvent(const css::ui::ContextChangeEventObject& rEvent) override;
+
+ // XFrameActionListener
+ virtual void SAL_CALL frameAction(const css::frame::FrameActionEvent& rEvent) override;
+
+ virtual void SAL_CALL disposing(const ::css::lang::EventObject&) override;
+};
+
+NotebookBar::NotebookBar(Window* pParent, const OUString& rID, const OUString& rUIXMLDescription,
+ const css::uno::Reference<css::frame::XFrame>& rFrame,
+ const NotebookBarAddonsItem& aNotebookBarAddonsItem)
+ : Control(pParent)
+ , m_pEventListener(new NotebookBarContextChangeEventListener(this, rFrame))
+ , m_pViewShell(nullptr)
+ , m_bIsWelded(false)
+ , m_sUIXMLDescription(rUIXMLDescription)
+{
+ m_pEventListener->setupFrameListener(true);
+
+ SetStyle(GetStyle() | WB_DIALOGCONTROL);
+ OUString sUIDir = AllSettings::GetUIRootDir();
+ bool doesCustomizedUIExist = doesFileExist(getCustomizedUIRootDir(), rUIXMLDescription);
+ if ( doesCustomizedUIExist )
+ sUIDir = getCustomizedUIRootDir();
+
+ bool bIsWelded = comphelper::LibreOfficeKit::isActive();
+ if (bIsWelded)
+ {
+ m_bIsWelded = true;
+ m_xVclContentArea = VclPtr<VclVBox>::Create(this);
+ m_xVclContentArea->Show();
+ // now access it using GetMainContainer and set dispose callback with SetDisposeCallback
+ }
+ else
+ {
+ m_pUIBuilder.reset(
+ new VclBuilder(this, sUIDir, rUIXMLDescription, rID, rFrame, true, &aNotebookBarAddonsItem));
+
+ // In the Notebookbar's .ui file must exist control handling context
+ // - implementing NotebookbarContextControl interface with id "ContextContainer"
+ // or "ContextContainerX" where X is a number >= 1
+ NotebookbarContextControl* pContextContainer = nullptr;
+ int i = 0;
+ do
+ {
+ OUString aName = "ContextContainer";
+ if (i)
+ aName += OUString::number(i);
+
+ pContextContainer = dynamic_cast<NotebookbarContextControl*>(m_pUIBuilder->get<Window>(aName));
+ if (pContextContainer)
+ m_pContextContainers.push_back(pContextContainer);
+ i++;
+ }
+ while( pContextContainer != nullptr );
+ }
+
+ UpdateBackground();
+}
+
+void NotebookBar::SetDisposeCallback(const Link<const SfxViewShell*, void> rDisposeCallback, const SfxViewShell* pViewShell)
+{
+ m_rDisposeLink = rDisposeCallback;
+ m_pViewShell = pViewShell;
+}
+
+NotebookBar::~NotebookBar()
+{
+ disposeOnce();
+}
+
+void NotebookBar::dispose()
+{
+ m_pContextContainers.clear();
+ if (m_pSystemWindow && m_pSystemWindow->ImplIsInTaskPaneList(this))
+ m_pSystemWindow->GetTaskPaneList()->RemoveWindow(this);
+ m_pSystemWindow.clear();
+
+ if (m_rDisposeLink.IsSet())
+ m_rDisposeLink.Call(m_pViewShell);
+
+ if (m_bIsWelded)
+ m_xVclContentArea.disposeAndClear();
+ else
+ disposeBuilder();
+
+ m_pEventListener->setupFrameListener(false);
+ m_pEventListener->setupListener(false);
+ m_pEventListener.clear();
+
+ Control::dispose();
+}
+
+bool NotebookBar::PreNotify(NotifyEvent& rNEvt)
+{
+ // capture KeyEvents for taskpane cycling
+ if (rNEvt.GetType() == NotifyEventType::KEYINPUT)
+ {
+ if (m_pSystemWindow)
+ return m_pSystemWindow->PreNotify(rNEvt);
+ }
+ return Window::PreNotify( rNEvt );
+}
+
+Size NotebookBar::GetOptimalSize() const
+{
+ if (isLayoutEnabled(this))
+ return VclContainer::getLayoutRequisition(*GetWindow(GetWindowType::FirstChild));
+
+ return Control::GetOptimalSize();
+}
+
+void NotebookBar::setPosSizePixel(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, PosSizeFlags nFlags)
+{
+ bool bCanHandleSmallerWidth = false;
+ bool bCanHandleSmallerHeight = false;
+
+ bool bIsLayoutEnabled = isLayoutEnabled(this);
+ Window *pChild = GetWindow(GetWindowType::FirstChild);
+
+ if (bIsLayoutEnabled && pChild->GetType() == WindowType::SCROLLWINDOW)
+ {
+ WinBits nStyle = pChild->GetStyle();
+ if (nStyle & (WB_AUTOHSCROLL | WB_HSCROLL))
+ bCanHandleSmallerWidth = true;
+ if (nStyle & (WB_AUTOVSCROLL | WB_VSCROLL))
+ bCanHandleSmallerHeight = true;
+ }
+
+ Size aSize(GetOptimalSize());
+ if (!bCanHandleSmallerWidth)
+ nWidth = std::max(nWidth, aSize.Width());
+ if (!bCanHandleSmallerHeight)
+ nHeight = std::max(nHeight, aSize.Height());
+
+ Control::setPosSizePixel(nX, nY, nWidth, nHeight, nFlags);
+
+ if (bIsLayoutEnabled && (nFlags & PosSizeFlags::Size))
+ VclContainer::setLayoutAllocation(*pChild, Point(0, 0), Size(nWidth, nHeight));
+}
+
+void NotebookBar::Resize()
+{
+ if(m_pUIBuilder && m_pUIBuilder->get_widget_root())
+ {
+ vcl::Window* pWindow = m_pUIBuilder->get_widget_root()->GetChild(0);
+ if (pWindow)
+ {
+ Size aSize = pWindow->GetSizePixel();
+ aSize.setWidth( GetSizePixel().Width() );
+ pWindow->SetSizePixel(aSize);
+ }
+ }
+ if(m_bIsWelded)
+ {
+ vcl::Window* pChild = GetWindow(GetWindowType::FirstChild);
+ assert(pChild);
+ VclContainer::setLayoutAllocation(*pChild, Point(0, 0), GetSizePixel());
+ Control::Resize();
+ }
+ Control::Resize();
+}
+
+void NotebookBar::SetSystemWindow(SystemWindow* pSystemWindow)
+{
+ m_pSystemWindow = pSystemWindow;
+ if (!m_pSystemWindow->ImplIsInTaskPaneList(this))
+ m_pSystemWindow->GetTaskPaneList()->AddWindow(this);
+}
+
+void SAL_CALL NotebookBarContextChangeEventListener::notifyContextChangeEvent(const css::ui::ContextChangeEventObject& rEvent)
+{
+ if (mpParent)
+ {
+ for (NotebookbarContextControl* pControl : mpParent->m_pContextContainers)
+ pControl->SetContext(vcl::EnumContext::GetContextEnum(rEvent.ContextName));
+ }
+}
+
+void NotebookBarContextChangeEventListener::setupListener(bool bListen)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return;
+
+ auto xMultiplexer(css::ui::ContextChangeEventMultiplexer::get(::comphelper::getProcessComponentContext()));
+
+ if (bListen)
+ {
+ try
+ {
+ xMultiplexer->addContextChangeEventListener(this, mxFrame->getController());
+ }
+ catch (const css::uno::Exception&)
+ {
+ }
+ }
+ else
+ xMultiplexer->removeAllContextChangeEventListeners(this);
+
+ mbActive = bListen;
+}
+
+void NotebookBarContextChangeEventListener::setupFrameListener(bool bListen)
+{
+ if (bListen)
+ mxFrame->addFrameActionListener(this);
+ else
+ mxFrame->removeFrameActionListener(this);
+}
+
+void SAL_CALL NotebookBarContextChangeEventListener::frameAction(const css::frame::FrameActionEvent& rEvent)
+{
+ if (!mbActive)
+ return;
+
+ if (rEvent.Action == css::frame::FrameAction_COMPONENT_REATTACHED)
+ {
+ setupListener(true);
+ }
+ else if (rEvent.Action == css::frame::FrameAction_COMPONENT_DETACHING)
+ {
+ setupListener(false);
+ // We don't want to give up on listening; just wait for
+ // another controller to be attached to the frame.
+ mbActive = true;
+ }
+}
+
+void NotebookBar::SetupListener(bool bListen)
+{
+ m_pEventListener->setupListener(bListen);
+}
+
+void SAL_CALL NotebookBarContextChangeEventListener::disposing(const ::css::lang::EventObject&)
+{
+ mpParent.clear();
+}
+
+void NotebookBar::DataChanged(const DataChangedEvent& rDCEvt)
+{
+ UpdateBackground();
+ Control::DataChanged(rDCEvt);
+}
+
+void NotebookBar::StateChanged(const StateChangedType nStateChange )
+{
+ UpdateBackground();
+ Control::StateChanged(nStateChange);
+ Invalidate();
+}
+
+void NotebookBar::UpdateBackground()
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ const BitmapEx& aPersona = rStyleSettings.GetPersonaHeader();
+ Wallpaper aWallpaper(aPersona);
+ aWallpaper.SetStyle(WallpaperStyle::TopRight);
+ if (!aPersona.IsEmpty())
+ {
+ SetBackground(aWallpaper);
+ UpdatePersonaSettings();
+ GetOutDev()->SetSettings( PersonaSettings );
+ }
+ else
+ {
+ SetBackground(rStyleSettings.GetDialogColor());
+ UpdateDefaultSettings();
+ GetOutDev()->SetSettings( DefaultSettings );
+ }
+
+ Invalidate(tools::Rectangle(Point(0,0), GetSizePixel()));
+}
+
+void NotebookBar::UpdateDefaultSettings()
+{
+ AllSettings aAllSettings( GetSettings() );
+ StyleSettings aStyleSet( aAllSettings.GetStyleSettings() );
+
+ ::Color aTextColor = aStyleSet.GetFieldTextColor();
+ aStyleSet.SetDialogTextColor( aTextColor );
+ aStyleSet.SetButtonTextColor( aTextColor );
+ aStyleSet.SetRadioCheckTextColor( aTextColor );
+ aStyleSet.SetGroupTextColor( aTextColor );
+ aStyleSet.SetLabelTextColor( aTextColor );
+ aStyleSet.SetWindowTextColor( aTextColor );
+ aStyleSet.SetTabTextColor(aTextColor);
+ aStyleSet.SetToolTextColor(aTextColor);
+
+ aAllSettings.SetStyleSettings(aStyleSet);
+ DefaultSettings = aAllSettings;
+}
+
+void NotebookBar::UpdatePersonaSettings()
+{
+ AllSettings aAllSettings( GetSettings() );
+ StyleSettings aStyleSet( aAllSettings.GetStyleSettings() );
+
+ ::Color aTextColor = aStyleSet.GetPersonaMenuBarTextColor().value_or(COL_BLACK );
+ aStyleSet.SetDialogTextColor( aTextColor );
+ aStyleSet.SetButtonTextColor( aTextColor );
+ aStyleSet.SetRadioCheckTextColor( aTextColor );
+ aStyleSet.SetGroupTextColor( aTextColor );
+ aStyleSet.SetLabelTextColor( aTextColor );
+ aStyleSet.SetWindowTextColor( aTextColor );
+ aStyleSet.SetTabTextColor(aTextColor);
+ aStyleSet.SetToolTextColor(aTextColor);
+
+ aAllSettings.SetStyleSettings(aStyleSet);
+ PersonaSettings = aAllSettings;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/prgsbar.cxx b/vcl/source/control/prgsbar.cxx
new file mode 100644
index 0000000000..e15c7c055d
--- /dev/null
+++ b/vcl/source/control/prgsbar.cxx
@@ -0,0 +1,239 @@
+/* -*- 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/status.hxx>
+#include <vcl/toolkit/prgsbar.hxx>
+#include <vcl/settings.hxx>
+#include <sal/log.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/idle.hxx>
+
+#define PROGRESSBAR_OFFSET 3
+#define PROGRESSBAR_WIN_OFFSET 2
+
+void ProgressBar::ImplInit()
+{
+ mnPrgsWidth = 0;
+ mnPrgsHeight = 0;
+ mnPercent = 0;
+ mnPercentCount = 0;
+ mbCalcNew = true;
+
+ ImplInitSettings( true, true, true );
+}
+
+static WinBits clearProgressBarBorder( vcl::Window const * pParent, WinBits nOrgStyle, ProgressBar::BarStyle eBarStyle )
+{
+ WinBits nOutStyle = nOrgStyle;
+ if( pParent && (nOrgStyle & WB_BORDER) != 0 )
+ {
+ if (pParent->IsNativeControlSupported(eBarStyle == ProgressBar::BarStyle::Progress
+ ? ControlType::Progress
+ : ControlType::LevelBar,
+ ControlPart::Entire))
+ nOutStyle &= WB_BORDER;
+ }
+ return nOutStyle;
+}
+
+Size ProgressBar::GetOptimalSize() const
+{
+ return meBarStyle == BarStyle::Progress ? Size(150, 20) : Size(150,10);
+}
+
+ProgressBar::ProgressBar( vcl::Window* pParent, WinBits nWinStyle, BarStyle eBarStyle ) :
+ Window( pParent, clearProgressBarBorder( pParent, nWinStyle, eBarStyle ) ),
+ meBarStyle(eBarStyle)
+{
+ SetOutputSizePixel( GetOptimalSize() );
+ ImplInit();
+}
+
+void ProgressBar::ImplInitSettings( bool bFont,
+ bool bForeground, bool bBackground )
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+
+/* FIXME: !!! We do not support text output at the moment
+ if ( bFont )
+ ApplyControlFont(*this, rStyleSettings.GetAppFont());
+*/
+
+ if ( bBackground )
+ {
+ if (!IsControlBackground()
+ && IsNativeControlSupported(meBarStyle == BarStyle::Progress ? ControlType::Progress
+ : ControlType::LevelBar,
+ ControlPart::Entire))
+ {
+ if( GetStyle() & WB_BORDER )
+ SetBorderStyle( WindowBorderStyle::REMOVEBORDER );
+ EnableChildTransparentMode();
+ SetPaintTransparent( true );
+ SetBackground();
+ SetParentClipMode( ParentClipMode::NoClip );
+ }
+ else
+ {
+ Color aColor;
+ if ( IsControlBackground() )
+ aColor = GetControlBackground();
+ else
+ aColor = rStyleSettings.GetFaceColor();
+ SetBackground( aColor );
+ }
+ }
+
+ if ( !(bForeground || bFont) )
+ return;
+
+ Color aColor = rStyleSettings.GetHighlightColor();
+ if ( IsControlForeground() )
+ aColor = GetControlForeground();
+ if ( aColor.IsRGBEqual( GetBackground().GetColor() ) )
+ {
+ if ( aColor.GetLuminance() > 100 )
+ aColor.DecreaseLuminance( 64 );
+ else
+ aColor.IncreaseLuminance( 64 );
+ }
+ GetOutDev()->SetLineColor();
+ GetOutDev()->SetFillColor( aColor );
+/* FIXME: !!! We do not support text output at the moment
+ SetTextColor( aColor );
+ SetTextFillColor();
+*/
+}
+
+void ProgressBar::ImplDrawProgress(vcl::RenderContext& rRenderContext, sal_uInt16 nNewPerc)
+{
+ if (mbCalcNew)
+ {
+ mbCalcNew = false;
+
+ Size aSize(GetOutputSizePixel());
+ mnPrgsHeight = aSize.Height() - (PROGRESSBAR_WIN_OFFSET * 2);
+ mnPrgsWidth = (mnPrgsHeight * 2) / 3;
+ maPos.setY( PROGRESSBAR_WIN_OFFSET );
+ tools::Long nMaxWidth = aSize.Width() - (PROGRESSBAR_WIN_OFFSET * 2) + PROGRESSBAR_OFFSET;
+ sal_uInt16 nMaxCount = static_cast<sal_uInt16>(nMaxWidth / (mnPrgsWidth+PROGRESSBAR_OFFSET));
+ if (nMaxCount <= 1)
+ {
+ nMaxCount = 1;
+ }
+ else
+ {
+ while (((10000 / (10000 / nMaxCount)) * (mnPrgsWidth + PROGRESSBAR_OFFSET)) > nMaxWidth)
+ {
+ nMaxCount--;
+ }
+ }
+ mnPercentCount = 10000 / nMaxCount;
+ nMaxWidth = ((10000 / (10000 / nMaxCount)) * (mnPrgsWidth + PROGRESSBAR_OFFSET)) - PROGRESSBAR_OFFSET;
+ maPos.setX( (aSize.Width() - nMaxWidth) / 2 );
+ }
+
+ ::DrawProgress(
+ this, rRenderContext, maPos, PROGRESSBAR_OFFSET, mnPrgsWidth, mnPrgsHeight,
+ /*nPercent1=*/0, nNewPerc * 100, mnPercentCount, tools::Rectangle(Point(), GetSizePixel()),
+ meBarStyle == BarStyle::Progress ? ControlType::Progress : ControlType::LevelBar);
+}
+
+void ProgressBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ ImplDrawProgress(rRenderContext, mnPercent);
+}
+
+void ProgressBar::Resize()
+{
+ mbCalcNew = true;
+ if ( IsReallyVisible() )
+ Invalidate();
+}
+
+void ProgressBar::SetValue( sal_uInt16 nNewPercent )
+{
+ SAL_WARN_IF( nNewPercent > 100, "vcl", "StatusBar::SetProgressValue(): nPercent > 100" );
+
+ if ( nNewPercent < mnPercent )
+ {
+ mbCalcNew = true;
+ mnPercent = nNewPercent;
+ if ( IsReallyVisible() )
+ {
+ Invalidate();
+ PaintImmediately();
+ }
+ }
+ else if ( mnPercent != nNewPercent )
+ {
+ mnPercent = nNewPercent;
+ Invalidate();
+
+ // Make sure the progressbar is actually painted even if the caller is busy with its task,
+ // so the main loop would not be invoked.
+ Idle aIdle("ProgressBar::SetValue aIdle");
+ aIdle.SetPriority(TaskPriority::POST_PAINT);
+ aIdle.Start();
+ while (aIdle.IsActive() && !Application::IsQuit())
+ {
+ Application::Yield();
+ }
+ }
+}
+
+void ProgressBar::StateChanged( StateChangedType nType )
+{
+/* FIXME: !!! We do not support text output at the moment
+ if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( true, false, false );
+ Invalidate();
+ }
+ else
+*/
+ if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false, true, false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( false, false, true );
+ Invalidate();
+ }
+
+ Window::StateChanged( nType );
+}
+
+void ProgressBar::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ImplInitSettings( true, true, true );
+ Invalidate();
+ }
+
+ Window::DataChanged( rDCEvt );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/quickselectionengine.cxx b/vcl/source/control/quickselectionengine.cxx
new file mode 100644
index 0000000000..777e00e0bc
--- /dev/null
+++ b/vcl/source/control/quickselectionengine.cxx
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/quickselectionengine.hxx>
+#include <vcl/event.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/i18nhelp.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <sal/log.hxx>
+
+#include <optional>
+
+namespace vcl
+{
+
+ struct QuickSelectionEngine_Data
+ {
+ ISearchableStringList& rEntryList;
+ OUString sCurrentSearchString;
+ ::std::optional< sal_Unicode > aSingleSearchChar;
+ Timer aSearchTimeout;
+
+ explicit QuickSelectionEngine_Data( ISearchableStringList& _entryList )
+ :rEntryList( _entryList )
+ ,aSearchTimeout( "vcl::QuickSelectionEngine_Data aSearchTimeout" )
+ {
+ aSearchTimeout.SetTimeout( 2500 );
+ aSearchTimeout.SetInvokeHandler( LINK( this, QuickSelectionEngine_Data, SearchStringTimeout ) );
+ }
+
+ ~QuickSelectionEngine_Data()
+ {
+ aSearchTimeout.Stop();
+ }
+
+ DECL_LINK( SearchStringTimeout, Timer*, void );
+ };
+
+ namespace
+ {
+ void lcl_reset( QuickSelectionEngine_Data& _data )
+ {
+ _data.sCurrentSearchString.clear();
+ _data.aSingleSearchChar.reset();
+ _data.aSearchTimeout.Stop();
+ }
+ }
+
+ IMPL_LINK_NOARG( QuickSelectionEngine_Data, SearchStringTimeout, Timer*, void )
+ {
+ lcl_reset( *this );
+ }
+
+ static StringEntryIdentifier findMatchingEntry( const OUString& _searchString, QuickSelectionEngine_Data const & _engineData )
+ {
+ const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetLocaleI18nHelper();
+ // TODO: do we really need the Window's settings here? The original code used it ...
+
+ OUString sEntryText;
+ // get the "current + 1" entry
+ StringEntryIdentifier pSearchEntry = _engineData.rEntryList.CurrentEntry( sEntryText );
+ if ( pSearchEntry )
+ pSearchEntry = _engineData.rEntryList.NextEntry( pSearchEntry, sEntryText );
+ // loop 'til we find another matching entry
+ StringEntryIdentifier pStartedWith = pSearchEntry;
+ while ( pSearchEntry )
+ {
+ if ( rI18nHelper.MatchString( _searchString, sEntryText ) )
+ break;
+
+ pSearchEntry = _engineData.rEntryList.NextEntry( pSearchEntry, sEntryText );
+ if ( pSearchEntry == pStartedWith )
+ pSearchEntry = nullptr;
+ }
+
+ return pSearchEntry;
+ }
+
+ QuickSelectionEngine::QuickSelectionEngine( ISearchableStringList& _entryList )
+ :m_pData( new QuickSelectionEngine_Data( _entryList ) )
+ {
+ }
+
+ QuickSelectionEngine::~QuickSelectionEngine()
+ {
+ }
+
+ bool QuickSelectionEngine::HandleKeyEvent( const KeyEvent& _keyEvent )
+ {
+ sal_Unicode c = _keyEvent.GetCharCode();
+
+ if ( ( c >= 32 ) && ( c != 127 ) && !_keyEvent.GetKeyCode().IsMod2() )
+ {
+ m_pData->sCurrentSearchString += OUStringChar(c);
+ SAL_INFO( "vcl", "QuickSelectionEngine::HandleKeyEvent: searching for " << m_pData->sCurrentSearchString );
+
+ if ( m_pData->sCurrentSearchString.getLength() == 1 )
+ { // first character in the search -> remember
+ m_pData->aSingleSearchChar = c;
+ }
+ else if ( m_pData->sCurrentSearchString.getLength() > 1 )
+ {
+ if ( !!m_pData->aSingleSearchChar && ( *m_pData->aSingleSearchChar != c ) )
+ // we already have a "single char", but the current one is different -> reset
+ m_pData->aSingleSearchChar.reset();
+ }
+
+ OUString aSearchTemp( m_pData->sCurrentSearchString );
+
+ StringEntryIdentifier pMatchingEntry = findMatchingEntry( aSearchTemp, *m_pData );
+ SAL_INFO( "vcl", "QuickSelectionEngine::HandleKeyEvent: found " << pMatchingEntry );
+ if ( !pMatchingEntry && (aSearchTemp.getLength() > 1) && !!m_pData->aSingleSearchChar )
+ {
+ // if there's only one letter in the search string, use a different search mode
+ aSearchTemp = OUString(*m_pData->aSingleSearchChar);
+ pMatchingEntry = findMatchingEntry( aSearchTemp, *m_pData );
+ }
+
+ if ( pMatchingEntry )
+ {
+ m_pData->rEntryList.SelectEntry( pMatchingEntry );
+ m_pData->aSearchTimeout.Start();
+ }
+ else
+ {
+ lcl_reset( *m_pData );
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ void QuickSelectionEngine::Reset()
+ {
+ lcl_reset( *m_pData );
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/roadmap.cxx b/vcl/source/control/roadmap.cxx
new file mode 100644
index 0000000000..a3d3251cea
--- /dev/null
+++ b/vcl/source/control/roadmap.cxx
@@ -0,0 +1,845 @@
+/* -*- 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 <vector>
+#include <algorithm>
+#include <o3tl/safeint.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/roadmap.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/vclevent.hxx>
+#include <hyperlabel.hxx>
+#include <tools/color.hxx>
+#include <rtl/ustring.hxx>
+
+constexpr tools::Long LABELBASEMAPHEIGHT = 8;
+constexpr tools::Long ROADMAP_INDENT_X = 4;
+constexpr tools::Long ROADMAP_INDENT_Y = 27;
+constexpr tools::Long ROADMAP_ITEM_DISTANCE_Y = 6;
+
+namespace vcl
+{
+
+typedef std::vector< RoadmapItem* > HL_Vector;
+
+//= ColorChanger
+
+namespace {
+
+class IDLabel : public FixedText
+{
+public:
+ IDLabel( vcl::Window* _pParent, WinBits _nWinStyle );
+ virtual void DataChanged( const DataChangedEvent& rDCEvt ) override;
+ virtual void ApplySettings(vcl::RenderContext& rRenderContext) override;
+};
+
+}
+
+class RoadmapItem : public RoadmapTypes
+{
+private:
+ VclPtr<IDLabel> mpID;
+ VclPtr<HyperLabel> mpDescription;
+ const Size m_aItemPlayground;
+
+public:
+ RoadmapItem( ORoadmap& _rParent, const Size& _rItemPlayground );
+ ~RoadmapItem();
+
+ void SetID( sal_Int16 ID );
+ sal_Int16 GetID() const;
+
+ void SetIndex( ItemIndex Index );
+ ItemIndex GetIndex() const;
+
+ void Update( ItemIndex RMIndex, const OUString& _rText );
+
+ void SetPosition( RoadmapItem const * OldHyperLabel );
+
+ void ToggleBackgroundColor( const Color& _rGBColor );
+ void SetInteractive( bool _bInteractive );
+
+ void SetClickHdl( const Link<HyperLabel*,void>& rLink );
+ void Enable( bool bEnable );
+ bool IsEnabled() const;
+ void GrabFocus();
+
+ bool Contains( const vcl::Window* _pWindow ) const;
+
+private:
+ void ImplUpdateIndex( const ItemIndex _nIndex );
+ void ImplUpdatePosSize();
+};
+
+//= RoadmapImpl
+
+class RoadmapImpl : public RoadmapTypes
+{
+protected:
+ const ORoadmap& m_rAntiImpl;
+ Link<LinkParamNone*,void> m_aSelectHdl;
+ BitmapEx m_aPicture;
+ HL_Vector m_aRoadmapSteps;
+ ItemId m_iCurItemID;
+ bool m_bInteractive : 1;
+ bool m_bComplete : 1;
+ Size m_aItemSizePixel;
+public:
+ bool m_bPaintInitialized : 1;
+
+public:
+ explicit RoadmapImpl(const ORoadmap& rAntiImpl)
+ : m_rAntiImpl(rAntiImpl)
+ , m_iCurItemID(-1)
+ , m_bInteractive(true)
+ , m_bComplete(true)
+ , m_bPaintInitialized(false)
+ , InCompleteHyperLabel(nullptr)
+ {}
+
+ RoadmapItem* InCompleteHyperLabel;
+
+ HL_Vector& getHyperLabels()
+ {
+ return m_aRoadmapSteps;
+ }
+
+ void insertHyperLabel(ItemIndex Index, RoadmapItem* _rRoadmapStep)
+ {
+ m_aRoadmapSteps.insert(m_aRoadmapSteps.begin() + Index, _rRoadmapStep);
+ }
+
+ ItemIndex getItemCount() const
+ {
+ return m_aRoadmapSteps.size();
+ }
+
+ void setCurItemID(ItemId i)
+ {
+ m_iCurItemID = i;
+ }
+ ItemId getCurItemID() const
+ {
+ return m_iCurItemID;
+ }
+
+ void setInteractive(const bool _bInteractive)
+ {
+ m_bInteractive = _bInteractive;
+ }
+ bool isInteractive() const
+ {
+ return m_bInteractive;
+ }
+
+ void setComplete(const bool _bComplete)
+ {
+ m_bComplete = _bComplete;
+ }
+ bool isComplete() const
+ {
+ return m_bComplete;
+ }
+
+ void setPicture(const BitmapEx& _rPic)
+ {
+ m_aPicture = _rPic;
+ }
+ const BitmapEx& getPicture() const
+ {
+ return m_aPicture;
+ }
+
+ void setSelectHdl(const Link<LinkParamNone*,void>& _rHdl)
+ {
+ m_aSelectHdl = _rHdl;
+ }
+ const Link<LinkParamNone*,void>& getSelectHdl() const
+ {
+ return m_aSelectHdl;
+ }
+
+ void initItemSize();
+ const Size& getItemSize() const
+ {
+ return m_aItemSizePixel;
+ }
+
+ void removeHyperLabel(ItemIndex Index)
+ {
+ if ((Index > -1) && (Index < getItemCount()))
+ {
+ delete m_aRoadmapSteps[Index];
+ m_aRoadmapSteps.erase(m_aRoadmapSteps.begin() + Index);
+ }
+ }
+};
+
+void RoadmapImpl::initItemSize()
+{
+ Size aLabelSize( m_rAntiImpl.GetOutputSizePixel() );
+ aLabelSize.setHeight( m_rAntiImpl.LogicToPixel(Size(0, LABELBASEMAPHEIGHT), MapMode(MapUnit::MapAppFont)).Height() );
+ aLabelSize.AdjustWidth( -(m_rAntiImpl.LogicToPixel(Size(2 * ROADMAP_INDENT_X, 0), MapMode(MapUnit::MapAppFont)).Width()) );
+ m_aItemSizePixel = aLabelSize;
+}
+
+//= Roadmap
+
+ORoadmap::ORoadmap(vcl::Window* _pParent, WinBits _nWinStyle)
+ : Control(_pParent, _nWinStyle)
+ , m_pImpl(new RoadmapImpl(*this))
+{
+}
+
+void ORoadmap::implInit(vcl::RenderContext& rRenderContext)
+{
+ delete m_pImpl->InCompleteHyperLabel;
+ m_pImpl->InCompleteHyperLabel = nullptr;
+ m_pImpl->setCurItemID(-1);
+ m_pImpl->setComplete(true);
+ m_pImpl->m_bPaintInitialized = true;
+
+ // Roadmap control should be reachable as one unit with a Tab key
+ // the next Tab key should spring out of the control.
+ // To reach it the control itself should get focus and set it
+ // on entries. The entries themself should not be reachable with
+ // the Tab key directly. So each entry should have WB_NOTABSTOP.
+
+ // In other words the creator should create the control with the following
+ // flags:
+ // SetStyle( ( GetStyle() | WB_TABSTOP ) & ~WB_DIALOGCONTROL );
+
+// TODO: if somebody sets a new font from outside (OutputDevice::SetFont), we would have to react
+// on this with calculating a new bold font.
+// Unfortunately, the OutputDevice does not offer a notify mechanism for a changed font.
+// So settings the font from outside is simply a forbidden scenario at the moment
+ rRenderContext.EnableMapMode(false);
+}
+
+ORoadmap::~ORoadmap()
+{
+ disposeOnce();
+}
+
+void ORoadmap::dispose()
+{
+ HL_Vector aItemsCopy = m_pImpl->getHyperLabels();
+ m_pImpl->getHyperLabels().clear();
+ for (auto const& itemCopy : aItemsCopy)
+ {
+ delete itemCopy;
+ }
+ if ( ! m_pImpl->isComplete() )
+ delete m_pImpl->InCompleteHyperLabel;
+ m_pImpl.reset();
+ Control::dispose();
+}
+
+RoadmapTypes::ItemId ORoadmap::GetCurrentRoadmapItemID() const
+{
+ return m_pImpl->getCurItemID();
+}
+
+RoadmapItem* ORoadmap::GetPreviousHyperLabel(ItemIndex Index)
+{
+ RoadmapItem* pOldItem = nullptr;
+ if ( Index > 0 )
+ pOldItem = m_pImpl->getHyperLabels().at( Index - 1 );
+ return pOldItem;
+}
+
+RoadmapItem* ORoadmap::InsertHyperLabel(ItemIndex Index, const OUString& _sLabel, ItemId RMID, bool _bEnabled, bool _bIncomplete)
+{
+ if (m_pImpl->getItemCount() == 0)
+ m_pImpl->initItemSize();
+
+ RoadmapItem* pItem = nullptr;
+ RoadmapItem* pOldItem = GetPreviousHyperLabel( Index );
+
+ pItem = new RoadmapItem( *this, m_pImpl->getItemSize() );
+ if ( _bIncomplete )
+ {
+ pItem->SetInteractive( false );
+ }
+ else
+ {
+ pItem->SetInteractive( m_pImpl->isInteractive() );
+ m_pImpl->insertHyperLabel( Index, pItem );
+ }
+ pItem->SetPosition( pOldItem );
+ pItem->Update( Index, _sLabel );
+ pItem->SetClickHdl(LINK( this, ORoadmap, ImplClickHdl ) );
+ pItem->SetID( RMID );
+ pItem->SetIndex( Index );
+ if (!_bEnabled)
+ pItem->Enable( _bEnabled );
+ return pItem;
+}
+
+void ORoadmap::SetRoadmapBitmap(const BitmapEx& _rBmp)
+{
+ m_pImpl->setPicture( _rBmp );
+ Invalidate( );
+}
+
+void ORoadmap::SetRoadmapInteractive(bool _bInteractive)
+{
+ m_pImpl->setInteractive( _bInteractive );
+
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ for (auto const& item : rItems)
+ {
+ item->SetInteractive( _bInteractive );
+ }
+}
+
+bool ORoadmap::IsRoadmapInteractive() const
+{
+ return m_pImpl->isInteractive();
+}
+
+void ORoadmap::SetRoadmapComplete(bool _bComplete)
+{
+ bool bWasComplete = m_pImpl->isComplete();
+ m_pImpl->setComplete( _bComplete );
+ if (_bComplete)
+ {
+ if (m_pImpl->InCompleteHyperLabel != nullptr)
+ {
+ delete m_pImpl->InCompleteHyperLabel;
+ m_pImpl->InCompleteHyperLabel = nullptr;
+ }
+ }
+ else if (bWasComplete)
+ m_pImpl->InCompleteHyperLabel = InsertHyperLabel(m_pImpl->getItemCount(), "...", -1, true/*bEnabled*/, true/*bIncomplete*/ );
+}
+
+void ORoadmap::UpdatefollowingHyperLabels(ItemIndex _nIndex)
+{
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ if ( _nIndex < static_cast<ItemIndex>(rItems.size()) )
+ {
+ for ( HL_Vector::const_iterator i = rItems.begin() + _nIndex;
+ i != rItems.end();
+ ++i, ++_nIndex
+ )
+ {
+ RoadmapItem* pItem = *i;
+
+ pItem->SetIndex( _nIndex );
+ pItem->SetPosition( GetPreviousHyperLabel( _nIndex ) );
+ }
+
+ }
+ if ( ! m_pImpl->isComplete() )
+ {
+ RoadmapItem* pOldItem = GetPreviousHyperLabel( m_pImpl->getItemCount() );
+ m_pImpl->InCompleteHyperLabel->SetPosition( pOldItem );
+ m_pImpl->InCompleteHyperLabel->Update( m_pImpl->getItemCount(), "..." );
+ }
+}
+
+void ORoadmap::ReplaceRoadmapItem(ItemIndex Index, const OUString& roadmapItem, ItemId RMID, bool _bEnabled)
+{
+ RoadmapItem* pItem = GetByIndex( Index);
+ if ( pItem != nullptr )
+ {
+ pItem->Update( Index, roadmapItem );
+ pItem->SetID( RMID );
+ pItem->Enable( _bEnabled );
+ }
+}
+
+RoadmapTypes::ItemIndex ORoadmap::GetItemCount() const
+{
+ return m_pImpl->getItemCount();
+}
+
+RoadmapTypes::ItemId ORoadmap::GetItemID(ItemIndex _nIndex) const
+{
+ const RoadmapItem* pHyperLabel = GetByIndex( _nIndex );
+ if ( pHyperLabel )
+ return pHyperLabel->GetID();
+ return -1;
+}
+
+void ORoadmap::InsertRoadmapItem(ItemIndex Index, const OUString& RoadmapItem, ItemId _nUniqueId, bool _bEnabled)
+{
+ InsertHyperLabel( Index, RoadmapItem, _nUniqueId, _bEnabled, false/*bIncomplete*/ );
+ // TODO YPos is superfluous, if items are always appended
+ UpdatefollowingHyperLabels( Index + 1 );
+}
+
+void ORoadmap::DeleteRoadmapItem(ItemIndex Index)
+{
+ if ( m_pImpl->getItemCount() > 0 && ( Index > -1) && ( Index < m_pImpl->getItemCount() ) )
+ {
+ m_pImpl->removeHyperLabel( Index );
+ UpdatefollowingHyperLabels( Index );
+ }
+}
+
+bool ORoadmap::IsRoadmapComplete() const
+{
+ return m_pImpl->isComplete();
+}
+
+void ORoadmap::EnableRoadmapItem( ItemId _nItemId, bool _bEnable )
+{
+ RoadmapItem* pItem = GetByID( _nItemId );
+ if ( pItem != nullptr )
+ pItem->Enable( _bEnable );
+}
+
+void ORoadmap::ChangeRoadmapItemLabel( ItemId _nID, const OUString& _sLabel )
+{
+ RoadmapItem* pItem = GetByID( _nID );
+ if ( pItem == nullptr )
+ return;
+
+ pItem->Update( pItem->GetIndex(), _sLabel );
+
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ size_t nPos = 0;
+ for (auto const& item : rItems)
+ {
+ item->SetPosition( GetPreviousHyperLabel(nPos) );
+ ++nPos;
+ }
+}
+
+void ORoadmap::ChangeRoadmapItemID(ItemId _nID, ItemId NewID)
+{
+ RoadmapItem* pItem = GetByID( _nID );
+ if ( pItem != nullptr )
+ pItem->SetID( NewID );
+}
+
+RoadmapItem* ORoadmap::GetByID(ItemId _nID)
+{
+ ItemId nLocID = 0;
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ for (auto const& item : rItems)
+ {
+ nLocID = item->GetID();
+ if ( nLocID == _nID )
+ return item;
+ }
+ return nullptr;
+}
+
+const RoadmapItem* ORoadmap::GetByID(ItemId _nID) const
+{
+ return const_cast< ORoadmap* >( this )->GetByID( _nID );
+}
+
+RoadmapItem* ORoadmap::GetByIndex(ItemIndex _nItemIndex)
+{
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ if ( ( _nItemIndex > -1 ) && ( o3tl::make_unsigned(_nItemIndex) < rItems.size() ) )
+ {
+ return rItems.at( _nItemIndex );
+ }
+ return nullptr;
+}
+
+const RoadmapItem* ORoadmap::GetByIndex(ItemIndex _nItemIndex) const
+{
+ return const_cast< ORoadmap* >( this )->GetByIndex( _nItemIndex );
+}
+
+RoadmapTypes::ItemId ORoadmap::GetNextAvailableItemId(ItemIndex _nNewIndex)
+{
+ ItemIndex searchIndex = ++_nNewIndex;
+ while ( searchIndex < m_pImpl->getItemCount() )
+ {
+ RoadmapItem* pItem = GetByIndex( searchIndex );
+ if ( pItem->IsEnabled() )
+ return pItem->GetID( );
+
+ ++searchIndex;
+ }
+ return -1;
+}
+
+RoadmapTypes::ItemId ORoadmap::GetPreviousAvailableItemId(ItemIndex _nNewIndex)
+{
+ ItemIndex searchIndex = --_nNewIndex;
+ while ( searchIndex > -1 )
+ {
+ RoadmapItem* pItem = GetByIndex( searchIndex );
+ if ( pItem->IsEnabled() )
+ return pItem->GetID( );
+
+ searchIndex--;
+ }
+ return -1;
+}
+
+void ORoadmap::DeselectOldRoadmapItems()
+{
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ for (auto const& item : rItems)
+ {
+ item->ToggleBackgroundColor( COL_TRANSPARENT );
+ }
+}
+
+void ORoadmap::SetItemSelectHdl(const Link<LinkParamNone*,void>& _rHdl)
+{
+ m_pImpl->setSelectHdl(_rHdl);
+}
+
+Link<LinkParamNone*,void> const & ORoadmap::GetItemSelectHdl() const
+{
+ return m_pImpl->getSelectHdl();
+}
+
+void ORoadmap::Select()
+{
+ GetItemSelectHdl().Call( nullptr );
+ CallEventListeners( VclEventId::RoadmapItemSelected );
+}
+
+void ORoadmap::GetFocus()
+{
+ RoadmapItem* pCurHyperLabel = GetByID( GetCurrentRoadmapItemID() );
+ if ( pCurHyperLabel != nullptr )
+ pCurHyperLabel->GrabFocus();
+}
+
+bool ORoadmap::SelectRoadmapItemByID(ItemId _nNewID, bool bGrabFocus)
+{
+ DeselectOldRoadmapItems();
+ RoadmapItem* pItem = GetByID( _nNewID );
+ if ( pItem != nullptr )
+ {
+ if ( pItem->IsEnabled() )
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ pItem->ToggleBackgroundColor( rStyleSettings.GetHighlightColor() ); //HighlightColor
+
+ if (bGrabFocus)
+ pItem->GrabFocus();
+ m_pImpl->setCurItemID(_nNewID);
+
+ Select();
+ return true;
+ }
+ }
+ return false;
+}
+
+void ORoadmap::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& _rRect)
+{
+ if (!m_pImpl->m_bPaintInitialized)
+ implInit(rRenderContext);
+ Control::Paint(rRenderContext, _rRect);
+
+ // draw the bitmap
+ if (!m_pImpl->getPicture().IsEmpty())
+ {
+ Size aBitmapSize = m_pImpl->getPicture().GetSizePixel();
+ Size aMySize(GetOutputSizePixel());
+
+ Point aBitmapPos(aMySize.Width() - aBitmapSize.Width(), aMySize.Height() - aBitmapSize.Height());
+
+ // draw it
+ rRenderContext.DrawBitmapEx( aBitmapPos, m_pImpl->getPicture() );
+ }
+
+ // draw the headline
+ DrawHeadline(rRenderContext);
+}
+
+void ORoadmap::DrawHeadline(vcl::RenderContext& rRenderContext)
+{
+ Point aTextPos = OutputDevice::LogicToLogic(Point(ROADMAP_INDENT_X, 8), GetMapMode(), MapMode(MapUnit::MapAppFont));
+
+ Size aOutputSize(rRenderContext.GetOutputSize());
+
+ // draw it
+ rRenderContext.DrawText(tools::Rectangle(aTextPos, aOutputSize), GetText(),
+ DrawTextFlags::Left | DrawTextFlags::Top | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak);
+ rRenderContext.DrawTextLine(aTextPos, aOutputSize.Width(), STRIKEOUT_NONE, LINESTYLE_SINGLE, LINESTYLE_NONE);
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ rRenderContext.SetLineColor(rStyleSettings.GetFieldTextColor());
+ rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
+}
+
+RoadmapItem* ORoadmap::GetByPointer(vcl::Window const * pWindow)
+{
+ const HL_Vector& rItems = m_pImpl->getHyperLabels();
+ for (auto const& item : rItems)
+ {
+ if ( item->Contains( pWindow ) )
+ return item;
+ }
+ return nullptr;
+}
+
+bool ORoadmap::PreNotify(NotifyEvent& _rNEvt)
+{
+ // capture KeyEvents for taskpane cycling
+ if ( _rNEvt.GetType() == NotifyEventType::KEYINPUT )
+ {
+ vcl::Window* pWindow = _rNEvt.GetWindow();
+ RoadmapItem* pItem = GetByPointer( pWindow );
+ if ( pItem != nullptr )
+ {
+ sal_Int16 nKeyCode = _rNEvt.GetKeyEvent()->GetKeyCode().GetCode();
+ switch( nKeyCode )
+ {
+ case KEY_UP:
+ { // Note: Performance wise this is not optimal, because we search for an ID in the labels
+ // and afterwards we search again for a label with the appropriate ID ->
+ // unnecessarily we search twice!!!
+ ItemId nPrevItemID = GetPreviousAvailableItemId( pItem->GetIndex() );
+ if ( nPrevItemID != -1 )
+ return SelectRoadmapItemByID( nPrevItemID );
+ }
+ break;
+ case KEY_DOWN:
+ {
+ ItemId nNextItemID = GetNextAvailableItemId( pItem->GetIndex() );
+ if ( nNextItemID != -1 )
+ return SelectRoadmapItemByID( nNextItemID );
+ }
+ break;
+ case KEY_SPACE:
+ return SelectRoadmapItemByID( pItem->GetID() );
+ }
+ }
+ }
+ return Window::PreNotify( _rNEvt );
+}
+
+IMPL_LINK(ORoadmap, ImplClickHdl, HyperLabel*, CurHyperLabel, void)
+{
+ SelectRoadmapItemByID( CurHyperLabel->GetID() );
+}
+
+void ORoadmap::DataChanged(const DataChangedEvent& rDCEvt)
+{
+ if (!((( rDCEvt.GetType() == DataChangedEventType::SETTINGS ) ||
+ ( rDCEvt.GetType() == DataChangedEventType::DISPLAY )) &&
+ ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE )))
+ return;
+
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ SetBackground( Wallpaper( rStyleSettings.GetFieldColor() ) );
+ Color aTextColor = rStyleSettings.GetFieldTextColor();
+ vcl::Font aFont = GetFont();
+ aFont.SetColor( aTextColor );
+ SetFont( aFont );
+ RoadmapTypes::ItemId curItemID = GetCurrentRoadmapItemID();
+ RoadmapItem* pLabelItem = GetByID( curItemID );
+ if (pLabelItem != nullptr)
+ {
+ pLabelItem->ToggleBackgroundColor(rStyleSettings.GetHighlightColor());
+ }
+ Invalidate();
+}
+
+void ORoadmap::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ Color aTextColor = rStyleSettings.GetFieldTextColor();
+ vcl::Font aFont = rRenderContext.GetFont();
+ aFont.SetColor(aTextColor);
+ aFont.SetWeight(WEIGHT_BOLD);
+ aFont.SetUnderline(LINESTYLE_SINGLE);
+ rRenderContext.SetFont(aFont);
+ rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
+}
+
+RoadmapItem::RoadmapItem(ORoadmap& _rParent, const Size& _rItemPlayground)
+ : m_aItemPlayground(_rItemPlayground)
+{
+ mpID = VclPtr<IDLabel>::Create( &_rParent, WB_WORDBREAK );
+ mpID->Show();
+ mpDescription = VclPtr<HyperLabel>::Create( &_rParent, WB_NOTABSTOP | WB_WORDBREAK );
+ mpDescription->Show();
+}
+
+RoadmapItem::~RoadmapItem()
+{
+ mpID.disposeAndClear();
+ mpDescription.disposeAndClear();
+}
+
+bool RoadmapItem::Contains(const vcl::Window* _pWindow) const
+{
+ return ( mpID == _pWindow ) || ( mpDescription == _pWindow );
+}
+
+void RoadmapItem::GrabFocus()
+{
+ if ( mpDescription )
+ mpDescription->GrabFocus();
+}
+
+void RoadmapItem::SetInteractive(bool _bInteractive)
+{
+ if ( mpDescription )
+ mpDescription->SetInteractive(_bInteractive);
+}
+
+void RoadmapItem::SetID(sal_Int16 ID)
+{
+ if ( mpDescription )
+ mpDescription->SetID(ID);
+}
+
+sal_Int16 RoadmapItem::GetID() const
+{
+ return mpDescription ? mpDescription->GetID() : sal_Int16(-1);
+}
+
+void RoadmapItem::ImplUpdateIndex(const ItemIndex _nIndex)
+{
+ mpDescription->SetIndex( _nIndex );
+
+ OUString aIDText = OUString::number( _nIndex + 1 ) + ".";
+ mpID->SetText( aIDText );
+
+ // update the geometry of both controls
+ ImplUpdatePosSize();
+}
+
+void RoadmapItem::SetIndex(ItemIndex Index)
+{
+ ImplUpdateIndex(Index);
+}
+
+RoadmapTypes::ItemIndex RoadmapItem::GetIndex() const
+{
+ return mpDescription ? mpDescription->GetIndex() : ItemIndex(-1);
+}
+
+void RoadmapItem::SetPosition(RoadmapItem const * _pOldItem)
+{
+ Point aIDPos;
+ if ( _pOldItem == nullptr )
+ {
+ aIDPos = mpID->LogicToPixel(Point(ROADMAP_INDENT_X, ROADMAP_INDENT_Y), MapMode(MapUnit::MapAppFont));
+ }
+ else
+ {
+ Size aOldSize = _pOldItem->mpDescription->GetSizePixel();
+
+ aIDPos = _pOldItem->mpID->GetPosPixel();
+ aIDPos.AdjustY(aOldSize.Height() );
+ aIDPos.AdjustY(mpID->GetParent()->LogicToPixel( Size( 0, ROADMAP_ITEM_DISTANCE_Y ) ).Height() );
+ }
+ mpID->SetPosPixel( aIDPos );
+
+ sal_Int32 nDescPos = aIDPos.X() + mpID->GetSizePixel().Width();
+ mpDescription->SetPosPixel( Point( nDescPos, aIDPos.Y() ) );
+}
+
+void RoadmapItem::Enable(bool _bEnable)
+{
+ mpID->Enable(_bEnable);
+ mpDescription->Enable(_bEnable);
+}
+
+bool RoadmapItem::IsEnabled() const
+{
+ return mpID->IsEnabled();
+}
+
+void RoadmapItem::ToggleBackgroundColor(const Color& _rGBColor)
+{
+ if (_rGBColor == COL_TRANSPARENT)
+ mpID->SetControlBackground();
+ else
+ mpID->SetControlBackground( mpID->GetSettings().GetStyleSettings().GetHighlightColor() );
+ mpDescription->ToggleBackgroundColor(_rGBColor);
+}
+
+void RoadmapItem::ImplUpdatePosSize()
+{
+ // calculate widths
+ tools::Long nIDWidth = mpID->GetTextWidth( mpID->GetText() );
+ tools::Long nMaxIDWidth = mpID->GetTextWidth( "100." );
+ nIDWidth = ::std::min( nIDWidth, nMaxIDWidth );
+
+ // check how many space the description would need
+ Size aDescriptionSize = mpDescription->CalcMinimumSize( m_aItemPlayground.Width() - nIDWidth );
+
+ // position and size both controls
+ Size aIDSize( nIDWidth, aDescriptionSize.Height() );
+ mpID->SetSizePixel( aIDSize );
+
+ Point aIDPos = mpID->GetPosPixel();
+ mpDescription->SetPosPixel( Point( aIDPos.X() + nIDWidth, aIDPos.Y() ) );
+ mpDescription->SetSizePixel( aDescriptionSize );
+}
+
+void RoadmapItem::Update(ItemIndex RMIndex, const OUString& _rText)
+{
+ // update description label
+ mpDescription->SetLabel( _rText );
+
+ // update the index in both controls, which triggers updating the geometry of both
+ ImplUpdateIndex( RMIndex );
+}
+
+void RoadmapItem::SetClickHdl(const Link<HyperLabel*,void>& rLink)
+{
+ if ( mpDescription )
+ mpDescription->SetClickHdl( rLink);
+}
+
+IDLabel::IDLabel(vcl::Window* _pParent, WinBits _nWinStyle)
+ : FixedText(_pParent, _nWinStyle)
+{
+}
+
+void IDLabel::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ FixedText::ApplySettings(rRenderContext);
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ if (GetControlBackground() == COL_TRANSPARENT)
+ rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
+ else
+ rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
+}
+
+void IDLabel::DataChanged(const DataChangedEvent& rDCEvt)
+{
+ FixedText::DataChanged( rDCEvt );
+
+ if ((( rDCEvt.GetType() == DataChangedEventType::SETTINGS ) ||
+ ( rDCEvt.GetType() == DataChangedEventType::DISPLAY )) &&
+ ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE ))
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ if (GetControlBackground() != COL_TRANSPARENT)
+ SetControlBackground(rStyleSettings.GetHighlightColor());
+ Invalidate();
+ }
+}
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/roadmapwizard.cxx b/vcl/source/control/roadmapwizard.cxx
new file mode 100644
index 0000000000..1b3c9e96b4
--- /dev/null
+++ b/vcl/source/control/roadmapwizard.cxx
@@ -0,0 +1,837 @@
+/* -*- 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/roadmap.hxx>
+#include <tools/debug.hxx>
+#include <tools/json_writer.hxx>
+#include <osl/diagnose.h>
+
+#include <strings.hrc>
+#include <svdata.hxx>
+#include <wizdlg.hxx>
+
+#include <vector>
+
+#include "wizimpldata.hxx"
+#include <uiobject-internal.hxx>
+
+namespace vcl
+{
+ sal_Int32 RoadmapWizardImpl::getStateIndexInPath( WizardTypes::WizardState _nState, const WizardPath& _rPath )
+ {
+ sal_Int32 nStateIndexInPath = 0;
+ bool bFound = false;
+ for (auto const& path : _rPath)
+ {
+ if (path == _nState)
+ {
+ bFound = true;
+ break;
+ }
+ ++nStateIndexInPath;
+ }
+ if (!bFound)
+ nStateIndexInPath = -1;
+ return nStateIndexInPath;
+ }
+
+
+ sal_Int32 RoadmapWizardImpl::getStateIndexInPath( WizardTypes::WizardState _nState, PathId _nPathId )
+ {
+ sal_Int32 nStateIndexInPath = -1;
+ Paths::const_iterator aPathPos = aPaths.find( _nPathId );
+ if ( aPathPos != aPaths.end( ) )
+ nStateIndexInPath = getStateIndexInPath( _nState, aPathPos->second );
+ return nStateIndexInPath;
+ }
+
+
+ sal_Int32 RoadmapWizardImpl::getFirstDifferentIndex( const WizardPath& _rLHS, const WizardPath& _rRHS )
+ {
+ sal_Int32 nMinLength = ::std::min( _rLHS.size(), _rRHS.size() );
+ for ( sal_Int32 nCheck = 0; nCheck < nMinLength; ++nCheck )
+ {
+ if ( _rLHS[ nCheck ] != _rRHS[ nCheck ] )
+ return nCheck;
+ }
+ return nMinLength;
+ }
+
+ //= RoadmapWizard
+ RoadmapWizard::RoadmapWizard(vcl::Window* pParent, WinBits nStyle, InitFlag eFlag)
+ : Dialog(pParent, nStyle, eFlag)
+ , maWizardLayoutIdle("vcl RoadmapWizard maWizardLayoutIdle")
+ , m_pFinish(nullptr)
+ , m_pCancel(nullptr)
+ , m_pNextPage(nullptr)
+ , m_pPrevPage(nullptr)
+ , m_pHelp(nullptr)
+ , m_xWizardImpl(new WizardMachineImplData)
+ , m_xRoadmapImpl(new RoadmapWizardImpl)
+ {
+ mpFirstPage = nullptr;
+ mpFirstBtn = nullptr;
+ mpCurTabPage = nullptr;
+ mpPrevBtn = nullptr;
+ mpNextBtn = nullptr;
+ mpViewWindow = nullptr;
+ mnCurLevel = 0;
+ mbEmptyViewMargin = false;
+ mnLeftAlignCount = 0;
+
+ maWizardLayoutIdle.SetPriority(TaskPriority::RESIZE);
+ maWizardLayoutIdle.SetInvokeHandler( LINK( this, RoadmapWizard, ImplHandleWizardLayoutTimerHdl ) );
+
+ implConstruct(WizardButtonFlags::NEXT | WizardButtonFlags::PREVIOUS | WizardButtonFlags::FINISH | WizardButtonFlags::CANCEL | WizardButtonFlags::HELP);
+
+ SetLeftAlignedButtonCount( 1 );
+ mbEmptyViewMargin = true;
+
+ m_xRoadmapImpl->pRoadmap.disposeAndReset( VclPtr<ORoadmap>::Create( this, WB_TABSTOP ) );
+ m_xRoadmapImpl->pRoadmap->SetText( VclResId( STR_WIZDLG_ROADMAP_TITLE ) );
+ m_xRoadmapImpl->pRoadmap->SetPosPixel( Point( 0, 0 ) );
+ m_xRoadmapImpl->pRoadmap->SetItemSelectHdl( LINK( this, RoadmapWizard, OnRoadmapItemSelected ) );
+
+ Size aRoadmapSize = LogicToPixel(Size(85, 0), MapMode(MapUnit::MapAppFont));
+ aRoadmapSize.setHeight( GetSizePixel().Height() );
+ m_xRoadmapImpl->pRoadmap->SetSizePixel( aRoadmapSize );
+
+ mpViewWindow = m_xRoadmapImpl->pRoadmap;
+ m_xRoadmapImpl->pRoadmap->Show();
+ }
+
+ RoadmapWizardMachine::RoadmapWizardMachine(weld::Window* pParent)
+ : WizardMachine(pParent, WizardButtonFlags::NEXT | WizardButtonFlags::PREVIOUS | WizardButtonFlags::FINISH | WizardButtonFlags::CANCEL | WizardButtonFlags::HELP)
+ , m_pImpl( new RoadmapWizardImpl )
+ {
+ m_xAssistant->connect_jump_page(LINK(this, RoadmapWizardMachine, OnRoadmapItemSelected));
+ }
+
+ void RoadmapWizard::ShowRoadmap(bool bShow)
+ {
+ m_xRoadmapImpl->pRoadmap->Show(bShow);
+ CalcAndSetSize();
+ }
+
+ RoadmapWizard::~RoadmapWizard()
+ {
+ disposeOnce();
+ }
+
+ RoadmapWizardMachine::~RoadmapWizardMachine()
+ {
+ }
+
+ void RoadmapWizard::dispose()
+ {
+ m_xRoadmapImpl.reset();
+
+ m_pFinish.disposeAndClear();
+ m_pCancel.disposeAndClear();
+ m_pNextPage.disposeAndClear();
+ m_pPrevPage.disposeAndClear();
+ m_pHelp.disposeAndClear();
+
+ if (m_xWizardImpl)
+ {
+ for (WizardTypes::WizardState i = 0; i < m_xWizardImpl->nFirstUnknownPage; ++i)
+ {
+ TabPage *pPage = GetPage(i);
+ if (pPage)
+ pPage->disposeOnce();
+ }
+ m_xWizardImpl.reset();
+ }
+
+ maWizardLayoutIdle.Stop();
+
+ // Remove all buttons
+ while ( mpFirstBtn )
+ RemoveButton( mpFirstBtn->mpButton );
+
+ // Remove all pages
+ while ( mpFirstPage )
+ RemovePage( mpFirstPage->mpPage );
+
+ mpCurTabPage.clear();
+ mpPrevBtn.clear();
+ mpNextBtn.clear();
+ mpViewWindow.clear();
+ Dialog::dispose();
+ }
+
+ void RoadmapWizard::SetRoadmapHelpId( const OUString& _rId )
+ {
+ m_xRoadmapImpl->pRoadmap->SetHelpId( _rId );
+ }
+
+ void RoadmapWizard::SetRoadmapBitmap(const BitmapEx& rBmp)
+ {
+ m_xRoadmapImpl->pRoadmap->SetRoadmapBitmap(rBmp);
+ }
+
+ void RoadmapWizardMachine::SetRoadmapHelpId(const OUString& rId)
+ {
+ m_xAssistant->set_page_side_help_id(rId);
+ }
+
+ void RoadmapWizardMachine::declarePath( PathId _nPathId, const WizardPath& _lWizardStates)
+ {
+ m_pImpl->aPaths.emplace( _nPathId, _lWizardStates );
+
+ if ( m_pImpl->aPaths.size() == 1 )
+ // the very first path -> activate it
+ activatePath( _nPathId );
+ else
+ implUpdateRoadmap( );
+ }
+
+ void RoadmapWizardMachine::activatePath( PathId _nPathId, bool _bDecideForIt )
+ {
+ if ( ( _nPathId == m_pImpl->nActivePath ) && ( _bDecideForIt == m_pImpl->bActivePathIsDefinite ) )
+ // nothing to do
+ return;
+
+ // does the given path exist?
+ Paths::const_iterator aNewPathPos = m_pImpl->aPaths.find( _nPathId );
+ DBG_ASSERT( aNewPathPos != m_pImpl->aPaths.end(), "RoadmapWizard::activate: there is no such path!" );
+ if ( aNewPathPos == m_pImpl->aPaths.end() )
+ return;
+
+ // determine the index of the current state in the current path
+ sal_Int32 nCurrentStatePathIndex = -1;
+ if ( m_pImpl->nActivePath != -1 )
+ nCurrentStatePathIndex = m_pImpl->getStateIndexInPath( getCurrentState(), m_pImpl->nActivePath );
+
+ DBG_ASSERT( static_cast<sal_Int32>(aNewPathPos->second.size()) > nCurrentStatePathIndex,
+ "RoadmapWizard::activate: you cannot activate a path which has less states than we've already advanced!" );
+ // If this asserts, this for instance means that we are already in state number, say, 5
+ // of our current path, and the caller tries to activate a path which has less than 5
+ // states
+ if ( static_cast<sal_Int32>(aNewPathPos->second.size()) <= nCurrentStatePathIndex )
+ return;
+
+ // assert that the current and the new path are equal, up to nCurrentStatePathIndex
+ Paths::const_iterator aActivePathPos = m_pImpl->aPaths.find( m_pImpl->nActivePath );
+ if ( aActivePathPos != m_pImpl->aPaths.end() )
+ {
+ if ( RoadmapWizardImpl::getFirstDifferentIndex( aActivePathPos->second, aNewPathPos->second ) <= nCurrentStatePathIndex )
+ {
+ OSL_FAIL( "RoadmapWizard::activate: you cannot activate a path which conflicts with the current one *before* the current state!" );
+ return;
+ }
+ }
+
+ m_pImpl->nActivePath = _nPathId;
+ m_pImpl->bActivePathIsDefinite = _bDecideForIt;
+
+ implUpdateRoadmap( );
+ }
+
+ void RoadmapWizard::implUpdateRoadmap( )
+ {
+ DBG_ASSERT( m_xRoadmapImpl->aPaths.find( m_xRoadmapImpl->nActivePath ) != m_xRoadmapImpl->aPaths.end(),
+ "RoadmapWizard::implUpdateRoadmap: there is no such path!" );
+ const WizardPath& rActivePath( m_xRoadmapImpl->aPaths[ m_xRoadmapImpl->nActivePath ] );
+
+ sal_Int32 nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( getCurrentState(), rActivePath );
+ if (nCurrentStatePathIndex < 0)
+ return;
+
+ // determine up to which index (in the new path) we have to display the items
+ RoadmapTypes::ItemIndex nUpperStepBoundary = static_cast<RoadmapTypes::ItemIndex>(rActivePath.size());
+ bool bIncompletePath = false;
+ if ( !m_xRoadmapImpl->bActivePathIsDefinite )
+ {
+ for (auto const& path : m_xRoadmapImpl->aPaths)
+ {
+ if ( path.first == m_xRoadmapImpl->nActivePath )
+ // it's the path we are just activating -> no need to check anything
+ continue;
+ // the index from which on both paths differ
+ sal_Int32 nDivergenceIndex = RoadmapWizardImpl::getFirstDifferentIndex( rActivePath, path.second );
+ if ( nDivergenceIndex <= nCurrentStatePathIndex )
+ // they differ in an index which we have already left behind us
+ // -> this is no conflict anymore
+ continue;
+
+ // the path conflicts with our new path -> don't activate the
+ // *complete* new path, but only up to the step which is unambiguous
+ nUpperStepBoundary = nDivergenceIndex;
+ bIncompletePath = true;
+ }
+ }
+
+ // now, we have to remove all items after nCurrentStatePathIndex, and insert the items from the active
+ // path, up to (excluding) nUpperStepBoundary
+ RoadmapTypes::ItemIndex nLoopUntil = ::std::max( nUpperStepBoundary, m_xRoadmapImpl->pRoadmap->GetItemCount() );
+ for ( RoadmapTypes::ItemIndex nItemIndex = nCurrentStatePathIndex; nItemIndex < nLoopUntil; ++nItemIndex )
+ {
+ bool bExistentItem = ( nItemIndex < m_xRoadmapImpl->pRoadmap->GetItemCount() );
+ bool bNeedItem = ( nItemIndex < nUpperStepBoundary );
+
+ bool bInsertItem = false;
+ if ( bExistentItem )
+ {
+ if ( !bNeedItem )
+ {
+ while ( nItemIndex < m_xRoadmapImpl->pRoadmap->GetItemCount() )
+ m_xRoadmapImpl->pRoadmap->DeleteRoadmapItem( nItemIndex );
+ break;
+ }
+ else
+ {
+ // there is an item with this index in the roadmap - does it match what is requested by
+ // the respective state in the active path?
+ RoadmapTypes::ItemId nPresentItemId = m_xRoadmapImpl->pRoadmap->GetItemID( nItemIndex );
+ WizardTypes::WizardState nRequiredState = rActivePath[ nItemIndex ];
+ if ( nPresentItemId != nRequiredState )
+ {
+ m_xRoadmapImpl->pRoadmap->DeleteRoadmapItem( nItemIndex );
+ bInsertItem = true;
+ }
+ }
+ }
+ else
+ {
+ DBG_ASSERT( bNeedItem, "RoadmapWizard::implUpdateRoadmap: ehm - none needed, none present - why did the loop not terminate?" );
+ bInsertItem = bNeedItem;
+ }
+
+ WizardTypes::WizardState nState( rActivePath[ nItemIndex ] );
+ if ( bInsertItem )
+ {
+ m_xRoadmapImpl->pRoadmap->InsertRoadmapItem(
+ nItemIndex,
+ getStateDisplayName( nState ),
+ nState,
+ true
+ );
+ }
+
+ const bool bEnable = m_xRoadmapImpl->aDisabledStates.find( nState ) == m_xRoadmapImpl->aDisabledStates.end();
+ m_xRoadmapImpl->pRoadmap->EnableRoadmapItem( m_xRoadmapImpl->pRoadmap->GetItemID( nItemIndex ), bEnable );
+ }
+
+ m_xRoadmapImpl->pRoadmap->SetRoadmapComplete( !bIncompletePath );
+ }
+
+ void RoadmapWizardMachine::implUpdateRoadmap( )
+ {
+
+ DBG_ASSERT( m_pImpl->aPaths.find( m_pImpl->nActivePath ) != m_pImpl->aPaths.end(),
+ "RoadmapWizard::implUpdateRoadmap: there is no such path!" );
+ const WizardPath& rActivePath( m_pImpl->aPaths[ m_pImpl->nActivePath ] );
+
+ sal_Int32 nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( getCurrentState(), rActivePath );
+ if (nCurrentStatePathIndex < 0)
+ return;
+
+ // determine up to which index (in the new path) we have to display the items
+ RoadmapTypes::ItemIndex nUpperStepBoundary = static_cast<RoadmapTypes::ItemIndex>(rActivePath.size());
+ if ( !m_pImpl->bActivePathIsDefinite )
+ {
+ for (auto const& path : m_pImpl->aPaths)
+ {
+ if ( path.first == m_pImpl->nActivePath )
+ // it's the path we are just activating -> no need to check anything
+ continue;
+ // the index from which on both paths differ
+ sal_Int32 nDivergenceIndex = RoadmapWizardImpl::getFirstDifferentIndex( rActivePath, path.second );
+ if ( nDivergenceIndex <= nCurrentStatePathIndex )
+ // they differ in an index which we have already left behind us
+ // -> this is no conflict anymore
+ continue;
+
+ // the path conflicts with our new path -> don't activate the
+ // *complete* new path, but only up to the step which is unambiguous
+ nUpperStepBoundary = nDivergenceIndex;
+ }
+ }
+
+ // can we advance from the current page?
+ bool bCurrentPageCanAdvance = true;
+ BuilderPage* pCurrentPage = GetPage( getCurrentState() );
+ if ( pCurrentPage )
+ {
+ const IWizardPageController* pController = getPageController( GetPage( getCurrentState() ) );
+ OSL_ENSURE( pController != nullptr, "RoadmapWizard::implUpdateRoadmap: no controller for the current page!" );
+ bCurrentPageCanAdvance = !pController || pController->canAdvance();
+ }
+
+ // now, we have to remove all items after nCurrentStatePathIndex, and insert the items from the active
+ // path, up to (excluding) nUpperStepBoundary
+ RoadmapTypes::ItemIndex nRoadmapItems = m_xAssistant->get_n_pages();
+ RoadmapTypes::ItemIndex nLoopUntil = ::std::max( nUpperStepBoundary, nRoadmapItems );
+ for ( RoadmapTypes::ItemIndex nItemIndex = nCurrentStatePathIndex; nItemIndex < nLoopUntil; ++nItemIndex )
+ {
+ bool bExistentItem = ( nItemIndex < nRoadmapItems );
+ bool bNeedItem = ( nItemIndex < nUpperStepBoundary );
+
+ bool bInsertItem = false;
+ if ( bExistentItem )
+ {
+ if ( !bNeedItem )
+ {
+ int nPages = nRoadmapItems;
+ for (int i = nPages - 1; i >= nItemIndex; --i)
+ {
+ m_xAssistant->set_page_title(m_xAssistant->get_page_ident(i), "");
+ --nRoadmapItems;
+ }
+ break;
+ }
+ else
+ {
+ // there is an item with this index in the roadmap - does it match what is requested by
+ // the respective state in the active path?
+ RoadmapTypes::ItemId nPresentItemId = m_xAssistant->get_page_ident(nItemIndex).toInt32();
+ WizardTypes::WizardState nRequiredState = rActivePath[ nItemIndex ];
+ if ( nPresentItemId != nRequiredState )
+ {
+ m_xAssistant->set_page_title(OUString::number(nPresentItemId), "");
+ bInsertItem = true;
+ }
+ }
+ }
+ else
+ {
+ DBG_ASSERT( bNeedItem, "RoadmapWizard::implUpdateRoadmap: ehm - none needed, none present - why did the loop not terminate?" );
+ bInsertItem = bNeedItem;
+ }
+
+ WizardTypes::WizardState nState( rActivePath[ nItemIndex ] );
+
+ if ( bInsertItem )
+ {
+ GetOrCreatePage(nState);
+ }
+
+ OUString sIdent(getPageIdentForState(nState));
+ m_xAssistant->set_page_index(sIdent, nItemIndex);
+ m_xAssistant->set_page_title(sIdent, getStateDisplayName(nState));
+
+ // if the item is *after* the current state, but the current page does not
+ // allow advancing, the disable the state. This relieves derived classes
+ // from disabling all future states just because the current state does not
+ // (yet) allow advancing.
+ const bool bUnconditionedDisable = !bCurrentPageCanAdvance && ( nItemIndex > nCurrentStatePathIndex );
+ const bool bEnable = !bUnconditionedDisable && ( m_pImpl->aDisabledStates.find( nState ) == m_pImpl->aDisabledStates.end() );
+ m_xAssistant->set_page_sensitive(sIdent, bEnable);
+ }
+ }
+
+ WizardTypes::WizardState RoadmapWizard::determineNextState( WizardTypes::WizardState _nCurrentState ) const
+ {
+ sal_Int32 nCurrentStatePathIndex = -1;
+
+ Paths::const_iterator aActivePathPos = m_xRoadmapImpl->aPaths.find( m_xRoadmapImpl->nActivePath );
+ if ( aActivePathPos != m_xRoadmapImpl->aPaths.end() )
+ nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( _nCurrentState, aActivePathPos->second );
+
+ DBG_ASSERT( nCurrentStatePathIndex != -1, "RoadmapWizard::determineNextState: ehm - how can we travel if there is no (valid) active path?" );
+ if ( nCurrentStatePathIndex == -1 )
+ return WZS_INVALID_STATE;
+
+ sal_Int32 nNextStateIndex = nCurrentStatePathIndex + 1;
+
+ while ( ( nNextStateIndex < static_cast<sal_Int32>(aActivePathPos->second.size()) )
+ && ( m_xRoadmapImpl->aDisabledStates.find( aActivePathPos->second[ nNextStateIndex ] ) != m_xRoadmapImpl->aDisabledStates.end() )
+ )
+ {
+ ++nNextStateIndex;
+ }
+
+ if ( nNextStateIndex >= static_cast<sal_Int32>(aActivePathPos->second.size()) )
+ // there is no next state in the current path (at least none which is enabled)
+ return WZS_INVALID_STATE;
+
+ return aActivePathPos->second[ nNextStateIndex ];
+ }
+
+ WizardTypes::WizardState RoadmapWizardMachine::determineNextState( WizardTypes::WizardState _nCurrentState ) const
+ {
+ sal_Int32 nCurrentStatePathIndex = -1;
+
+ Paths::const_iterator aActivePathPos = m_pImpl->aPaths.find( m_pImpl->nActivePath );
+ if ( aActivePathPos != m_pImpl->aPaths.end() )
+ nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( _nCurrentState, aActivePathPos->second );
+
+ DBG_ASSERT( nCurrentStatePathIndex != -1, "RoadmapWizard::determineNextState: ehm - how can we travel if there is no (valid) active path?" );
+ if ( nCurrentStatePathIndex == -1 )
+ return WZS_INVALID_STATE;
+
+ sal_Int32 nNextStateIndex = nCurrentStatePathIndex + 1;
+
+ while ( ( nNextStateIndex < static_cast<sal_Int32>(aActivePathPos->second.size()) )
+ && ( m_pImpl->aDisabledStates.find( aActivePathPos->second[ nNextStateIndex ] ) != m_pImpl->aDisabledStates.end() )
+ )
+ {
+ ++nNextStateIndex;
+ }
+
+ if ( nNextStateIndex >= static_cast<sal_Int32>(aActivePathPos->second.size()) )
+ // there is no next state in the current path (at least none which is enabled)
+ return WZS_INVALID_STATE;
+
+ return aActivePathPos->second[ nNextStateIndex ];
+ }
+
+ bool RoadmapWizardMachine::canAdvance() const
+ {
+ if ( !m_pImpl->bActivePathIsDefinite )
+ {
+ // check how many paths are still allowed
+ const WizardPath& rActivePath( m_pImpl->aPaths[ m_pImpl->nActivePath ] );
+
+ // if current path has only the base item, it is not possible to proceed without activating another path
+ if(rActivePath.size()<=1)
+ return false;
+
+ sal_Int32 nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( getCurrentState(), rActivePath );
+
+ size_t nPossiblePaths(0);
+ for (auto const& path : m_pImpl->aPaths)
+ {
+ // the index from which on both paths differ
+ sal_Int32 nDivergenceIndex = RoadmapWizardImpl::getFirstDifferentIndex( rActivePath, path.second );
+
+ if ( nDivergenceIndex > nCurrentStatePathIndex )
+ // this path is still a possible path
+ nPossiblePaths += 1;
+ }
+
+ // if we have more than one path which is still possible, then we assume
+ // to always have a next state. Though there might be scenarios where this
+ // is not true, but this is too sophisticated (means not really needed) right now.
+ if ( nPossiblePaths > 1 )
+ return true;
+ }
+
+ const WizardPath& rPath = m_pImpl->aPaths[ m_pImpl->nActivePath ];
+ return *rPath.rbegin() != getCurrentState();
+ }
+
+ void RoadmapWizardMachine::updateTravelUI()
+ {
+ WizardMachine::updateTravelUI();
+
+ // disable the "Previous" button if all states in our history are disabled
+ std::vector< WizardTypes::WizardState > aHistory;
+ getStateHistory( aHistory );
+ bool bHaveEnabledState = false;
+ for (auto const& state : aHistory)
+ {
+ if ( isStateEnabled(state) )
+ {
+ bHaveEnabledState = true;
+ break;
+ }
+ }
+
+ enableButtons( WizardButtonFlags::PREVIOUS, bHaveEnabledState );
+
+ implUpdateRoadmap();
+ }
+
+ IMPL_LINK_NOARG(RoadmapWizard, OnRoadmapItemSelected, LinkParamNone*, void)
+ {
+ RoadmapTypes::ItemId nCurItemId = m_xRoadmapImpl->pRoadmap->GetCurrentRoadmapItemID();
+ if ( nCurItemId == getCurrentState() )
+ // nothing to do
+ return;
+
+ if ( isTravelingSuspended() )
+ return;
+
+ RoadmapWizardTravelSuspension aTravelGuard( *this );
+
+ sal_Int32 nCurrentIndex = m_xRoadmapImpl->getStateIndexInPath( getCurrentState(), m_xRoadmapImpl->nActivePath );
+ sal_Int32 nNewIndex = m_xRoadmapImpl->getStateIndexInPath( nCurItemId, m_xRoadmapImpl->nActivePath );
+
+ DBG_ASSERT( ( nCurrentIndex != -1 ) && ( nNewIndex != -1 ),
+ "RoadmapWizard::OnRoadmapItemSelected: something's wrong here!" );
+ if ( ( nCurrentIndex == -1 ) || ( nNewIndex == -1 ) )
+ {
+ return;
+ }
+
+ bool bResult = true;
+ if ( nNewIndex > nCurrentIndex )
+ {
+ bResult = skipUntil( static_cast<WizardTypes::WizardState>(nCurItemId) );
+ WizardTypes::WizardState nTemp = static_cast<WizardTypes::WizardState>(nCurItemId);
+ while( nTemp )
+ {
+ if( m_xRoadmapImpl->aDisabledStates.find( --nTemp ) != m_xRoadmapImpl->aDisabledStates.end() )
+ removePageFromHistory( nTemp );
+ }
+ }
+ else
+ bResult = skipBackwardUntil( static_cast<WizardTypes::WizardState>(nCurItemId) );
+
+ if ( !bResult )
+ m_xRoadmapImpl->pRoadmap->SelectRoadmapItemByID( getCurrentState() );
+ }
+
+ IMPL_LINK(RoadmapWizardMachine, OnRoadmapItemSelected, const OUString&, rCurItemId, bool)
+ {
+ WizardTypes::WizardState nSelectedState = getStateFromPageIdent(rCurItemId);
+
+ if (nSelectedState == getCurrentState())
+ // nothing to do
+ return false;
+
+ if ( isTravelingSuspended() )
+ return false;
+
+ WizardTravelSuspension aTravelGuard( *this );
+
+ sal_Int32 nCurrentIndex = m_pImpl->getStateIndexInPath( getCurrentState(), m_pImpl->nActivePath );
+ sal_Int32 nNewIndex = m_pImpl->getStateIndexInPath( nSelectedState, m_pImpl->nActivePath );
+
+ DBG_ASSERT( ( nCurrentIndex != -1 ) && ( nNewIndex != -1 ),
+ "RoadmapWizard::OnRoadmapItemSelected: something's wrong here!" );
+ if ( ( nCurrentIndex == -1 ) || ( nNewIndex == -1 ) )
+ {
+ return false;
+ }
+
+ bool bResult = true;
+ if ( nNewIndex > nCurrentIndex )
+ {
+ bResult = skipUntil(nSelectedState);
+ WizardTypes::WizardState nTemp = nSelectedState;
+ while( nTemp )
+ {
+ if( m_pImpl->aDisabledStates.find( --nTemp ) != m_pImpl->aDisabledStates.end() )
+ removePageFromHistory( nTemp );
+ }
+ }
+ else
+ bResult = skipBackwardUntil(nSelectedState);
+
+ return bResult;
+ }
+
+ void RoadmapWizard::enterState(WizardTypes::WizardState /*nState*/)
+ {
+ // synchronize the roadmap
+ implUpdateRoadmap( );
+ m_xRoadmapImpl->pRoadmap->SelectRoadmapItemByID( getCurrentState() );
+ }
+
+ void RoadmapWizardMachine::enterState( WizardTypes::WizardState _nState )
+ {
+ WizardMachine::enterState( _nState );
+
+ // synchronize the roadmap
+ implUpdateRoadmap();
+ }
+
+ OUString RoadmapWizard::getStateDisplayName( WizardTypes::WizardState _nState ) const
+ {
+ OUString sDisplayName;
+
+ StateDescriptions::const_iterator pos = m_xRoadmapImpl->aStateDescriptors.find( _nState );
+ OSL_ENSURE( pos != m_xRoadmapImpl->aStateDescriptors.end(),
+ "RoadmapWizard::getStateDisplayName: no default implementation available for this state!" );
+ if ( pos != m_xRoadmapImpl->aStateDescriptors.end() )
+ sDisplayName = pos->second.first;
+
+ return sDisplayName;
+ }
+
+ OUString RoadmapWizardMachine::getStateDisplayName( WizardTypes::WizardState _nState ) const
+ {
+ OUString sDisplayName;
+
+ StateDescriptions::const_iterator pos = m_pImpl->aStateDescriptors.find( _nState );
+ OSL_ENSURE( pos != m_pImpl->aStateDescriptors.end(),
+ "RoadmapWizard::getStateDisplayName: no default implementation available for this state!" );
+ if ( pos != m_pImpl->aStateDescriptors.end() )
+ sDisplayName = pos->second.first;
+
+ return sDisplayName;
+ }
+
+ VclPtr<TabPage> RoadmapWizard::createPage( WizardTypes::WizardState _nState )
+ {
+ VclPtr<TabPage> pPage;
+
+ StateDescriptions::const_iterator pos = m_xRoadmapImpl->aStateDescriptors.find( _nState );
+ OSL_ENSURE( pos != m_xRoadmapImpl->aStateDescriptors.end(),
+ "RoadmapWizard::createPage: no default implementation available for this state!" );
+ if ( pos != m_xRoadmapImpl->aStateDescriptors.end() )
+ {
+ RoadmapPageFactory pFactory = pos->second.second;
+ pPage = (*pFactory)( *this );
+ }
+
+ return pPage;
+ }
+
+ void RoadmapWizardMachine::enableState( WizardTypes::WizardState _nState, bool _bEnable )
+ {
+ // remember this (in case the state appears in the roadmap later on)
+ if ( _bEnable )
+ m_pImpl->aDisabledStates.erase( _nState );
+ else
+ {
+ m_pImpl->aDisabledStates.insert( _nState );
+ removePageFromHistory( _nState );
+ }
+
+ // if the state is currently in the roadmap, reflect it's new status
+ m_xAssistant->set_page_sensitive(getPageIdentForState(_nState), _bEnable);
+ }
+
+ bool RoadmapWizardMachine::knowsState( WizardTypes::WizardState i_nState ) const
+ {
+ for (auto const& path : m_pImpl->aPaths)
+ {
+ for (auto const& state : path.second)
+ {
+ if ( state == i_nState )
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool RoadmapWizardMachine::isStateEnabled( WizardTypes::WizardState _nState ) const
+ {
+ return m_pImpl->aDisabledStates.find( _nState ) == m_pImpl->aDisabledStates.end();
+ }
+
+ void RoadmapWizard::InsertRoadmapItem(int nItemIndex, const OUString& rText, int nItemId, bool bEnable)
+ {
+ m_xRoadmapImpl->pRoadmap->InsertRoadmapItem(nItemIndex, rText, nItemId, bEnable);
+ }
+
+ void RoadmapWizard::SelectRoadmapItemByID(int nItemId, bool bGrabFocus)
+ {
+ m_xRoadmapImpl->pRoadmap->SelectRoadmapItemByID(nItemId, bGrabFocus);
+ }
+
+ void RoadmapWizard::DeleteRoadmapItems()
+ {
+ while (m_xRoadmapImpl->pRoadmap->GetItemCount())
+ m_xRoadmapImpl->pRoadmap->DeleteRoadmapItem(0);
+ }
+
+ void RoadmapWizard::SetItemSelectHdl( const Link<LinkParamNone*,void>& _rHdl )
+ {
+ m_xRoadmapImpl->pRoadmap->SetItemSelectHdl(_rHdl);
+ }
+
+ int RoadmapWizard::GetCurrentRoadmapItemID() const
+ {
+ return m_xRoadmapImpl->pRoadmap->GetCurrentRoadmapItemID();
+ }
+
+ FactoryFunction RoadmapWizard::GetUITestFactory() const
+ {
+ return RoadmapWizardUIObject::create;
+ }
+
+ namespace
+ {
+ bool isButton(WindowType eType)
+ {
+ return eType == WindowType::PUSHBUTTON || eType == WindowType::OKBUTTON
+ || eType == WindowType::CANCELBUTTON || eType == WindowType::HELPBUTTON;
+ }
+ }
+
+ void RoadmapWizard::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+ {
+ rJsonWriter.put("id", get_id());
+ rJsonWriter.put("type", "dialog");
+ rJsonWriter.put("title", GetText());
+
+ OUString sDialogId = GetHelpId();
+ 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 : m_xRoadmapImpl->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());
+
+ {
+ auto childrenNode = rJsonWriter.startArray("children");
+
+ auto containerNode = rJsonWriter.startStruct();
+ rJsonWriter.put("id", "container");
+ rJsonWriter.put("type", "container");
+ rJsonWriter.put("vertical", true);
+
+ {
+ auto containerChildrenNode = rJsonWriter.startArray("children");
+
+ // tabpages
+ for (int i = 0; i < GetChildCount(); i++)
+ {
+ vcl::Window* pChild = GetChild(i);
+
+ if (!isButton(pChild->GetType()) && pChild != mpViewWindow)
+ {
+ auto childNode = rJsonWriter.startStruct();
+ pChild->DumpAsPropertyTree(rJsonWriter);
+ }
+ }
+
+ // buttons
+ {
+ auto buttonsNode = rJsonWriter.startStruct();
+ rJsonWriter.put("id", "buttons");
+ rJsonWriter.put("type", "buttonbox");
+ rJsonWriter.put("layoutstyle", "end");
+ {
+ auto buttonsChildrenNode = rJsonWriter.startArray("children");
+ for (int i = 0; i < GetChildCount(); i++)
+ {
+ vcl::Window* pChild = GetChild(i);
+
+ if (isButton(pChild->GetType()))
+ {
+ auto childNode = rJsonWriter.startStruct();
+ pChild->DumpAsPropertyTree(rJsonWriter);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/scrbar.cxx b/vcl/source/control/scrbar.cxx
new file mode 100644
index 0000000000..b652360139
--- /dev/null
+++ b/vcl/source/control/scrbar.cxx
@@ -0,0 +1,1473 @@
+/* -*- 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/decoview.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/toolkit/scrbar.hxx>
+#include <vcl/vclevent.hxx>
+
+#include <sal/log.hxx>
+
+/* #i77549#
+ HACK: for scrollbars in case of thumb rect, page up and page down rect we
+ abuse the HitTestNativeScrollbar interface. All theming engines but macOS
+ are actually able to draw the thumb according to our internal representation.
+ However macOS draws a little outside. The canonical way would be to enhance the
+ HitTestNativeScrollbar passing a ScrollbarValue additionally so all necessary
+ information is available in the call.
+ .
+ However since there is only this one small exception we will deviate a little and
+ instead pass the respective rect as control region to allow for a small correction.
+
+ So all places using HitTestNativeScrollbar on ControlPart::ThumbHorz, ControlPart::ThumbVert,
+ ControlPart::TrackHorzLeft, ControlPart::TrackHorzRight, ControlPart::TrackVertUpper, ControlPart::TrackVertLower
+ do not use the control rectangle as region but the actual part rectangle, making
+ only small deviations feasible.
+*/
+
+#include "thumbpos.hxx"
+
+#define SCRBAR_DRAW_BTN1 (sal_uInt16(0x0001))
+#define SCRBAR_DRAW_BTN2 (sal_uInt16(0x0002))
+#define SCRBAR_DRAW_PAGE1 (sal_uInt16(0x0004))
+#define SCRBAR_DRAW_PAGE2 (sal_uInt16(0x0008))
+#define SCRBAR_DRAW_THUMB (sal_uInt16(0x0010))
+#define SCRBAR_DRAW_BACKGROUND (sal_uInt16(0x0020))
+
+#define SCRBAR_STATE_BTN1_DOWN (sal_uInt16(0x0001))
+#define SCRBAR_STATE_BTN1_DISABLE (sal_uInt16(0x0002))
+#define SCRBAR_STATE_BTN2_DOWN (sal_uInt16(0x0004))
+#define SCRBAR_STATE_BTN2_DISABLE (sal_uInt16(0x0008))
+#define SCRBAR_STATE_PAGE1_DOWN (sal_uInt16(0x0010))
+#define SCRBAR_STATE_PAGE2_DOWN (sal_uInt16(0x0020))
+#define SCRBAR_STATE_THUMB_DOWN (sal_uInt16(0x0040))
+
+#define SCRBAR_VIEW_STYLE (WB_3DLOOK | WB_HORZ | WB_VERT)
+
+struct ImplScrollBarData
+{
+ AutoTimer maTimer { "vcl::ScrollBar mpData->maTimer" };
+ bool mbHide;
+};
+
+void ScrollBar::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ mpData = nullptr;
+ mnThumbPixRange = 0;
+ mnThumbPixPos = 0;
+ mnThumbPixSize = 0;
+ mnMinRange = 0;
+ mnMaxRange = 100;
+ mnThumbPos = 0;
+ mnVisibleSize = 0;
+ mnLineSize = 1;
+ mnPageSize = 1;
+ mnDelta = 0;
+ mnStateFlags = 0;
+ meScrollType = ScrollType::DontKnow;
+ mbCalcSize = true;
+ mbFullDrag = false;
+
+ ImplInitStyle( nStyle );
+ Control::ImplInit( pParent, nStyle, nullptr );
+
+ tools::Long nScrollSize = GetSettings().GetStyleSettings().GetScrollBarSize();
+ SetSizePixel( Size( nScrollSize, nScrollSize ) );
+}
+
+void ScrollBar::ImplInitStyle( WinBits nStyle )
+{
+ if ( nStyle & WB_DRAG )
+ mbFullDrag = true;
+ else
+ mbFullDrag = bool(GetSettings().GetStyleSettings().GetDragFullOptions() & DragFullOptions::Scroll);
+}
+
+ScrollBar::ScrollBar( vcl::Window* pParent, WinBits nStyle ) :
+ Control( WindowType::SCROLLBAR )
+{
+ ImplInit( pParent, nStyle );
+}
+
+ScrollBar::~ScrollBar()
+{
+ disposeOnce();
+}
+
+void ScrollBar::dispose()
+{
+ mpData.reset();
+ Control::dispose();
+}
+
+void ScrollBar::ImplUpdateRects( bool bUpdate )
+{
+ mnStateFlags &= ~SCRBAR_STATE_BTN1_DISABLE;
+ mnStateFlags &= ~SCRBAR_STATE_BTN2_DISABLE;
+
+ if ( mnThumbPixRange )
+ {
+ if ( GetStyle() & WB_HORZ )
+ {
+ maThumbRect.SetLeft( maTrackRect.Left()+mnThumbPixPos );
+ maThumbRect.SetRight( maThumbRect.Left()+mnThumbPixSize-1 );
+ if ( !mnThumbPixPos )
+ maPage1Rect.SetWidthEmpty();
+ else
+ maPage1Rect.SetRight( maThumbRect.Left()-1 );
+ if ( mnThumbPixPos >= (mnThumbPixRange-mnThumbPixSize) )
+ maPage2Rect.SetWidthEmpty();
+ else
+ {
+ maPage2Rect.SetLeft( maThumbRect.Right()+1 );
+ maPage2Rect.SetRight( maTrackRect.Right() );
+ }
+ }
+ else
+ {
+ maThumbRect.SetTop( maTrackRect.Top()+mnThumbPixPos );
+ maThumbRect.SetBottom( maThumbRect.Top()+mnThumbPixSize-1 );
+ if ( !mnThumbPixPos )
+ maPage1Rect.SetHeightEmpty();
+ else
+ maPage1Rect.SetBottom( maThumbRect.Top()-1 );
+ if ( mnThumbPixPos >= (mnThumbPixRange-mnThumbPixSize) )
+ maPage2Rect.SetHeightEmpty();
+ else
+ {
+ maPage2Rect.SetTop( maThumbRect.Bottom()+1 );
+ maPage2Rect.SetBottom( maTrackRect.Bottom() );
+ }
+ }
+ }
+ else
+ {
+ if ( GetStyle() & WB_HORZ )
+ {
+ const tools::Long nSpace = maTrackRect.Right() - maTrackRect.Left();
+ if ( nSpace > 0 )
+ {
+ maPage1Rect.SetLeft( maTrackRect.Left() );
+ maPage1Rect.SetRight( maTrackRect.Left() + (nSpace/2) );
+ maPage2Rect.SetLeft( maPage1Rect.Right() + 1 );
+ maPage2Rect.SetRight( maTrackRect.Right() );
+ }
+ }
+ else
+ {
+ const tools::Long nSpace = maTrackRect.Bottom() - maTrackRect.Top();
+ if ( nSpace > 0 )
+ {
+ maPage1Rect.SetTop( maTrackRect.Top() );
+ maPage1Rect.SetBottom( maTrackRect.Top() + (nSpace/2) );
+ maPage2Rect.SetTop( maPage1Rect.Bottom() + 1 );
+ maPage2Rect.SetBottom( maTrackRect.Bottom() );
+ }
+ }
+ }
+
+ if( !IsNativeControlSupported(ControlType::Scrollbar, ControlPart::Entire) )
+ {
+ // disable scrollbar buttons only in VCL's own 'theme'
+ // as it is uncommon on other platforms
+ if ( mnThumbPos == mnMinRange )
+ mnStateFlags |= SCRBAR_STATE_BTN1_DISABLE;
+ if ( mnThumbPos >= (mnMaxRange-mnVisibleSize) )
+ mnStateFlags |= SCRBAR_STATE_BTN2_DISABLE;
+ }
+
+ if ( bUpdate )
+ {
+ Invalidate();
+ }
+}
+
+tools::Long ScrollBar::ImplCalcThumbPos( tools::Long nPixPos ) const
+{
+ // Calculate position
+ tools::Long nCalcThumbPos;
+ nCalcThumbPos = ImplMulDiv( nPixPos, mnMaxRange-mnVisibleSize-mnMinRange,
+ mnThumbPixRange-mnThumbPixSize );
+ nCalcThumbPos += mnMinRange;
+ return nCalcThumbPos;
+}
+
+tools::Long ScrollBar::ImplCalcThumbPosPix( tools::Long nPos ) const
+{
+ tools::Long nCalcThumbPos;
+
+ // Calculate position
+ nCalcThumbPos = ImplMulDiv( nPos-mnMinRange, mnThumbPixRange-mnThumbPixSize,
+ mnMaxRange-mnVisibleSize-mnMinRange );
+
+ // At the start and end of the ScrollBar, we try to show the display correctly
+ if ( !nCalcThumbPos && (mnThumbPos > mnMinRange) )
+ nCalcThumbPos = 1;
+ if ( nCalcThumbPos &&
+ ((nCalcThumbPos+mnThumbPixSize) >= mnThumbPixRange) &&
+ (mnThumbPos < (mnMaxRange-mnVisibleSize)) )
+ nCalcThumbPos--;
+
+ return nCalcThumbPos;
+}
+
+void ScrollBar::ImplCalc( bool bUpdate )
+{
+ const Size aSize = GetOutputSizePixel();
+ const tools::Long nMinThumbSize = GetSettings().GetStyleSettings().GetMinThumbSize();
+
+ if ( mbCalcSize )
+ {
+ Size aOldSize = getCurrentCalcSize();
+
+ const tools::Rectangle aControlRegion( Point(0,0), aSize );
+ tools::Rectangle aBtn1Region, aBtn2Region, aTrackRegion, aBoundingRegion;
+
+ // reset rectangles to empty *and* (0,0) position
+ maThumbRect = tools::Rectangle();
+ maPage1Rect = tools::Rectangle();
+ maPage2Rect = tools::Rectangle();
+
+ if ( GetStyle() & WB_HORZ )
+ {
+ if ( GetNativeControlRegion( ControlType::Scrollbar, IsRTLEnabled()? ControlPart::ButtonRight: ControlPart::ButtonLeft,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aBtn1Region ) &&
+ GetNativeControlRegion( ControlType::Scrollbar, IsRTLEnabled()? ControlPart::ButtonLeft: ControlPart::ButtonRight,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aBtn2Region ) )
+ {
+ maBtn1Rect = aBtn1Region;
+ maBtn2Rect = aBtn2Region;
+ }
+ else
+ {
+ Size aBtnSize( aSize.Height(), aSize.Height() );
+ maBtn2Rect.SetTop( maBtn1Rect.Top() );
+ maBtn2Rect.SetLeft( aSize.Width()-aSize.Height() );
+ maBtn1Rect.SetSize( aBtnSize );
+ maBtn2Rect.SetSize( aBtnSize );
+ }
+
+ if ( GetNativeControlRegion( ControlType::Scrollbar, ControlPart::TrackHorzArea,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aTrackRegion ) )
+ maTrackRect = aTrackRegion;
+ else
+ maTrackRect = tools::Rectangle::Normalize( maBtn1Rect.TopRight(), maBtn2Rect.BottomLeft() );
+
+ // Check if available space is big enough for thumb ( min thumb size = ScrBar width/height )
+ mnThumbPixRange = maTrackRect.Right() - maTrackRect.Left();
+ if( mnThumbPixRange > 0 )
+ {
+ maPage1Rect.SetLeft( maTrackRect.Left() );
+ maPage1Rect.SetBottom( maTrackRect.Bottom() );
+ maPage2Rect.SetBottom (maTrackRect.Bottom() );
+ maThumbRect.SetBottom( maTrackRect.Bottom() );
+ }
+ else
+ mnThumbPixRange = 0;
+ }
+ else // WB_VERT
+ {
+ if ( GetNativeControlRegion( ControlType::Scrollbar, ControlPart::ButtonUp,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aBtn1Region ) &&
+ GetNativeControlRegion( ControlType::Scrollbar, ControlPart::ButtonDown,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aBtn2Region ) )
+ {
+ maBtn1Rect = aBtn1Region;
+ maBtn2Rect = aBtn2Region;
+ }
+ else
+ {
+ const Size aBtnSize( aSize.Width(), aSize.Width() );
+ maBtn2Rect.SetLeft( maBtn1Rect.Left() );
+ maBtn2Rect.SetTop( aSize.Height()-aSize.Width() );
+ maBtn1Rect.SetSize( aBtnSize );
+ maBtn2Rect.SetSize( aBtnSize );
+ }
+
+ if ( GetNativeControlRegion( ControlType::Scrollbar, ControlPart::TrackVertArea,
+ aControlRegion, ControlState::NONE, ImplControlValue(), aBoundingRegion, aTrackRegion ) )
+ maTrackRect = aTrackRegion;
+ else
+ maTrackRect = tools::Rectangle::Normalize( maBtn1Rect.BottomLeft()+Point(0,1), maBtn2Rect.TopRight() );
+
+ // Check if available space is big enough for thumb
+ mnThumbPixRange = maTrackRect.Bottom() - maTrackRect.Top();
+ if( mnThumbPixRange > 0 )
+ {
+ maPage1Rect.SetTop( maTrackRect.Top() );
+ maPage1Rect.SetRight( maTrackRect.Right() );
+ maPage2Rect.SetRight( maTrackRect.Right() );
+ maThumbRect.SetRight( maTrackRect.Right() );
+ }
+ else
+ mnThumbPixRange = 0;
+ }
+
+ mbCalcSize = false;
+
+ Size aNewSize = getCurrentCalcSize();
+ if (aOldSize != aNewSize)
+ {
+ queue_resize();
+ }
+ }
+
+ if ( mnThumbPixRange )
+ {
+ // Calculate values
+ if ( (mnVisibleSize >= (mnMaxRange-mnMinRange)) ||
+ ((mnMaxRange-mnMinRange) <= 0) )
+ {
+ mnThumbPos = mnMinRange;
+ mnThumbPixPos = 0;
+ mnThumbPixSize = mnThumbPixRange;
+ }
+ else
+ {
+ if ( mnVisibleSize )
+ mnThumbPixSize = ImplMulDiv( mnThumbPixRange, mnVisibleSize, mnMaxRange-mnMinRange );
+ else
+ {
+ if ( GetStyle() & WB_HORZ )
+ mnThumbPixSize = maThumbRect.GetWidth();
+ else
+ mnThumbPixSize = maThumbRect.GetHeight();
+ }
+ if ( mnThumbPixSize < nMinThumbSize )
+ mnThumbPixSize = nMinThumbSize;
+ if ( mnThumbPixSize > mnThumbPixRange )
+ mnThumbPixSize = mnThumbPixRange;
+ mnThumbPixPos = ImplCalcThumbPosPix( mnThumbPos );
+ }
+ }
+
+ // If we're ought to output again and we have been triggered
+ // a Paint event via an Action, we don't output directly,
+ // but invalidate everything
+ if ( bUpdate && HasPaintEvent() )
+ {
+ Invalidate();
+ bUpdate = false;
+ }
+ ImplUpdateRects( bUpdate );
+}
+
+void ScrollBar::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags )
+{
+ Point aPos = pDev->LogicToPixel( rPos );
+
+ pDev->Push();
+ pDev->SetMapMode();
+ if ( !(nFlags & SystemTextColorFlags::Mono) )
+ {
+ // DecoView uses the FaceColor...
+ AllSettings aSettings = pDev->GetSettings();
+ StyleSettings aStyleSettings = aSettings.GetStyleSettings();
+ if ( IsControlBackground() )
+ aStyleSettings.SetFaceColor( GetControlBackground() );
+ else
+ aStyleSettings.SetFaceColor( GetSettings().GetStyleSettings().GetFaceColor() );
+
+ aSettings.SetStyleSettings( aStyleSettings );
+ pDev->SetSettings( aSettings );
+ }
+
+ // For printing:
+ // - calculate the size of the rects
+ // - because this is zero-based add the correct offset
+ // - print
+ // - force recalculate
+
+ if ( mbCalcSize )
+ ImplCalc( false );
+
+ maBtn1Rect+=aPos;
+ maBtn2Rect+=aPos;
+ maThumbRect+=aPos;
+ maTrackRect+=aPos;
+ maPage1Rect+=aPos;
+ maPage2Rect+=aPos;
+
+ ImplDraw(*pDev);
+ pDev->Pop();
+
+ mbCalcSize = true;
+}
+
+bool ScrollBar::ImplDrawNative(vcl::RenderContext& rRenderContext, sal_uInt16 nSystemTextColorFlags)
+{
+ ScrollbarValue scrValue;
+
+ bool bNativeOK = rRenderContext.IsNativeControlSupported(ControlType::Scrollbar, ControlPart::Entire);
+ if (!bNativeOK)
+ return false;
+
+ bool bHorz = (GetStyle() & WB_HORZ) != 0;
+
+ // Draw the entire background if the control supports it
+ if (rRenderContext.IsNativeControlSupported(ControlType::Scrollbar, bHorz ? ControlPart::DrawBackgroundHorz : ControlPart::DrawBackgroundVert))
+ {
+ ControlState nState = (IsEnabled() ? ControlState::ENABLED : ControlState::NONE)
+ | (HasFocus() ? ControlState::FOCUSED : ControlState::NONE);
+
+ scrValue.mnMin = mnMinRange;
+ scrValue.mnMax = mnMaxRange;
+ scrValue.mnCur = mnThumbPos;
+ scrValue.mnVisibleSize = mnVisibleSize;
+ scrValue.maThumbRect = maThumbRect;
+ scrValue.maButton1Rect = maBtn1Rect;
+ scrValue.maButton2Rect = maBtn2Rect;
+ scrValue.mnButton1State = ((mnStateFlags & SCRBAR_STATE_BTN1_DOWN) ? ControlState::PRESSED : ControlState::NONE) |
+ ((!(mnStateFlags & SCRBAR_STATE_BTN1_DISABLE)) ? ControlState::ENABLED : ControlState::NONE);
+ scrValue.mnButton2State = ((mnStateFlags & SCRBAR_STATE_BTN2_DOWN) ? ControlState::PRESSED : ControlState::NONE) |
+ ((!(mnStateFlags & SCRBAR_STATE_BTN2_DISABLE)) ? ControlState::ENABLED : ControlState::NONE);
+ scrValue.mnThumbState = nState | ((mnStateFlags & SCRBAR_STATE_THUMB_DOWN) ? ControlState::PRESSED : ControlState::NONE);
+
+ if (IsMouseOver())
+ {
+ tools::Rectangle* pRect = ImplFindPartRect(GetPointerPosPixel());
+ if (pRect)
+ {
+ if (pRect == &maThumbRect)
+ scrValue.mnThumbState |= ControlState::ROLLOVER;
+ else if (pRect == &maBtn1Rect)
+ scrValue.mnButton1State |= ControlState::ROLLOVER;
+ else if (pRect == &maBtn2Rect)
+ scrValue.mnButton2State |= ControlState::ROLLOVER;
+ }
+ }
+
+ tools::Rectangle aCtrlRegion;
+ aCtrlRegion.Union(maBtn1Rect);
+ aCtrlRegion.Union(maBtn2Rect);
+ aCtrlRegion.Union(maPage1Rect);
+ aCtrlRegion.Union(maPage2Rect);
+ aCtrlRegion.Union(maThumbRect);
+
+ tools::Rectangle aRequestedRegion(Point(0,0), GetOutputSizePixel());
+ // if the actual native control region is smaller then the region that
+ // we requested the control to draw in, then draw a background rectangle
+ // to avoid drawing artifacts in the uncovered region
+ if (aCtrlRegion.GetWidth() < aRequestedRegion.GetWidth() ||
+ aCtrlRegion.GetHeight() < aRequestedRegion.GetHeight())
+ {
+ Color aFaceColor = rRenderContext.GetSettings().GetStyleSettings().GetFaceColor();
+ rRenderContext.SetFillColor(aFaceColor);
+ rRenderContext.SetLineColor(aFaceColor);
+ rRenderContext.DrawRect(aRequestedRegion);
+ }
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, (bHorz ? ControlPart::DrawBackgroundHorz : ControlPart::DrawBackgroundVert),
+ aCtrlRegion, nState, scrValue, OUString());
+ }
+ else
+ {
+ if ((nSystemTextColorFlags & SCRBAR_DRAW_PAGE1) || (nSystemTextColorFlags & SCRBAR_DRAW_PAGE2))
+ {
+ ControlPart part1 = bHorz ? ControlPart::TrackHorzLeft : ControlPart::TrackVertUpper;
+ ControlPart part2 = bHorz ? ControlPart::TrackHorzRight : ControlPart::TrackVertLower;
+ tools::Rectangle aCtrlRegion1(maPage1Rect);
+ tools::Rectangle aCtrlRegion2(maPage2Rect);
+ ControlState nState1 = (IsEnabled() ? ControlState::ENABLED : ControlState::NONE)
+ | (HasFocus() ? ControlState::FOCUSED : ControlState::NONE);
+ ControlState nState2 = nState1;
+
+ nState1 |= ((mnStateFlags & SCRBAR_STATE_PAGE1_DOWN) ? ControlState::PRESSED : ControlState::NONE);
+ nState2 |= ((mnStateFlags & SCRBAR_STATE_PAGE2_DOWN) ? ControlState::PRESSED : ControlState::NONE);
+
+ if (IsMouseOver())
+ {
+ tools::Rectangle* pRect = ImplFindPartRect(GetPointerPosPixel());
+ if (pRect)
+ {
+ if (pRect == &maPage1Rect)
+ nState1 |= ControlState::ROLLOVER;
+ else if (pRect == &maPage2Rect)
+ nState2 |= ControlState::ROLLOVER;
+ }
+ }
+
+ if (nSystemTextColorFlags & SCRBAR_DRAW_PAGE1)
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, part1, aCtrlRegion1, nState1, scrValue, OUString());
+
+ if (nSystemTextColorFlags & SCRBAR_DRAW_PAGE2)
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, part2, aCtrlRegion2, nState2, scrValue, OUString());
+ }
+ if ((nSystemTextColorFlags & SCRBAR_DRAW_BTN1) || (nSystemTextColorFlags & SCRBAR_DRAW_BTN2))
+ {
+ ControlPart part1 = bHorz ? ControlPart::ButtonLeft : ControlPart::ButtonUp;
+ ControlPart part2 = bHorz ? ControlPart::ButtonRight : ControlPart::ButtonDown;
+ tools::Rectangle aCtrlRegion1(maBtn1Rect);
+ tools::Rectangle aCtrlRegion2(maBtn2Rect);
+ ControlState nState1 = HasFocus() ? ControlState::FOCUSED : ControlState::NONE;
+ ControlState nState2 = nState1;
+
+ if (!Window::IsEnabled() || !IsEnabled())
+ nState1 = (nState2 &= ~ControlState::ENABLED);
+ else
+ nState1 = (nState2 |= ControlState::ENABLED);
+
+ nState1 |= ((mnStateFlags & SCRBAR_STATE_BTN1_DOWN) ? ControlState::PRESSED : ControlState::NONE);
+ nState2 |= ((mnStateFlags & SCRBAR_STATE_BTN2_DOWN) ? ControlState::PRESSED : ControlState::NONE);
+
+ if (mnStateFlags & SCRBAR_STATE_BTN1_DISABLE)
+ nState1 &= ~ControlState::ENABLED;
+ if (mnStateFlags & SCRBAR_STATE_BTN2_DISABLE)
+ nState2 &= ~ControlState::ENABLED;
+
+ if (IsMouseOver())
+ {
+ tools::Rectangle* pRect = ImplFindPartRect(GetPointerPosPixel());
+ if (pRect)
+ {
+ if (pRect == &maBtn1Rect)
+ nState1 |= ControlState::ROLLOVER;
+ else if (pRect == &maBtn2Rect)
+ nState2 |= ControlState::ROLLOVER;
+ }
+ }
+
+ if (nSystemTextColorFlags & SCRBAR_DRAW_BTN1)
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, part1, aCtrlRegion1, nState1, scrValue, OUString());
+
+ if (nSystemTextColorFlags & SCRBAR_DRAW_BTN2)
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, part2, aCtrlRegion2, nState2, scrValue, OUString());
+ }
+ if ((nSystemTextColorFlags & SCRBAR_DRAW_THUMB) && !maThumbRect.IsEmpty())
+ {
+ ControlState nState = IsEnabled() ? ControlState::ENABLED : ControlState::NONE;
+ tools::Rectangle aCtrlRegion(maThumbRect);
+
+ if (mnStateFlags & SCRBAR_STATE_THUMB_DOWN)
+ nState |= ControlState::PRESSED;
+
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+
+ if (IsMouseOver())
+ {
+ tools::Rectangle* pRect = ImplFindPartRect(GetPointerPosPixel());
+ if (pRect && pRect == &maThumbRect)
+ nState |= ControlState::ROLLOVER;
+ }
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Scrollbar, (bHorz ? ControlPart::ThumbHorz : ControlPart::ThumbVert),
+ aCtrlRegion, nState, scrValue, OUString());
+ }
+ }
+ return bNativeOK;
+}
+
+void ScrollBar::ImplDraw(vcl::RenderContext& rRenderContext)
+{
+ DecorationView aDecoView(&rRenderContext);
+ tools::Rectangle aTempRect;
+ DrawButtonFlags nStyle;
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ SymbolType eSymbolType;
+ bool bEnabled = IsEnabled();
+
+ // Finish some open calculations (if any)
+ if (mbCalcSize)
+ ImplCalc(false);
+
+ //vcl::Window *pWin = NULL;
+ //if (rRenderContext.GetOutDevType() == OUTDEV_WINDOW)
+ // pWin = static_cast<vcl::Window*>(&rRenderContext);
+
+ // Draw the entire control if the native theme engine needs it
+ if (rRenderContext.IsNativeControlSupported(ControlType::Scrollbar, ControlPart::DrawBackgroundHorz))
+ {
+ ImplDrawNative(rRenderContext, SCRBAR_DRAW_BACKGROUND);
+ return;
+ }
+
+ if (!ImplDrawNative(rRenderContext, SCRBAR_DRAW_BTN1))
+ {
+ nStyle = DrawButtonFlags::NoLightBorder;
+ if (mnStateFlags & SCRBAR_STATE_BTN1_DOWN)
+ nStyle |= DrawButtonFlags::Pressed;
+ aTempRect = aDecoView.DrawButton( PixelToLogic(maBtn1Rect), nStyle );
+ ImplCalcSymbolRect( aTempRect );
+ DrawSymbolFlags nSymbolStyle = DrawSymbolFlags::NONE;
+ if ((mnStateFlags & SCRBAR_STATE_BTN1_DISABLE) || !bEnabled)
+ nSymbolStyle |= DrawSymbolFlags::Disable;
+ if (GetStyle() & WB_HORZ)
+ eSymbolType = SymbolType::SPIN_LEFT;
+ else
+ eSymbolType = SymbolType::SPIN_UP;
+ aDecoView.DrawSymbol(aTempRect, eSymbolType, rStyleSettings.GetButtonTextColor(), nSymbolStyle);
+ }
+
+ if (!ImplDrawNative(rRenderContext, SCRBAR_DRAW_BTN2))
+ {
+ nStyle = DrawButtonFlags::NoLightBorder;
+ if (mnStateFlags & SCRBAR_STATE_BTN2_DOWN)
+ nStyle |= DrawButtonFlags::Pressed;
+ aTempRect = aDecoView.DrawButton(PixelToLogic(maBtn2Rect), nStyle);
+ ImplCalcSymbolRect(aTempRect);
+ DrawSymbolFlags nSymbolStyle = DrawSymbolFlags::NONE;
+ if ((mnStateFlags & SCRBAR_STATE_BTN2_DISABLE) || !bEnabled)
+ nSymbolStyle |= DrawSymbolFlags::Disable;
+ if (GetStyle() & WB_HORZ)
+ eSymbolType = SymbolType::SPIN_RIGHT;
+ else
+ eSymbolType = SymbolType::SPIN_DOWN;
+ aDecoView.DrawSymbol(aTempRect, eSymbolType, rStyleSettings.GetButtonTextColor(), nSymbolStyle);
+ }
+
+ rRenderContext.SetLineColor();
+
+ if (!ImplDrawNative(rRenderContext, SCRBAR_DRAW_THUMB))
+ {
+ if (!maThumbRect.IsEmpty())
+ {
+ if (bEnabled)
+ {
+ nStyle = DrawButtonFlags::NoLightBorder;
+ aTempRect = aDecoView.DrawButton(PixelToLogic(maThumbRect), nStyle);
+ }
+ else
+ {
+ rRenderContext.SetFillColor(rStyleSettings.GetCheckedColor());
+ rRenderContext.DrawRect(PixelToLogic(maThumbRect));
+ }
+ }
+ }
+
+ if (!ImplDrawNative(rRenderContext, SCRBAR_DRAW_PAGE1))
+ {
+ if (mnStateFlags & SCRBAR_STATE_PAGE1_DOWN)
+ rRenderContext.SetFillColor(rStyleSettings.GetShadowColor());
+ else
+ rRenderContext.SetFillColor(rStyleSettings.GetCheckedColor());
+ rRenderContext.DrawRect(PixelToLogic(maPage1Rect));
+ }
+ if (!ImplDrawNative(rRenderContext, SCRBAR_DRAW_PAGE2))
+ {
+ if (mnStateFlags & SCRBAR_STATE_PAGE2_DOWN)
+ rRenderContext.SetFillColor(rStyleSettings.GetShadowColor());
+ else
+ rRenderContext.SetFillColor(rStyleSettings.GetCheckedColor());
+ rRenderContext.DrawRect(PixelToLogic(maPage2Rect));
+ }
+}
+
+tools::Long ScrollBar::ImplScroll( tools::Long nNewPos, bool bCallEndScroll )
+{
+ tools::Long nOldPos = mnThumbPos;
+ SetThumbPos( nNewPos );
+ tools::Long nDelta = mnThumbPos-nOldPos;
+ if ( nDelta )
+ {
+ mnDelta = nDelta;
+ Scroll();
+ if ( bCallEndScroll )
+ EndScroll();
+ mnDelta = 0;
+ }
+ return nDelta;
+}
+
+tools::Long ScrollBar::ImplDoAction( bool bCallEndScroll )
+{
+ tools::Long nDelta = 0;
+
+ switch ( meScrollType )
+ {
+ case ScrollType::LineUp:
+ nDelta = ImplScroll( mnThumbPos-mnLineSize, bCallEndScroll );
+ break;
+
+ case ScrollType::LineDown:
+ nDelta = ImplScroll( mnThumbPos+mnLineSize, bCallEndScroll );
+ break;
+
+ case ScrollType::PageUp:
+ nDelta = ImplScroll( mnThumbPos-mnPageSize, bCallEndScroll );
+ break;
+
+ case ScrollType::PageDown:
+ nDelta = ImplScroll( mnThumbPos+mnPageSize, bCallEndScroll );
+ break;
+ default:
+ ;
+ }
+
+ return nDelta;
+}
+
+void ScrollBar::ImplDoMouseAction( const Point& rMousePos, bool bCallAction )
+{
+ sal_uInt16 nOldStateFlags = mnStateFlags;
+ bool bAction = false;
+ bool bHorizontal = ( GetStyle() & WB_HORZ ) != 0;
+ bool bIsInside = false;
+
+ Point aPoint( 0, 0 );
+ tools::Rectangle aControlRegion( aPoint, GetOutputSizePixel() );
+
+ switch ( meScrollType )
+ {
+ case ScrollType::LineUp:
+ if ( GetOutDev()->HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonRight: ControlPart::ButtonLeft): ControlPart::ButtonUp,
+ aControlRegion, rMousePos, bIsInside )?
+ bIsInside:
+ maBtn1Rect.Contains( rMousePos ) )
+ {
+ bAction = bCallAction;
+ mnStateFlags |= SCRBAR_STATE_BTN1_DOWN;
+ }
+ else
+ mnStateFlags &= ~SCRBAR_STATE_BTN1_DOWN;
+ break;
+
+ case ScrollType::LineDown:
+ if ( GetOutDev()->HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonLeft: ControlPart::ButtonRight): ControlPart::ButtonDown,
+ aControlRegion, rMousePos, bIsInside )?
+ bIsInside:
+ maBtn2Rect.Contains( rMousePos ) )
+ {
+ bAction = bCallAction;
+ mnStateFlags |= SCRBAR_STATE_BTN2_DOWN;
+ }
+ else
+ mnStateFlags &= ~SCRBAR_STATE_BTN2_DOWN;
+ break;
+
+ case ScrollType::PageUp:
+ // HitTestNativeScrollbar, see remark at top of file
+ if ( GetOutDev()->HitTestNativeScrollbar( bHorizontal? ControlPart::TrackHorzLeft: ControlPart::TrackVertUpper,
+ maPage1Rect, rMousePos, bIsInside )?
+ bIsInside:
+ maPage1Rect.Contains( rMousePos ) )
+ {
+ bAction = bCallAction;
+ mnStateFlags |= SCRBAR_STATE_PAGE1_DOWN;
+ }
+ else
+ mnStateFlags &= ~SCRBAR_STATE_PAGE1_DOWN;
+ break;
+
+ case ScrollType::PageDown:
+ // HitTestNativeScrollbar, see remark at top of file
+ if ( GetOutDev()->HitTestNativeScrollbar( bHorizontal? ControlPart::TrackHorzRight: ControlPart::TrackVertLower,
+ maPage2Rect, rMousePos, bIsInside )?
+ bIsInside:
+ maPage2Rect.Contains( rMousePos ) )
+ {
+ bAction = bCallAction;
+ mnStateFlags |= SCRBAR_STATE_PAGE2_DOWN;
+ }
+ else
+ mnStateFlags &= ~SCRBAR_STATE_PAGE2_DOWN;
+ break;
+ default:
+ ;
+ }
+
+ if ( nOldStateFlags != mnStateFlags )
+ Invalidate();
+ if ( bAction )
+ ImplDoAction( false );
+}
+
+void ScrollBar::ImplDragThumb( const Point& rMousePos )
+{
+ tools::Long nMovePix;
+ if ( GetStyle() & WB_HORZ )
+ nMovePix = rMousePos.X()-(maThumbRect.Left()+mnMouseOff);
+ else
+ nMovePix = rMousePos.Y()-(maThumbRect.Top()+mnMouseOff);
+
+ // Move thumb if necessary
+ if ( !nMovePix )
+ return;
+
+ mnThumbPixPos += nMovePix;
+ if ( mnThumbPixPos < 0 )
+ mnThumbPixPos = 0;
+ if ( mnThumbPixPos > (mnThumbPixRange-mnThumbPixSize) )
+ mnThumbPixPos = mnThumbPixRange-mnThumbPixSize;
+ tools::Long nOldPos = mnThumbPos;
+ mnThumbPos = ImplCalcThumbPos( mnThumbPixPos );
+ ImplUpdateRects();
+ if ( !(mbFullDrag && (nOldPos != mnThumbPos)) )
+ return;
+
+ // When dragging in windows the repaint request gets starved so dragging
+ // the scrollbar feels slower than it actually is. Let's force an immediate
+ // repaint of the scrollbar.
+ if (SupportsDoubleBuffering())
+ {
+ Invalidate();
+ PaintImmediately();
+ }
+ else
+ ImplDraw(*GetOutDev());
+
+ mnDelta = mnThumbPos-nOldPos;
+ Scroll();
+ mnDelta = 0;
+}
+
+void ScrollBar::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ bool bPrimaryWarps = GetSettings().GetStyleSettings().GetPrimaryButtonWarpsSlider();
+ bool bWarp = bPrimaryWarps ? rMEvt.IsLeft() : rMEvt.IsMiddle();
+ bool bPrimaryWarping = bWarp && rMEvt.IsLeft();
+ bool bPage = bPrimaryWarps ? rMEvt.IsRight() : rMEvt.IsLeft();
+
+ if (!rMEvt.IsLeft() && !rMEvt.IsMiddle() && !rMEvt.IsRight())
+ return;
+
+ Point aPosPixel;
+ if (!IsMapModeEnabled() && GetMapMode().GetMapUnit() == MapUnit::MapTwip)
+ {
+ // rMEvt coordinates are in twips.
+ GetOutDev()->Push(vcl::PushFlags::MAPMODE);
+ EnableMapMode();
+ MapMode aMapMode = GetMapMode();
+ aMapMode.SetOrigin(Point(0, 0));
+ SetMapMode(aMapMode);
+ aPosPixel = LogicToPixel(rMEvt.GetPosPixel());
+ GetOutDev()->Pop();
+ }
+ const Point& rMousePos = (GetMapMode().GetMapUnit() != MapUnit::MapTwip ? rMEvt.GetPosPixel() : aPosPixel);
+ StartTrackingFlags nTrackFlags = StartTrackingFlags::NONE;
+ bool bHorizontal = ( GetStyle() & WB_HORZ ) != 0;
+ bool bIsInside = false;
+ bool bDragToMouse = false;
+
+ Point aPoint( 0, 0 );
+ tools::Rectangle aControlRegion( aPoint, GetOutputSizePixel() );
+
+ if ( GetOutDev()->HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonRight: ControlPart::ButtonLeft): ControlPart::ButtonUp,
+ aControlRegion, rMousePos, bIsInside )?
+ bIsInside:
+ maBtn1Rect.Contains( rMousePos ) )
+ {
+ if (rMEvt.IsLeft() && !(mnStateFlags & SCRBAR_STATE_BTN1_DISABLE) )
+ {
+ nTrackFlags = StartTrackingFlags::ButtonRepeat;
+ meScrollType = ScrollType::LineUp;
+ }
+ }
+ else if ( GetOutDev()->HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonLeft: ControlPart::ButtonRight): ControlPart::ButtonDown,
+ aControlRegion, rMousePos, bIsInside )?
+ bIsInside:
+ maBtn2Rect.Contains( rMousePos ) )
+ {
+ if (rMEvt.IsLeft() && !(mnStateFlags & SCRBAR_STATE_BTN2_DISABLE) )
+ {
+ nTrackFlags = StartTrackingFlags::ButtonRepeat;
+ meScrollType = ScrollType::LineDown;
+ }
+ }
+ else
+ {
+ bool bThumbHit = GetOutDev()->HitTestNativeScrollbar( bHorizontal? ControlPart::ThumbHorz : ControlPart::ThumbVert,
+ maThumbRect, rMousePos, bIsInside )
+ ? bIsInside : maThumbRect.Contains( rMousePos );
+
+ bool bThumbAction = bWarp || bPage;
+
+ bool bDragHandling = bWarp || (bThumbHit && bThumbAction);
+ if( bDragHandling )
+ {
+ if( mpData )
+ {
+ mpData->mbHide = true; // disable focus blinking
+ if (HasFocus())
+ {
+ mnStateFlags |= SCRBAR_DRAW_THUMB; // paint without focus
+ Invalidate();
+ }
+ }
+
+ if ( mnVisibleSize < mnMaxRange-mnMinRange )
+ {
+ nTrackFlags = StartTrackingFlags::NONE;
+ meScrollType = ScrollType::Drag;
+
+ // calculate mouse offset
+ if (bWarp && (!bThumbHit || !bPrimaryWarping))
+ {
+ bDragToMouse = true;
+ if ( GetStyle() & WB_HORZ )
+ mnMouseOff = maThumbRect.GetWidth()/2;
+ else
+ mnMouseOff = maThumbRect.GetHeight()/2;
+ }
+ else
+ {
+ if ( GetStyle() & WB_HORZ )
+ mnMouseOff = rMousePos.X()-maThumbRect.Left();
+ else
+ mnMouseOff = rMousePos.Y()-maThumbRect.Top();
+ }
+
+ mnStateFlags |= SCRBAR_STATE_THUMB_DOWN;
+ Invalidate();
+ }
+ }
+ else if(bPage && (!GetOutDev()->HitTestNativeScrollbar( bHorizontal? ControlPart::TrackHorzArea : ControlPart::TrackVertArea,
+ aControlRegion, rMousePos, bIsInside ) ||
+ bIsInside) )
+ {
+ nTrackFlags = StartTrackingFlags::ButtonRepeat;
+
+ // HitTestNativeScrollbar, see remark at top of file
+ if ( GetOutDev()->HitTestNativeScrollbar( bHorizontal? ControlPart::TrackHorzLeft : ControlPart::TrackVertUpper,
+ maPage1Rect, rMousePos, bIsInside )?
+ bIsInside:
+ maPage1Rect.Contains( rMousePos ) )
+ {
+ meScrollType = ScrollType::PageUp;
+ }
+ else
+ {
+ meScrollType = ScrollType::PageDown;
+ }
+ }
+ }
+
+ // Should we start Tracking?
+ if ( meScrollType == ScrollType::DontKnow )
+ return;
+
+ // store original position for cancel and EndScroll delta
+ mnStartPos = mnThumbPos;
+ // #92906# Call StartTracking() before ImplDoMouseAction(), otherwise
+ // MouseButtonUp() / EndTracking() may be called if somebody is spending
+ // a lot of time in the scroll handler
+ StartTracking( nTrackFlags );
+ ImplDoMouseAction( rMousePos );
+
+ if( bDragToMouse )
+ ImplDragThumb( rMousePos );
+
+}
+
+void ScrollBar::Tracking( const TrackingEvent& rTEvt )
+{
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ // Restore Button and PageRect status
+ sal_uInt16 nOldStateFlags = mnStateFlags;
+ mnStateFlags &= ~(SCRBAR_STATE_BTN1_DOWN | SCRBAR_STATE_BTN2_DOWN |
+ SCRBAR_STATE_PAGE1_DOWN | SCRBAR_STATE_PAGE2_DOWN |
+ SCRBAR_STATE_THUMB_DOWN);
+ if ( nOldStateFlags != mnStateFlags )
+ Invalidate();
+
+ // Restore the old ThumbPosition when canceled
+ if ( rTEvt.IsTrackingCanceled() )
+ {
+ tools::Long nOldPos = mnThumbPos;
+ SetThumbPos( mnStartPos );
+ mnDelta = mnThumbPos-nOldPos;
+ Scroll();
+ }
+
+ if ( meScrollType == ScrollType::Drag )
+ {
+ // On a SCROLLDRAG we recalculate the Thumb, so that it's back to a
+ // rounded ThumbPosition
+ ImplCalc();
+
+ if ( !mbFullDrag && (mnStartPos != mnThumbPos) )
+ {
+ mnDelta = mnThumbPos-mnStartPos;
+ Scroll();
+ mnDelta = 0;
+ }
+ }
+
+ mnDelta = mnThumbPos-mnStartPos;
+ EndScroll();
+ mnDelta = 0;
+ meScrollType = ScrollType::DontKnow;
+
+ if( mpData )
+ mpData->mbHide = false; // re-enable focus blinking
+ }
+ else
+ {
+ Point aPosPixel;
+ if (!IsMapModeEnabled() && GetMapMode().GetMapUnit() == MapUnit::MapTwip)
+ {
+ // rTEvt coordinates are in twips.
+ GetOutDev()->Push(vcl::PushFlags::MAPMODE);
+ EnableMapMode();
+ MapMode aMapMode = GetMapMode();
+ aMapMode.SetOrigin(Point(0, 0));
+ SetMapMode(aMapMode);
+ aPosPixel = LogicToPixel(rTEvt.GetMouseEvent().GetPosPixel());
+ GetOutDev()->Pop();
+ }
+ const Point rMousePos = (GetMapMode().GetMapUnit() != MapUnit::MapTwip ? rTEvt.GetMouseEvent().GetPosPixel() : aPosPixel);
+
+ // Dragging is treated in a special way
+ if ( meScrollType == ScrollType::Drag )
+ ImplDragThumb( rMousePos );
+ else
+ ImplDoMouseAction( rMousePos, rTEvt.IsTrackingRepeat() );
+
+ // If ScrollBar values are translated in a way that there's
+ // nothing left to track, we cancel here
+ if ( !IsVisible() || (mnVisibleSize >= (mnMaxRange-mnMinRange)) )
+ EndTracking();
+ }
+}
+
+void ScrollBar::KeyInput( const KeyEvent& rKEvt )
+{
+ if ( !rKEvt.GetKeyCode().GetModifier() )
+ {
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_HOME:
+ DoScroll( 0 );
+ break;
+
+ case KEY_END:
+ DoScroll( GetRangeMax() );
+ break;
+
+ case KEY_LEFT:
+ case KEY_UP:
+ DoScrollAction( ScrollType::LineUp );
+ break;
+
+ case KEY_RIGHT:
+ case KEY_DOWN:
+ DoScrollAction( ScrollType::LineDown );
+ break;
+
+ case KEY_PAGEUP:
+ DoScrollAction( ScrollType::PageUp );
+ break;
+
+ case KEY_PAGEDOWN:
+ DoScrollAction( ScrollType::PageDown );
+ break;
+
+ default:
+ Control::KeyInput( rKEvt );
+ break;
+ }
+ }
+ else
+ Control::KeyInput( rKEvt );
+}
+
+void ScrollBar::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ rRenderContext.SetBackground();
+}
+
+void ScrollBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImplDraw(rRenderContext);
+}
+
+void ScrollBar::Move()
+{
+ Control::Move();
+ mbCalcSize = true;
+ if (IsReallyVisible())
+ ImplCalc(false);
+ Invalidate();
+}
+
+void ScrollBar::Resize()
+{
+ Control::Resize();
+ mbCalcSize = true;
+ if ( IsReallyVisible() )
+ ImplCalc( false );
+ Invalidate();
+}
+
+IMPL_LINK_NOARG(ScrollBar, ImplAutoTimerHdl, Timer *, void)
+{
+ if( mpData && mpData->mbHide )
+ return;
+ ImplInvert();
+}
+
+void ScrollBar::ImplInvert()
+{
+ tools::Rectangle aRect( maThumbRect );
+ if( aRect.GetWidth() > 5 )
+ {
+ aRect.AdjustLeft(2 );
+ aRect.AdjustRight( -2 );
+ }
+ if( aRect.GetHeight() > 5 )
+ {
+ aRect.AdjustTop(2 );
+ aRect.AdjustBottom( -2 );
+ }
+
+ GetOutDev()->Invert( aRect );
+}
+
+void ScrollBar::GetFocus()
+{
+ if( !mpData )
+ {
+ mpData.reset(new ImplScrollBarData);
+ mpData->maTimer.SetInvokeHandler( LINK( this, ScrollBar, ImplAutoTimerHdl ) );
+ mpData->mbHide = false;
+ }
+ ImplInvert(); // react immediately
+ mpData->maTimer.SetTimeout( GetSettings().GetStyleSettings().GetCursorBlinkTime() );
+ if (mpData->maTimer.GetTimeout() != STYLE_CURSOR_NOBLINKTIME)
+ mpData->maTimer.Start();
+ Control::GetFocus();
+}
+
+void ScrollBar::LoseFocus()
+{
+ if( mpData )
+ mpData->maTimer.Stop();
+ Invalidate();
+
+ Control::LoseFocus();
+}
+
+void ScrollBar::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ ImplCalc( false );
+ else if ( nType == StateChangedType::Data )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ ImplCalc();
+ }
+ else if ( nType == StateChangedType::UpdateMode )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ ImplCalc( false );
+ Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::Enable )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ ImplInitStyle( GetStyle() );
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ if ( (GetPrevStyle() & SCRBAR_VIEW_STYLE) !=
+ (GetStyle() & SCRBAR_VIEW_STYLE) )
+ {
+ mbCalcSize = true;
+ ImplCalc( false );
+ Invalidate();
+ }
+ }
+ }
+}
+
+void ScrollBar::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ mbCalcSize = true;
+ ImplCalc( false );
+ Invalidate();
+ }
+}
+
+tools::Rectangle* ScrollBar::ImplFindPartRect( const Point& rPt )
+{
+ bool bHorizontal = ( GetStyle() & WB_HORZ ) != 0;
+ bool bIsInside = false;
+
+ Point aPoint( 0, 0 );
+ tools::Rectangle aControlRegion( aPoint, GetOutputSizePixel() );
+
+ if( GetOutDev()->HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonRight: ControlPart::ButtonLeft): ControlPart::ButtonUp,
+ aControlRegion, rPt, bIsInside )?
+ bIsInside:
+ maBtn1Rect.Contains( rPt ) )
+ return &maBtn1Rect;
+ else if( GetOutDev()->HitTestNativeScrollbar( bHorizontal? (IsRTLEnabled()? ControlPart::ButtonLeft: ControlPart::ButtonRight): ControlPart::ButtonDown,
+ aControlRegion, rPt, bIsInside )?
+ bIsInside:
+ maBtn2Rect.Contains( rPt ) )
+ return &maBtn2Rect;
+ // HitTestNativeScrollbar, see remark at top of file
+ else if( GetOutDev()->HitTestNativeScrollbar( bHorizontal ? ControlPart::TrackHorzLeft : ControlPart::TrackVertUpper,
+ maPage1Rect, rPt, bIsInside)?
+ bIsInside:
+ maPage1Rect.Contains( rPt ) )
+ return &maPage1Rect;
+ // HitTestNativeScrollbar, see remark at top of file
+ else if( GetOutDev()->HitTestNativeScrollbar( bHorizontal ? ControlPart::TrackHorzRight : ControlPart::TrackVertLower,
+ maPage2Rect, rPt, bIsInside)?
+ bIsInside:
+ maPage2Rect.Contains( rPt ) )
+ return &maPage2Rect;
+ // HitTestNativeScrollbar, see remark at top of file
+ else if( GetOutDev()->HitTestNativeScrollbar( bHorizontal ? ControlPart::ThumbHorz : ControlPart::ThumbVert,
+ maThumbRect, rPt, bIsInside)?
+ bIsInside:
+ maThumbRect.Contains( rPt ) )
+ return &maThumbRect;
+ else
+ return nullptr;
+}
+
+bool ScrollBar::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == NotifyEventType::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
+ {
+ // Trigger a redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::Scrollbar, ControlPart::Entire) )
+ {
+ tools::Rectangle* pRect = ImplFindPartRect( GetPointerPosPixel() );
+ tools::Rectangle* pLastRect = ImplFindPartRect( GetLastPointerPosPixel() );
+ if( pRect != pLastRect || pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow() )
+ {
+ vcl::Region aRgn( GetOutDev()->GetActiveClipRegion() );
+ vcl::Region aClipRegion;
+
+ if ( pRect )
+ aClipRegion.Union( *pRect );
+ if ( pLastRect )
+ aClipRegion.Union( *pLastRect );
+
+ // Support for 3-button scroll bars
+ bool bHas3Buttons = IsNativeControlSupported( ControlType::Scrollbar, ControlPart::HasThreeButtons );
+ if ( bHas3Buttons && ( pRect == &maBtn1Rect || pLastRect == &maBtn1Rect ) )
+ {
+ aClipRegion.Union( maBtn2Rect );
+ }
+
+ GetOutDev()->SetClipRegion( aClipRegion );
+ Invalidate(aClipRegion.GetBoundRect());
+
+ GetOutDev()->SetClipRegion( aRgn );
+ }
+ }
+ }
+ }
+
+ return Control::PreNotify(rNEvt);
+}
+
+void ScrollBar::Scroll()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ScrollbarScroll, [this] () { maScrollHdl.Call(this); } );
+}
+
+void ScrollBar::EndScroll()
+{
+ ImplCallEventListenersAndHandler( VclEventId::ScrollbarEndScroll, [this] () { maEndScrollHdl.Call(this); } );
+}
+
+tools::Long ScrollBar::DoScroll( tools::Long nNewPos )
+{
+ if ( meScrollType != ScrollType::DontKnow )
+ return 0;
+
+ SAL_INFO("vcl.scrollbar", "DoScroll(" << nNewPos << ")");
+ meScrollType = ScrollType::Drag;
+ tools::Long nDelta = ImplScroll( nNewPos, true );
+ meScrollType = ScrollType::DontKnow;
+ return nDelta;
+}
+
+tools::Long ScrollBar::DoScrollAction( ScrollType eScrollType )
+{
+ if ( (meScrollType != ScrollType::DontKnow) ||
+ (eScrollType == ScrollType::DontKnow) ||
+ (eScrollType == ScrollType::Drag) )
+ return 0;
+
+ meScrollType = eScrollType;
+ tools::Long nDelta = ImplDoAction( true );
+ meScrollType = ScrollType::DontKnow;
+ return nDelta;
+}
+
+void ScrollBar::SetRangeMin( tools::Long nNewRange )
+{
+ SetRange( Range( nNewRange, GetRangeMax() ) );
+}
+
+void ScrollBar::SetRangeMax( tools::Long nNewRange )
+{
+ SetRange( Range( GetRangeMin(), nNewRange ) );
+}
+
+void ScrollBar::SetRange( const Range& rRange )
+{
+ // Adapt Range
+ Range aRange = rRange;
+ aRange.Normalize();
+ tools::Long nNewMinRange = aRange.Min();
+ tools::Long nNewMaxRange = aRange.Max();
+
+ // If Range differs, set a new one
+ if ( (mnMinRange == nNewMinRange) && (mnMaxRange == nNewMaxRange))
+ return;
+
+ mnMinRange = nNewMinRange;
+ mnMaxRange = nNewMaxRange;
+
+ // Adapt Thumb
+ if ( mnThumbPos > mnMaxRange-mnVisibleSize )
+ mnThumbPos = mnMaxRange-mnVisibleSize;
+ if ( mnThumbPos < mnMinRange )
+ mnThumbPos = mnMinRange;
+
+ CompatStateChanged( StateChangedType::Data );
+}
+
+void ScrollBar::SetThumbPos( tools::Long nNewThumbPos )
+{
+ if ( nNewThumbPos > mnMaxRange-mnVisibleSize )
+ nNewThumbPos = mnMaxRange-mnVisibleSize;
+ if ( nNewThumbPos < mnMinRange )
+ nNewThumbPos = mnMinRange;
+
+ if ( mnThumbPos != nNewThumbPos )
+ {
+ mnThumbPos = nNewThumbPos;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void ScrollBar::SetVisibleSize( tools::Long nNewSize )
+{
+ if ( mnVisibleSize != nNewSize )
+ {
+ mnVisibleSize = nNewSize;
+
+ // Adapt Thumb
+ if ( mnThumbPos > mnMaxRange-mnVisibleSize )
+ mnThumbPos = mnMaxRange-mnVisibleSize;
+ if ( mnThumbPos < mnMinRange )
+ mnThumbPos = mnMinRange;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+Size ScrollBar::GetOptimalSize() const
+{
+ if (mbCalcSize)
+ const_cast<ScrollBar*>(this)->ImplCalc(false);
+
+ Size aRet = getCurrentCalcSize();
+
+ const tools::Long nMinThumbSize = GetSettings().GetStyleSettings().GetMinThumbSize();
+
+ if (GetStyle() & WB_HORZ)
+ {
+ aRet.setWidth( maBtn1Rect.GetWidth() + nMinThumbSize + maBtn2Rect.GetWidth() );
+ }
+ else
+ {
+ aRet.setHeight( maBtn1Rect.GetHeight() + nMinThumbSize + maBtn2Rect.GetHeight() );
+ }
+
+ return aRet;
+}
+
+Size ScrollBar::getCurrentCalcSize() const
+{
+ tools::Rectangle aCtrlRegion;
+ aCtrlRegion.Union(maBtn1Rect);
+ aCtrlRegion.Union(maBtn2Rect);
+ aCtrlRegion.Union(maPage1Rect);
+ aCtrlRegion.Union(maPage2Rect);
+ aCtrlRegion.Union(maThumbRect);
+ return aCtrlRegion.GetSize();
+}
+
+bool ScrollBar::Inactive() const
+{
+ return !IsEnabled() || !IsInputEnabled() || IsInModalMode();
+}
+
+void ScrollBarBox::ImplInit(vcl::Window* pParent, WinBits nStyle)
+{
+ Window::ImplInit( pParent, nStyle, nullptr );
+
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ tools::Long nScrollSize = rStyleSettings.GetScrollBarSize();
+ SetSizePixel(Size(nScrollSize, nScrollSize));
+}
+
+ScrollBarBox::ScrollBarBox( vcl::Window* pParent, WinBits nStyle ) :
+ Window( WindowType::SCROLLBARBOX )
+{
+ ImplInit( pParent, nStyle );
+}
+
+void ScrollBarBox::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ if (rRenderContext.IsBackground())
+ {
+ Color aColor = rRenderContext.GetSettings().GetStyleSettings().GetFaceColor();
+ ApplyControlBackground(rRenderContext, aColor);
+ }
+}
+
+void ScrollBarBox::StateChanged( StateChangedType nType )
+{
+ Window::StateChanged( nType );
+
+ if (nType == StateChangedType::ControlBackground)
+ {
+ Invalidate();
+ }
+}
+
+void ScrollBarBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Window::DataChanged( rDCEvt );
+
+ if ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))
+ {
+ Invalidate();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/slider.cxx b/vcl/source/control/slider.cxx
new file mode 100644
index 0000000000..3a119ea4f5
--- /dev/null
+++ b/vcl/source/control/slider.cxx
@@ -0,0 +1,906 @@
+/* -*- 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/decoview.hxx>
+#include <slider.hxx>
+#include <vcl/settings.hxx>
+
+#include "thumbpos.hxx"
+
+#define SLIDER_STATE_CHANNEL1_DOWN (sal_uInt16(0x0001))
+#define SLIDER_STATE_CHANNEL2_DOWN (sal_uInt16(0x0002))
+#define SLIDER_STATE_THUMB_DOWN (sal_uInt16(0x0004))
+
+#define SLIDER_THUMB_SIZE 9
+#define SLIDER_CHANNEL_SIZE 4
+#define SLIDER_CHANNEL_HALFSIZE 2
+
+#define SLIDER_HEIGHT 16
+
+#define SLIDER_VIEW_STYLE (WB_3DLOOK | WB_HORZ | WB_VERT)
+
+void Slider::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ mnThumbPixOffset = 0;
+ mnThumbPixRange = 0;
+ mnThumbPixPos = 0; // between mnThumbPixOffset and mnThumbPixOffset+mnThumbPixRange
+ mnThumbSize = SLIDER_THUMB_SIZE;
+ mnChannelPixRange = 0;
+ mnChannelPixTop = 0;
+ mnChannelPixBottom = 0;
+
+ mnMinRange = 0;
+ mnMaxRange = 100;
+ mnThumbPos = 0;
+ mnLineSize = 1;
+ mnPageSize = 1;
+ mnStateFlags = 0;
+ meScrollType = ScrollType::DontKnow;
+ mbCalcSize = true;
+
+ Control::ImplInit( pParent, nStyle, nullptr );
+
+ ImplInitSettings();
+ SetSizePixel( CalcWindowSizePixel() );
+}
+
+Slider::Slider( vcl::Window* pParent, WinBits nStyle ) :
+ Control(WindowType::SLIDER)
+{
+ ImplInit( pParent, nStyle );
+}
+
+Slider::~Slider()
+{
+ disposeOnce();
+}
+
+void Slider::ImplInitSettings()
+{
+ vcl::Window* pParent = GetParent();
+ if ( pParent->IsChildTransparentModeEnabled() && !IsControlBackground() )
+ {
+ EnableChildTransparentMode();
+ SetParentClipMode( ParentClipMode::NoClip );
+ SetPaintTransparent( true );
+ SetBackground();
+ }
+ else
+ {
+ EnableChildTransparentMode( false );
+ SetParentClipMode();
+ SetPaintTransparent( false );
+
+ if ( IsControlBackground() )
+ SetBackground( GetControlBackground() );
+ else
+ SetBackground( pParent->GetBackground() );
+ }
+}
+
+void Slider::ImplUpdateRects( bool bUpdate )
+{
+ tools::Rectangle aOldThumbRect = maThumbRect;
+ bool bInvalidateAll = false;
+
+ if ( mnThumbPixRange )
+ {
+ if ( GetStyle() & WB_HORZ )
+ {
+ maThumbRect.SetLeft(mnThumbPixPos - (mnThumbSize / 2));
+ maThumbRect.SetRight(maThumbRect.Left() + mnThumbSize - 1);
+ if ( 0 < maThumbRect.Left() )
+ {
+ maChannel1Rect.SetLeft( 0 );
+ maChannel1Rect.SetRight( maThumbRect.Left()-1 );
+ maChannel1Rect.SetTop( mnChannelPixTop );
+ maChannel1Rect.SetBottom( mnChannelPixBottom );
+ }
+ else
+ maChannel1Rect.SetEmpty();
+ if ( mnChannelPixRange-1 > maThumbRect.Right() )
+ {
+ maChannel2Rect.SetLeft( maThumbRect.Right()+1 );
+ maChannel2Rect.SetRight( mnChannelPixRange-1 );
+ maChannel2Rect.SetTop( mnChannelPixTop );
+ maChannel2Rect.SetBottom( mnChannelPixBottom );
+ }
+ else
+ maChannel2Rect.SetEmpty();
+
+ const tools::Rectangle aControlRegion(tools::Rectangle(Point(), Size(mnThumbSize, 10)));
+ tools::Rectangle aThumbBounds, aThumbContent;
+ if ( GetNativeControlRegion( ControlType::Slider, ControlPart::ThumbHorz,
+ aControlRegion, ControlState::NONE, ImplControlValue(),
+ aThumbBounds, aThumbContent ) )
+ {
+ maThumbRect.SetLeft( mnThumbPixPos - aThumbBounds.GetWidth()/2 );
+ maThumbRect.SetRight( maThumbRect.Left() + aThumbBounds.GetWidth() - 1 );
+ bInvalidateAll = true;
+ }
+ }
+ else
+ {
+ maThumbRect.SetTop( mnThumbPixPos - (mnThumbSize / 2));
+ maThumbRect.SetBottom( maThumbRect.Top() + mnThumbSize - 1);
+ if ( 0 < maThumbRect.Top() )
+ {
+ maChannel1Rect.SetTop( 0 );
+ maChannel1Rect.SetBottom( maThumbRect.Top()-1 );
+ maChannel1Rect.SetLeft( mnChannelPixTop );
+ maChannel1Rect.SetRight( mnChannelPixBottom );
+ }
+ else
+ maChannel1Rect.SetEmpty();
+ if ( mnChannelPixRange-1 > maThumbRect.Bottom() )
+ {
+ maChannel2Rect.SetTop( maThumbRect.Bottom()+1 );
+ maChannel2Rect.SetBottom( mnChannelPixRange-1 );
+ maChannel2Rect.SetLeft( mnChannelPixTop );
+ maChannel2Rect.SetRight( mnChannelPixBottom );
+ }
+ else
+ maChannel2Rect.SetEmpty();
+
+ const tools::Rectangle aControlRegion(tools::Rectangle(Point(), Size(10, mnThumbSize)));
+ tools::Rectangle aThumbBounds, aThumbContent;
+ if ( GetNativeControlRegion( ControlType::Slider, ControlPart::ThumbVert,
+ aControlRegion, ControlState::NONE, ImplControlValue(),
+ aThumbBounds, aThumbContent ) )
+ {
+ maThumbRect.SetTop( mnThumbPixPos - aThumbBounds.GetHeight()/2 );
+ maThumbRect.SetBottom( maThumbRect.Top() + aThumbBounds.GetHeight() - 1 );
+ bInvalidateAll = true;
+ }
+ }
+ }
+ else
+ {
+ maChannel1Rect.SetEmpty();
+ maChannel2Rect.SetEmpty();
+ maThumbRect.SetEmpty();
+ }
+
+ if ( !bUpdate )
+ return;
+
+ if ( aOldThumbRect == maThumbRect )
+ return;
+
+ if( bInvalidateAll )
+ Invalidate(InvalidateFlags::NoChildren | InvalidateFlags::NoErase);
+ else
+ {
+ vcl::Region aInvalidRegion( aOldThumbRect );
+ aInvalidRegion.Union( maThumbRect );
+
+ if( !IsBackground() && GetParent() )
+ {
+ const Point aPos( GetPosPixel() );
+ aInvalidRegion.Move( aPos.X(), aPos.Y() );
+ GetParent()->Invalidate( aInvalidRegion, InvalidateFlags::Transparent | InvalidateFlags::Update );
+ }
+ else
+ Invalidate( aInvalidRegion );
+ }
+}
+
+tools::Long Slider::ImplCalcThumbPos( tools::Long nPixPos ) const
+{
+ // calculate position
+ tools::Long nCalcThumbPos;
+ nCalcThumbPos = ImplMulDiv( nPixPos-mnThumbPixOffset, mnMaxRange-mnMinRange, mnThumbPixRange-1 );
+ nCalcThumbPos += mnMinRange;
+ return nCalcThumbPos;
+}
+
+tools::Long Slider::ImplCalcThumbPosPix( tools::Long nPos ) const
+{
+ // calculate position
+ tools::Long nCalcThumbPos;
+ nCalcThumbPos = ImplMulDiv( nPos-mnMinRange, mnThumbPixRange-1, mnMaxRange-mnMinRange );
+ // at the beginning and end we try to display Slider correctly
+ if ( !nCalcThumbPos && (mnThumbPos > mnMinRange) )
+ nCalcThumbPos = 1;
+ if ( nCalcThumbPos &&
+ (nCalcThumbPos == mnThumbPixRange-1) &&
+ (mnThumbPos < mnMaxRange) )
+ nCalcThumbPos--;
+ return nCalcThumbPos+mnThumbPixOffset;
+}
+
+void Slider::ImplCalc( bool bUpdate )
+{
+ bool bInvalidateAll = false;
+
+ if (mbCalcSize)
+ {
+ if (GetStyle() & WB_HORZ)
+ {
+ const tools::Rectangle aControlRegion(tools::Rectangle(Point(), Size(SLIDER_THUMB_SIZE, 10)));
+ tools::Rectangle aThumbBounds, aThumbContent;
+ if (GetNativeControlRegion(ControlType::Slider, ControlPart::ThumbHorz,
+ aControlRegion, ControlState::NONE, ImplControlValue(),
+ aThumbBounds, aThumbContent))
+ {
+ mnThumbSize = aThumbBounds.GetWidth();
+ }
+ else
+ {
+ mnThumbSize = SLIDER_THUMB_SIZE;
+ }
+ }
+ else
+ {
+ const tools::Rectangle aControlRegion(tools::Rectangle(Point(), Size(10, SLIDER_THUMB_SIZE)));
+ tools::Rectangle aThumbBounds, aThumbContent;
+ if (GetNativeControlRegion( ControlType::Slider, ControlPart::ThumbVert,
+ aControlRegion, ControlState::NONE, ImplControlValue(),
+ aThumbBounds, aThumbContent))
+ {
+ mnThumbSize = aThumbBounds.GetHeight();
+ }
+ else
+ {
+ mnThumbSize = SLIDER_THUMB_SIZE;
+ }
+ }
+
+ tools::Long nOldChannelPixRange = mnChannelPixRange;
+ tools::Long nOldChannelPixTop = mnChannelPixTop;
+ tools::Long nOldChannelPixBottom = mnChannelPixBottom;
+ tools::Long nCalcWidth;
+ tools::Long nCalcHeight;
+
+ maChannel1Rect.SetEmpty();
+ maChannel2Rect.SetEmpty();
+ maThumbRect.SetEmpty();
+
+ Size aSize = GetOutputSizePixel();
+ if ( GetStyle() & WB_HORZ )
+ {
+ nCalcWidth = aSize.Width();
+ nCalcHeight = aSize.Height();
+ maThumbRect.SetTop( 0 );
+ maThumbRect.SetBottom( aSize.Height()-1 );
+ }
+ else
+ {
+ nCalcWidth = aSize.Height();
+ nCalcHeight = aSize.Width();
+ maThumbRect.SetLeft( 0 );
+ maThumbRect.SetRight( aSize.Width()-1 );
+ }
+
+ if (nCalcWidth >= mnThumbSize)
+ {
+ mnThumbPixOffset = mnThumbSize / 2;
+ mnThumbPixRange = nCalcWidth - mnThumbSize;
+ mnThumbPixPos = 0;
+ mnChannelPixRange = nCalcWidth;
+ mnChannelPixTop = (nCalcHeight/2)-SLIDER_CHANNEL_HALFSIZE;
+ mnChannelPixBottom = mnChannelPixTop+SLIDER_CHANNEL_SIZE-1;
+ }
+ else
+ {
+ mnThumbPixRange = 0;
+ mnChannelPixRange = 0;
+ }
+
+ if ( (nOldChannelPixRange != mnChannelPixRange) ||
+ (nOldChannelPixTop != mnChannelPixTop) ||
+ (nOldChannelPixBottom != mnChannelPixBottom) )
+ bInvalidateAll = true;
+
+ mbCalcSize = false;
+ }
+
+ if ( mnThumbPixRange )
+ mnThumbPixPos = ImplCalcThumbPosPix( mnThumbPos );
+
+ if ( bUpdate && bInvalidateAll )
+ {
+ Invalidate();
+ bUpdate = false;
+ }
+ ImplUpdateRects( bUpdate );
+}
+
+void Slider::ImplDraw(vcl::RenderContext& rRenderContext)
+{
+ // do missing calculations
+ if (mbCalcSize)
+ ImplCalc(false);
+
+ ControlPart nPart = (GetStyle() & WB_HORZ) ? ControlPart::TrackHorzArea : ControlPart::TrackVertArea;
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::Slider, nPart))
+ {
+ ControlState nState = (IsEnabled() ? ControlState::ENABLED : ControlState::NONE);
+ nState |= (HasFocus() ? ControlState::FOCUSED : ControlState::NONE);
+
+ SliderValue aSliderValue;
+ aSliderValue.mnMin = mnMinRange;
+ aSliderValue.mnMax = mnMaxRange;
+ aSliderValue.mnCur = mnThumbPos;
+ aSliderValue.maThumbRect = maThumbRect;
+
+ if (IsMouseOver())
+ {
+ if (maThumbRect.Contains(GetPointerPosPixel()))
+ aSliderValue.mnThumbState |= ControlState::ROLLOVER;
+ }
+
+ const tools::Rectangle aCtrlRegion(Point(0,0), GetOutputSizePixel());
+
+ if (rRenderContext.DrawNativeControl(ControlType::Slider, nPart, aCtrlRegion, nState, aSliderValue, OUString()))
+ return;
+ }
+
+ DecorationView aDecoView(&rRenderContext);
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ bool bEnabled = IsEnabled();
+
+ if (!maChannel1Rect.IsEmpty())
+ {
+ tools::Long nRectSize;
+ tools::Rectangle aRect = maChannel1Rect;
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ if (GetStyle() & WB_HORZ)
+ {
+ rRenderContext.DrawLine(aRect.TopLeft(), Point(aRect.Left(), aRect.Bottom() - 1));
+ rRenderContext.DrawLine(aRect.TopLeft(), aRect.TopRight());
+ }
+ else
+ {
+ rRenderContext.DrawLine(aRect.TopLeft(), Point(aRect.Right() - 1, aRect.Top()));
+ rRenderContext.DrawLine(aRect.TopLeft(), aRect.BottomLeft());
+ }
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ if (GetStyle() & WB_HORZ)
+ {
+ rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
+ nRectSize = aRect.GetWidth();
+ }
+ else
+ {
+ rRenderContext.DrawLine(aRect.TopRight(), aRect.BottomRight());
+ nRectSize = aRect.GetHeight();
+ }
+
+ if (nRectSize > 1)
+ {
+ aRect.AdjustLeft( 1 );
+ aRect.AdjustTop( 1 );
+ if (GetStyle() & WB_HORZ)
+ aRect.AdjustBottom( -1 );
+ else
+ aRect.AdjustRight( -1 );
+ rRenderContext.SetLineColor();
+ if (mnStateFlags & SLIDER_STATE_CHANNEL1_DOWN)
+ rRenderContext.SetFillColor(rStyleSettings.GetShadowColor());
+ else
+ rRenderContext.SetFillColor(rStyleSettings.GetCheckedColor());
+ rRenderContext.DrawRect(aRect);
+ }
+ }
+
+ if (!maChannel2Rect.IsEmpty())
+ {
+ tools::Long nRectSize;
+ tools::Rectangle aRect = maChannel2Rect;
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ if (GetStyle() & WB_HORZ)
+ {
+ rRenderContext.DrawLine(aRect.TopRight(), aRect.BottomRight());
+ rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
+ nRectSize = aRect.GetWidth();
+ }
+ else
+ {
+ rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
+ rRenderContext.DrawLine(aRect.TopRight(), aRect.BottomRight());
+ nRectSize = aRect.GetHeight();
+ }
+
+ if (nRectSize > 1)
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ if (GetStyle() & WB_HORZ)
+ rRenderContext.DrawLine(aRect.TopLeft(), Point(aRect.Right() - 1, aRect.Top()));
+ else
+ rRenderContext.DrawLine(aRect.TopLeft(), Point(aRect.Left(), aRect.Bottom() - 1));
+
+ aRect.AdjustRight( -1 );
+ aRect.AdjustBottom( -1 );
+ if (GetStyle() & WB_HORZ)
+ aRect.AdjustTop( 1 );
+ else
+ aRect.AdjustLeft( 1 );
+ rRenderContext.SetLineColor();
+ if (mnStateFlags & SLIDER_STATE_CHANNEL2_DOWN)
+ rRenderContext.SetFillColor(rStyleSettings.GetShadowColor());
+ else
+ rRenderContext.SetFillColor(rStyleSettings.GetCheckedColor());
+ rRenderContext.DrawRect(aRect);
+ }
+ }
+
+ if (maThumbRect.IsEmpty())
+ return;
+
+ if (bEnabled)
+ {
+ DrawButtonFlags nStyle = DrawButtonFlags::NONE;
+ if (mnStateFlags & SLIDER_STATE_THUMB_DOWN)
+ nStyle |= DrawButtonFlags::Pressed;
+ aDecoView.DrawButton(maThumbRect, nStyle);
+ }
+ else
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.SetFillColor(rStyleSettings.GetCheckedColor());
+ rRenderContext.DrawRect(maThumbRect);
+ }
+}
+
+bool Slider::ImplIsPageUp( const Point& rPos ) const
+{
+ Size aSize = GetOutputSizePixel();
+ tools::Rectangle aRect = maChannel1Rect;
+ if ( GetStyle() & WB_HORZ )
+ {
+ aRect.SetTop( 0 );
+ aRect.SetBottom( aSize.Height()-1 );
+ }
+ else
+ {
+ aRect.SetLeft( 0 );
+ aRect.SetRight( aSize.Width()-1 );
+ }
+ return aRect.Contains( rPos );
+}
+
+bool Slider::ImplIsPageDown( const Point& rPos ) const
+{
+ Size aSize = GetOutputSizePixel();
+ tools::Rectangle aRect = maChannel2Rect;
+ if ( GetStyle() & WB_HORZ )
+ {
+ aRect.SetTop( 0 );
+ aRect.SetBottom( aSize.Height()-1 );
+ }
+ else
+ {
+ aRect.SetLeft( 0 );
+ aRect.SetRight( aSize.Width()-1 );
+ }
+ return aRect.Contains( rPos );
+}
+
+tools::Long Slider::ImplSlide( tools::Long nNewPos )
+{
+ tools::Long nOldPos = mnThumbPos;
+ SetThumbPos( nNewPos );
+ tools::Long nDelta = mnThumbPos-nOldPos;
+ if ( nDelta )
+ {
+ Slide();
+ }
+ return nDelta;
+}
+
+tools::Long Slider::ImplDoAction()
+{
+ tools::Long nDelta = 0;
+
+ switch ( meScrollType )
+ {
+ case ScrollType::LineUp:
+ nDelta = ImplSlide( mnThumbPos-mnLineSize );
+ break;
+
+ case ScrollType::LineDown:
+ nDelta = ImplSlide( mnThumbPos+mnLineSize );
+ break;
+
+ case ScrollType::PageUp:
+ nDelta = ImplSlide( mnThumbPos-mnPageSize );
+ break;
+
+ case ScrollType::PageDown:
+ nDelta = ImplSlide( mnThumbPos+mnPageSize );
+ break;
+
+ default:
+ break;
+ }
+
+ return nDelta;
+}
+
+void Slider::ImplDoMouseAction( const Point& rMousePos, bool bCallAction )
+{
+ sal_uInt16 nOldStateFlags = mnStateFlags;
+ bool bAction = false;
+
+ switch ( meScrollType )
+ {
+ case ScrollType::PageUp:
+ if ( ImplIsPageUp( rMousePos ) )
+ {
+ bAction = bCallAction;
+ mnStateFlags |= SLIDER_STATE_CHANNEL1_DOWN;
+ }
+ else
+ mnStateFlags &= ~SLIDER_STATE_CHANNEL1_DOWN;
+ break;
+
+ case ScrollType::PageDown:
+ if ( ImplIsPageDown( rMousePos ) )
+ {
+ bAction = bCallAction;
+ mnStateFlags |= SLIDER_STATE_CHANNEL2_DOWN;
+ }
+ else
+ mnStateFlags &= ~SLIDER_STATE_CHANNEL2_DOWN;
+ break;
+ default:
+ break;
+ }
+
+ if ( bAction )
+ {
+ if ( ImplDoAction() )
+ {
+ Invalidate();
+ }
+ }
+ else if ( nOldStateFlags != mnStateFlags )
+ {
+ Invalidate();
+ }
+}
+
+void Slider::ImplDoSlide( tools::Long nNewPos )
+{
+ if ( meScrollType != ScrollType::DontKnow )
+ return;
+
+ meScrollType = ScrollType::Drag;
+ ImplSlide( nNewPos );
+ meScrollType = ScrollType::DontKnow;
+}
+
+void Slider::ImplDoSlideAction( ScrollType eScrollType )
+{
+ if ( (meScrollType != ScrollType::DontKnow) ||
+ (eScrollType == ScrollType::DontKnow) ||
+ (eScrollType == ScrollType::Drag) )
+ return;
+
+ meScrollType = eScrollType;
+ ImplDoAction();
+ meScrollType = ScrollType::DontKnow;
+}
+
+void Slider::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( !rMEvt.IsLeft() )
+ return;
+
+ const Point& rMousePos = rMEvt.GetPosPixel();
+ StartTrackingFlags nTrackFlags = StartTrackingFlags::NONE;
+
+ if ( maThumbRect.Contains( rMousePos ) )
+ {
+ meScrollType = ScrollType::Drag;
+
+ // calculate additional values
+ Point aCenterPos = maThumbRect.Center();
+ if ( GetStyle() & WB_HORZ )
+ mnMouseOff = rMousePos.X()-aCenterPos.X();
+ else
+ mnMouseOff = rMousePos.Y()-aCenterPos.Y();
+ }
+ else if ( ImplIsPageUp( rMousePos ) )
+ {
+ nTrackFlags = StartTrackingFlags::ButtonRepeat;
+ meScrollType = ScrollType::PageUp;
+ }
+ else if ( ImplIsPageDown( rMousePos ) )
+ {
+ nTrackFlags = StartTrackingFlags::ButtonRepeat;
+ meScrollType = ScrollType::PageDown;
+ }
+
+ // Shall we start Tracking?
+ if( meScrollType != ScrollType::DontKnow )
+ {
+ // store Start position for cancel and EndScroll delta
+ mnStartPos = mnThumbPos;
+ ImplDoMouseAction( rMousePos, /*bCallAction*/true );
+ PaintImmediately();
+
+ StartTracking( nTrackFlags );
+ }
+}
+
+void Slider::MouseButtonUp( const MouseEvent& )
+{
+}
+
+void Slider::Tracking( const TrackingEvent& rTEvt )
+{
+ if ( rTEvt.IsTrackingEnded() )
+ {
+ // reset Button and PageRect state
+ sal_uInt16 nOldStateFlags = mnStateFlags;
+ mnStateFlags &= ~(SLIDER_STATE_CHANNEL1_DOWN | SLIDER_STATE_CHANNEL2_DOWN |
+ SLIDER_STATE_THUMB_DOWN);
+ if ( nOldStateFlags != mnStateFlags )
+ {
+ Invalidate(InvalidateFlags::NoChildren | InvalidateFlags::NoErase);
+ }
+
+ // on cancel, reset the previous Thumb position
+ if ( rTEvt.IsTrackingCanceled() )
+ {
+ SetThumbPos( mnStartPos );
+ Slide();
+ }
+
+ if ( meScrollType == ScrollType::Drag )
+ {
+ // after dragging, recalculate to a rounded Thumb position
+ ImplCalc();
+ PaintImmediately();
+ }
+
+ meScrollType = ScrollType::DontKnow;
+ }
+ else
+ {
+ const Point rMousePos = rTEvt.GetMouseEvent().GetPosPixel();
+
+ // special handling for dragging
+ if ( meScrollType == ScrollType::Drag )
+ {
+ tools::Long nMovePix;
+ Point aCenterPos = maThumbRect.Center();
+ if ( GetStyle() & WB_HORZ )
+ nMovePix = rMousePos.X()-(aCenterPos.X()+mnMouseOff);
+ else
+ nMovePix = rMousePos.Y()-(aCenterPos.Y()+mnMouseOff);
+ // only if the mouse moves in Scroll direction we have to act
+ if ( nMovePix )
+ {
+ mnThumbPixPos += nMovePix;
+ if ( mnThumbPixPos < mnThumbPixOffset )
+ mnThumbPixPos = mnThumbPixOffset;
+ if ( mnThumbPixPos > (mnThumbPixOffset+mnThumbPixRange-1) )
+ mnThumbPixPos = mnThumbPixOffset+mnThumbPixRange-1;
+ tools::Long nOldPos = mnThumbPos;
+ mnThumbPos = ImplCalcThumbPos( mnThumbPixPos );
+ if ( nOldPos != mnThumbPos )
+ {
+ ImplUpdateRects();
+ PaintImmediately();
+ if ( nOldPos != mnThumbPos )
+ {
+ Slide();
+ }
+ }
+ }
+ }
+ else
+ ImplDoMouseAction( rMousePos, rTEvt.IsTrackingRepeat() );
+
+ // end tracking if ScrollBar values indicate we are done
+ if ( !IsVisible() )
+ EndTracking();
+ }
+}
+
+void Slider::KeyInput( const KeyEvent& rKEvt )
+{
+ if ( !rKEvt.GetKeyCode().GetModifier() )
+ {
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_HOME:
+ ImplDoSlide( GetRangeMin() );
+ break;
+ case KEY_END:
+ ImplDoSlide( GetRangeMax() );
+ break;
+
+ case KEY_LEFT:
+ case KEY_UP:
+ ImplDoSlideAction( ScrollType::LineUp );
+ break;
+
+ case KEY_RIGHT:
+ case KEY_DOWN:
+ ImplDoSlideAction( ScrollType::LineDown );
+ break;
+
+ case KEY_PAGEUP:
+ ImplDoSlideAction( ScrollType::PageUp );
+ break;
+
+ case KEY_PAGEDOWN:
+ ImplDoSlideAction( ScrollType::PageDown );
+ break;
+
+ default:
+ Control::KeyInput( rKEvt );
+ break;
+ }
+ }
+ else
+ Control::KeyInput( rKEvt );
+}
+
+void Slider::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ ImplDraw(rRenderContext);
+}
+
+void Slider::Resize()
+{
+ Control::Resize();
+ mbCalcSize = true;
+ if ( IsReallyVisible() )
+ ImplCalc( false );
+ Invalidate(InvalidateFlags::NoChildren | InvalidateFlags::NoErase);
+}
+
+void Slider::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ ImplCalc( false );
+ else if ( nType == StateChangedType::Data )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ ImplCalc();
+ }
+ else if ( nType == StateChangedType::UpdateMode )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ ImplCalc( false );
+ Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::Enable ||
+ nType == StateChangedType::ControlFocus )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ Invalidate();
+ }
+ }
+ else if ( nType == StateChangedType::Style )
+ {
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ if ( (GetPrevStyle() & SLIDER_VIEW_STYLE) !=
+ (GetStyle() & SLIDER_VIEW_STYLE) )
+ {
+ mbCalcSize = true;
+ ImplCalc( false );
+ Invalidate();
+ }
+ }
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+void Slider::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ ImplInitSettings();
+ Invalidate();
+ }
+}
+
+void Slider::Slide()
+{
+ maSlideHdl.Call( this );
+}
+
+void Slider::SetRangeMin(tools::Long nNewRange)
+{
+ SetRange(Range(nNewRange, GetRangeMax()));
+}
+
+void Slider::SetRangeMax(tools::Long nNewRange)
+{
+ SetRange(Range(GetRangeMin(), nNewRange));
+}
+
+void Slider::SetRange( const Range& rRange )
+{
+ // adjust Range
+ Range aRange = rRange;
+ aRange.Normalize();
+ tools::Long nNewMinRange = aRange.Min();
+ tools::Long nNewMaxRange = aRange.Max();
+
+ // reset Range if different
+ if ( (mnMinRange != nNewMinRange) ||
+ (mnMaxRange != nNewMaxRange) )
+ {
+ mnMinRange = nNewMinRange;
+ mnMaxRange = nNewMaxRange;
+
+ // adjust Thumb
+ if ( mnThumbPos > mnMaxRange )
+ mnThumbPos = mnMaxRange;
+ if ( mnThumbPos < mnMinRange )
+ mnThumbPos = mnMinRange;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void Slider::SetThumbPos( tools::Long nNewThumbPos )
+{
+ if ( nNewThumbPos < mnMinRange )
+ nNewThumbPos = mnMinRange;
+ if ( nNewThumbPos > mnMaxRange )
+ nNewThumbPos = mnMaxRange;
+
+ if ( mnThumbPos != nNewThumbPos )
+ {
+ mnThumbPos = nNewThumbPos;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+Size Slider::CalcWindowSizePixel() const
+{
+ tools::Long nWidth = mnMaxRange - mnMinRange + mnThumbSize + 1;
+ tools::Long nHeight = SLIDER_HEIGHT;
+ Size aSize;
+ if ( GetStyle() & WB_HORZ )
+ {
+ aSize.setWidth( nWidth );
+ aSize.setHeight( nHeight );
+ }
+ else
+ {
+ aSize.setHeight( nWidth );
+ aSize.setWidth( nHeight );
+ }
+ return aSize;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/spinbtn.cxx b/vcl/source/control/spinbtn.cxx
new file mode 100644
index 0000000000..d56138c6cd
--- /dev/null
+++ b/vcl/source/control/spinbtn.cxx
@@ -0,0 +1,468 @@
+/* -*- 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/toolkit/spin.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/vclevent.hxx>
+
+#include <spin.hxx>
+
+void SpinButton::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ mbUpperIn = false;
+ mbLowerIn = false;
+ mbInitialUp = false;
+ mbInitialDown = false;
+
+ mnMinRange = 0;
+ mnMaxRange = 100;
+ mnValue = 0;
+ mnValueStep = 1;
+
+ maRepeatTimer.SetTimeout(MouseSettings::GetButtonStartRepeat());
+ maRepeatTimer.SetInvokeHandler(LINK(this, SpinButton, ImplTimeout));
+
+ mbRepeat = 0 != (nStyle & WB_REPEAT);
+
+ if (nStyle & WB_HSCROLL)
+ mbHorz = true;
+ else
+ mbHorz = false;
+
+ Control::ImplInit( pParent, nStyle, nullptr );
+}
+
+SpinButton::SpinButton( vcl::Window* pParent, WinBits nStyle )
+ : Control(WindowType::SPINBUTTON)
+ , maRepeatTimer("SpinButton maRepeatTimer")
+ , mbUpperIsFocused(false)
+{
+ ImplInit(pParent, nStyle);
+}
+
+IMPL_LINK(SpinButton, ImplTimeout, Timer*, pTimer, void)
+{
+ if (pTimer->GetTimeout() == static_cast<sal_uInt64>(MouseSettings::GetButtonStartRepeat()))
+ {
+ pTimer->SetTimeout( GetSettings().GetMouseSettings().GetButtonRepeat() );
+ pTimer->Start();
+ }
+ else
+ {
+ if (mbInitialUp)
+ Up();
+ else
+ Down();
+ }
+}
+
+void SpinButton::Up()
+{
+ if (ImplIsUpperEnabled())
+ {
+ mnValue += mnValueStep;
+ CompatStateChanged(StateChangedType::Data);
+
+ ImplMoveFocus(true);
+ }
+
+ ImplCallEventListenersAndHandler(VclEventId::SpinbuttonUp, nullptr );
+}
+
+void SpinButton::Down()
+{
+ if (ImplIsLowerEnabled())
+ {
+ mnValue -= mnValueStep;
+ CompatStateChanged(StateChangedType::Data);
+
+ ImplMoveFocus(false);
+ }
+
+ ImplCallEventListenersAndHandler(VclEventId::SpinbuttonDown, nullptr );
+}
+
+void SpinButton::Resize()
+{
+ Control::Resize();
+
+ Size aSize(GetOutputSizePixel());
+ tools::Rectangle aRect(Point(), aSize);
+ if (mbHorz)
+ {
+ maLowerRect = tools::Rectangle(0, 0, aSize.Width() / 2, aSize.Height() - 1);
+ maUpperRect = tools::Rectangle(maLowerRect.TopRight(), aRect.BottomRight());
+ }
+ else
+ {
+ maUpperRect = tools::Rectangle(0, 0, aSize.Width() - 1, aSize.Height() / 2);
+ maLowerRect = tools::Rectangle(maUpperRect.BottomLeft(), aRect.BottomRight());
+ }
+
+ ImplCalcFocusRect(ImplIsUpperEnabled() || !ImplIsLowerEnabled());
+
+ Invalidate();
+}
+
+void SpinButton::Draw(OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags)
+{
+ Point aPos = pDev->LogicToPixel(rPos);
+ Size aSize = GetSizePixel();
+
+ pDev->Push();
+ pDev->SetMapMode();
+ if ( !(nFlags & SystemTextColorFlags::Mono) )
+ {
+ // DecoView uses the FaceColor...
+ AllSettings aSettings = pDev->GetSettings();
+ StyleSettings aStyleSettings = aSettings.GetStyleSettings();
+ if ( IsControlBackground() )
+ aStyleSettings.SetFaceColor( GetControlBackground() );
+ else
+ aStyleSettings.SetFaceColor( GetSettings().GetStyleSettings().GetFaceColor() );
+
+ aSettings.SetStyleSettings( aStyleSettings );
+ pDev->SetSettings( aSettings );
+ }
+
+ tools::Rectangle aRect( Point( 0, 0 ), aSize );
+ tools::Rectangle aLowerRect, aUpperRect;
+ if ( mbHorz )
+ {
+ aLowerRect = tools::Rectangle( 0, 0, aSize.Width()/2, aSize.Height()-1 );
+ aUpperRect = tools::Rectangle( aLowerRect.TopRight(), aRect.BottomRight() );
+ }
+ else
+ {
+ aUpperRect = tools::Rectangle( 0, 0, aSize.Width()-1, aSize.Height()/2 );
+ aLowerRect = tools::Rectangle( aUpperRect.BottomLeft(), aRect.BottomRight() );
+ }
+
+ aUpperRect += aPos;
+ aLowerRect += aPos;
+
+ ImplDrawSpinButton(*pDev, this, aUpperRect, aLowerRect, false, false,
+ IsEnabled() && ImplIsUpperEnabled(),
+ IsEnabled() && ImplIsLowerEnabled(), mbHorz, true);
+ pDev->Pop();
+}
+
+void SpinButton::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ HideFocus();
+
+ bool bEnable = IsEnabled();
+ ImplDrawSpinButton(rRenderContext, this, maUpperRect, maLowerRect, mbUpperIn, mbLowerIn,
+ bEnable && ImplIsUpperEnabled(),
+ bEnable && ImplIsLowerEnabled(), mbHorz, true);
+
+ if (HasFocus())
+ ShowFocus(maFocusRect);
+}
+
+void SpinButton::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if ( maUpperRect.Contains( rMEvt.GetPosPixel() ) && ( ImplIsUpperEnabled() ) )
+ {
+ mbUpperIn = true;
+ mbInitialUp = true;
+ Invalidate( maUpperRect );
+ }
+ else if ( maLowerRect.Contains( rMEvt.GetPosPixel() ) && ( ImplIsLowerEnabled() ) )
+ {
+ mbLowerIn = true;
+ mbInitialDown = true;
+ Invalidate( maLowerRect );
+ }
+
+ if ( mbUpperIn || mbLowerIn )
+ {
+ CaptureMouse();
+ if ( mbRepeat )
+ maRepeatTimer.Start();
+ }
+}
+
+void SpinButton::MouseButtonUp( const MouseEvent& )
+{
+ ReleaseMouse();
+ if ( mbRepeat )
+ {
+ maRepeatTimer.Stop();
+ maRepeatTimer.SetTimeout(MouseSettings::GetButtonStartRepeat() );
+ }
+
+ if ( mbUpperIn )
+ {
+ mbUpperIn = false;
+ Invalidate( maUpperRect );
+ Up();
+ }
+ else if ( mbLowerIn )
+ {
+ mbLowerIn = false;
+ Invalidate( maLowerRect );
+ Down();
+ }
+
+ mbInitialUp = mbInitialDown = false;
+}
+
+void SpinButton::MouseMove( const MouseEvent& rMEvt )
+{
+ if ( !rMEvt.IsLeft() || (!mbInitialUp && !mbInitialDown) )
+ return;
+
+ if ( !maUpperRect.Contains( rMEvt.GetPosPixel() ) &&
+ mbUpperIn && mbInitialUp )
+ {
+ mbUpperIn = false;
+ maRepeatTimer.Stop();
+ Invalidate( maUpperRect );
+ }
+ else if ( !maLowerRect.Contains( rMEvt.GetPosPixel() ) &&
+ mbLowerIn && mbInitialDown )
+ {
+ mbLowerIn = false;
+ maRepeatTimer.Stop();
+ Invalidate( maLowerRect );
+ }
+ else if ( maUpperRect.Contains( rMEvt.GetPosPixel() ) &&
+ !mbUpperIn && mbInitialUp )
+ {
+ mbUpperIn = true;
+ if ( mbRepeat )
+ maRepeatTimer.Start();
+ Invalidate( maUpperRect );
+ }
+ else if ( maLowerRect.Contains( rMEvt.GetPosPixel() ) &&
+ !mbLowerIn && mbInitialDown )
+ {
+ mbLowerIn = true;
+ if ( mbRepeat )
+ maRepeatTimer.Start();
+ Invalidate( maLowerRect );
+ }
+}
+
+void SpinButton::KeyInput( const KeyEvent& rKEvt )
+{
+ if ( !rKEvt.GetKeyCode().GetModifier() )
+ {
+ switch ( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ {
+ bool bUp = KEY_RIGHT == rKEvt.GetKeyCode().GetCode();
+ if ( mbHorz && !ImplMoveFocus( bUp ) )
+ bUp ? Up() : Down();
+ }
+ break;
+
+ case KEY_UP:
+ case KEY_DOWN:
+ {
+ bool bUp = KEY_UP == rKEvt.GetKeyCode().GetCode();
+ if ( !mbHorz && !ImplMoveFocus( KEY_UP == rKEvt.GetKeyCode().GetCode() ) )
+ bUp ? Up() : Down();
+ }
+ break;
+
+ case KEY_SPACE:
+ mbUpperIsFocused ? Up() : Down();
+ break;
+
+ default:
+ Control::KeyInput( rKEvt );
+ break;
+ }
+ }
+ else
+ Control::KeyInput( rKEvt );
+}
+
+void SpinButton::StateChanged( StateChangedType nType )
+{
+ switch ( nType )
+ {
+ case StateChangedType::Data:
+ case StateChangedType::Enable:
+ Invalidate();
+ break;
+
+ case StateChangedType::Style:
+ {
+ bool bNewRepeat = 0 != ( GetStyle() & WB_REPEAT );
+ if ( bNewRepeat != mbRepeat )
+ {
+ if ( maRepeatTimer.IsActive() )
+ {
+ maRepeatTimer.Stop();
+ maRepeatTimer.SetTimeout( MouseSettings::GetButtonStartRepeat() );
+ }
+ mbRepeat = bNewRepeat;
+ }
+
+ bool bNewHorz = 0 != ( GetStyle() & WB_HSCROLL );
+ if ( bNewHorz != mbHorz )
+ {
+ mbHorz = bNewHorz;
+ Resize();
+ }
+ }
+ break;
+ default:;
+ }
+
+ Control::StateChanged( nType );
+}
+
+void SpinButton::SetRangeMin( tools::Long nNewRange )
+{
+ SetRange( Range( nNewRange, GetRangeMax() ) );
+}
+
+void SpinButton::SetRangeMax( tools::Long nNewRange )
+{
+ SetRange( Range( GetRangeMin(), nNewRange ) );
+}
+
+void SpinButton::SetRange( const Range& rRange )
+{
+ // adjust rage
+ Range aRange = rRange;
+ aRange.Normalize();
+ tools::Long nNewMinRange = aRange.Min();
+ tools::Long nNewMaxRange = aRange.Max();
+
+ // do something only if old and new range differ
+ if ( (mnMinRange == nNewMinRange) && (mnMaxRange == nNewMaxRange))
+ return;
+
+ mnMinRange = nNewMinRange;
+ mnMaxRange = nNewMaxRange;
+
+ // adjust value to new range, if necessary
+ if ( mnValue > mnMaxRange )
+ mnValue = mnMaxRange;
+ if ( mnValue < mnMinRange )
+ mnValue = mnMinRange;
+
+ CompatStateChanged( StateChangedType::Data );
+}
+
+void SpinButton::SetValue( tools::Long nValue )
+{
+ // adjust, if necessary
+ if ( nValue > mnMaxRange )
+ nValue = mnMaxRange;
+ if ( nValue < mnMinRange )
+ nValue = mnMinRange;
+
+ if ( mnValue != nValue )
+ {
+ mnValue = nValue;
+ CompatStateChanged( StateChangedType::Data );
+ }
+}
+
+void SpinButton::GetFocus()
+{
+ ShowFocus( maFocusRect );
+ Control::GetFocus();
+}
+
+void SpinButton::LoseFocus()
+{
+ HideFocus();
+ Control::LoseFocus();
+}
+
+bool SpinButton::ImplMoveFocus( bool _bUpper )
+{
+ if ( _bUpper == mbUpperIsFocused )
+ return false;
+
+ HideFocus();
+ ImplCalcFocusRect( _bUpper );
+ if ( HasFocus() )
+ ShowFocus( maFocusRect );
+ return true;
+}
+
+void SpinButton::ImplCalcFocusRect( bool _bUpper )
+{
+ maFocusRect = _bUpper ? maUpperRect : maLowerRect;
+ // inflate by some pixels
+ maFocusRect.AdjustLeft(2 );
+ maFocusRect.AdjustTop(2 );
+ maFocusRect.AdjustRight( -2 );
+ maFocusRect.AdjustBottom( -2 );
+ mbUpperIsFocused = _bUpper;
+}
+
+tools::Rectangle* SpinButton::ImplFindPartRect( const Point& rPt )
+{
+ if( maUpperRect.Contains( rPt ) )
+ return &maUpperRect;
+ else if( maLowerRect.Contains( rPt ) )
+ return &maLowerRect;
+ else
+ return nullptr;
+}
+
+bool SpinButton::PreNotify( NotifyEvent& rNEvt )
+{
+ if (rNEvt.GetType() == NotifyEventType::MOUSEMOVE)
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if (pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged())
+ {
+ // trigger redraw if mouse over state has changed
+ if (IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire) ||
+ IsNativeControlSupported(ControlType::Spinbox, ControlPart::AllButtons) )
+ {
+ tools::Rectangle* pRect = ImplFindPartRect( GetPointerPosPixel() );
+ tools::Rectangle* pLastRect = ImplFindPartRect( GetLastPointerPosPixel() );
+ if (pRect != pLastRect || (pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow()))
+ {
+ vcl::Region aRgn(GetOutDev()->GetActiveClipRegion());
+ if (pLastRect)
+ {
+ GetOutDev()->SetClipRegion(vcl::Region(*pLastRect));
+ Invalidate(*pLastRect);
+ GetOutDev()->SetClipRegion( aRgn );
+ }
+ if (pRect)
+ {
+ GetOutDev()->SetClipRegion(vcl::Region(*pRect));
+ Invalidate(*pRect);
+ GetOutDev()->SetClipRegion(aRgn);
+ }
+ }
+ }
+ }
+ }
+
+ return Control::PreNotify(rNEvt);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/spinfld.cxx b/vcl/source/control/spinfld.cxx
new file mode 100644
index 0000000000..973825a3a9
--- /dev/null
+++ b/vcl/source/control/spinfld.cxx
@@ -0,0 +1,1028 @@
+/* -*- 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/event.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/toolkit/spinfld.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <sal/log.hxx>
+
+#include <spin.hxx>
+#include <svdata.hxx>
+
+namespace {
+
+void ImplGetSpinbuttonValue(vcl::Window* pWin,
+ const tools::Rectangle& rUpperRect, const tools::Rectangle& rLowerRect,
+ bool bUpperIn, bool bLowerIn, bool bUpperEnabled, bool bLowerEnabled,
+ bool bHorz, SpinbuttonValue& rValue )
+{
+ // convert spinbutton data to a SpinbuttonValue structure for native painting
+
+ rValue.maUpperRect = rUpperRect;
+ rValue.maLowerRect = rLowerRect;
+
+ Point aPointerPos = pWin->GetPointerPosPixel();
+
+ ControlState nState = ControlState::ENABLED;
+ if (bUpperIn)
+ nState |= ControlState::PRESSED;
+ if (!pWin->IsEnabled() || !bUpperEnabled)
+ nState &= ~ControlState::ENABLED;
+ if (pWin->HasFocus())
+ nState |= ControlState::FOCUSED;
+ if (pWin->IsMouseOver() && rUpperRect.Contains(aPointerPos))
+ nState |= ControlState::ROLLOVER;
+ rValue.mnUpperState = nState;
+
+ nState = ControlState::ENABLED;
+ if (bLowerIn)
+ nState |= ControlState::PRESSED;
+ if (!pWin->IsEnabled() || !bLowerEnabled)
+ nState &= ~ControlState::ENABLED;
+ if (pWin->HasFocus())
+ nState |= ControlState::FOCUSED;
+ // for overlapping spins: highlight only one
+ if (pWin->IsMouseOver() && rLowerRect.Contains(aPointerPos) && !rUpperRect.Contains(aPointerPos))
+ nState |= ControlState::ROLLOVER;
+ rValue.mnLowerState = nState;
+
+ rValue.mnUpperPart = bHorz ? ControlPart::ButtonLeft : ControlPart::ButtonUp;
+ rValue.mnLowerPart = bHorz ? ControlPart::ButtonRight : ControlPart::ButtonDown;
+}
+
+bool ImplDrawNativeSpinfield(vcl::RenderContext& rRenderContext, vcl::Window const * pWin, const SpinbuttonValue& rSpinbuttonValue)
+{
+ bool bNativeOK = false;
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire) &&
+ // there is just no useful native support for spinfields with dropdown
+ !(pWin->GetStyle() & WB_DROPDOWN))
+ {
+ if (rRenderContext.IsNativeControlSupported(ControlType::Spinbox, rSpinbuttonValue.mnUpperPart) &&
+ rRenderContext.IsNativeControlSupported(ControlType::Spinbox, rSpinbuttonValue.mnLowerPart))
+ {
+ // only paint the embedded spin buttons, all buttons are painted at once
+ tools::Rectangle aUpperAndLowerButtons( rSpinbuttonValue.maUpperRect.GetUnion( rSpinbuttonValue.maLowerRect ) );
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::Spinbox, ControlPart::AllButtons, aUpperAndLowerButtons,
+ ControlState::ENABLED, rSpinbuttonValue, OUString());
+ }
+ else
+ {
+ // paint the spinbox as a whole, use borderwindow to have proper clipping
+ vcl::Window* pBorder = pWin->GetWindow(GetWindowType::Border);
+
+ // to not overwrite everything, set the button region as clipregion to the border window
+ tools::Rectangle aClipRect(rSpinbuttonValue.maLowerRect);
+ aClipRect.Union(rSpinbuttonValue.maUpperRect);
+
+ vcl::RenderContext* pContext = &rRenderContext;
+ vcl::Region oldRgn;
+ Point aPt;
+ Size aSize(pBorder->GetOutputSizePixel()); // the size of the border window, i.e., the whole control
+ tools::Rectangle aNatRgn(aPt, aSize);
+
+ if (!pWin->SupportsDoubleBuffering())
+ {
+ // convert from screen space to borderwin space
+ aClipRect.SetPos(pBorder->ScreenToOutputPixel(pWin->OutputToScreenPixel(aClipRect.TopLeft())));
+
+ oldRgn = pBorder->GetOutDev()->GetClipRegion();
+ pBorder->GetOutDev()->SetClipRegion(vcl::Region(aClipRect));
+
+ pContext = pBorder->GetOutDev();
+ }
+
+ tools::Rectangle aBound, aContent;
+ if (!ImplGetSVData()->maNWFData.mbCanDrawWidgetAnySize &&
+ pContext->GetNativeControlRegion(ControlType::Spinbox, ControlPart::Entire,
+ aNatRgn, ControlState::NONE, rSpinbuttonValue,
+ aBound, aContent))
+ {
+ aSize = aContent.GetSize();
+ }
+
+ tools::Rectangle aRgn(aPt, aSize);
+ if (pWin->SupportsDoubleBuffering())
+ {
+ // convert from borderwin space, to the pWin's space
+ aRgn.SetPos(pWin->ScreenToOutputPixel(pBorder->OutputToScreenPixel(aRgn.TopLeft())));
+ }
+
+ bNativeOK = pContext->DrawNativeControl(ControlType::Spinbox, ControlPart::Entire, aRgn,
+ ControlState::ENABLED, rSpinbuttonValue, OUString());
+
+ if (!pWin->SupportsDoubleBuffering())
+ pBorder->GetOutDev()->SetClipRegion(oldRgn);
+ }
+ }
+ return bNativeOK;
+}
+
+bool ImplDrawNativeSpinbuttons(vcl::RenderContext& rRenderContext, const SpinbuttonValue& rSpinbuttonValue)
+{
+ bool bNativeOK = false;
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::SpinButtons, ControlPart::Entire))
+ {
+ tools::Rectangle aArea = rSpinbuttonValue.maUpperRect.GetUnion(rSpinbuttonValue.maLowerRect);
+ // only paint the standalone spin buttons, all buttons are painted at once
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::SpinButtons, ControlPart::AllButtons, aArea,
+ ControlState::ENABLED, rSpinbuttonValue, OUString());
+ }
+ return bNativeOK;
+}
+
+}
+
+void ImplDrawSpinButton(vcl::RenderContext& rRenderContext, vcl::Window* pWindow,
+ const tools::Rectangle& rUpperRect, const tools::Rectangle& rLowerRect,
+ bool bUpperIn, bool bLowerIn, bool bUpperEnabled, bool bLowerEnabled,
+ bool bHorz, bool bMirrorHorz)
+{
+ bool bNativeOK = false;
+
+ if (pWindow)
+ {
+ // are we drawing standalone spin buttons or members of a spinfield ?
+ ControlType aControl = ControlType::SpinButtons;
+ switch (pWindow->GetType())
+ {
+ case WindowType::EDIT:
+ case WindowType::MULTILINEEDIT:
+ case WindowType::PATTERNFIELD:
+ case WindowType::METRICFIELD:
+ case WindowType::CURRENCYFIELD:
+ case WindowType::DATEFIELD:
+ case WindowType::TIMEFIELD:
+ case WindowType::SPINFIELD:
+ case WindowType::FORMATTEDFIELD:
+ aControl = ControlType::Spinbox;
+ break;
+ default:
+ aControl = ControlType::SpinButtons;
+ break;
+ }
+
+ SpinbuttonValue aValue;
+ ImplGetSpinbuttonValue(pWindow, rUpperRect, rLowerRect,
+ bUpperIn, bLowerIn, bUpperEnabled, bLowerEnabled,
+ bHorz, aValue);
+
+ if( aControl == ControlType::Spinbox )
+ bNativeOK = ImplDrawNativeSpinfield(rRenderContext, pWindow, aValue);
+ else if( aControl == ControlType::SpinButtons )
+ bNativeOK = ImplDrawNativeSpinbuttons(rRenderContext, aValue);
+ }
+
+ if (bNativeOK)
+ return;
+
+ ImplDrawUpDownButtons(rRenderContext,
+ rUpperRect, rLowerRect,
+ bUpperIn, bLowerIn, bUpperEnabled, bLowerEnabled,
+ bHorz, bMirrorHorz);
+}
+
+void ImplDrawUpDownButtons(vcl::RenderContext& rRenderContext,
+ const tools::Rectangle& rUpperRect, const tools::Rectangle& rLowerRect,
+ bool bUpperIn, bool bLowerIn, bool bUpperEnabled, bool bLowerEnabled,
+ bool bHorz, bool bMirrorHorz)
+{
+ DecorationView aDecoView(&rRenderContext);
+
+ SymbolType eType1, eType2;
+
+ if ( bHorz )
+ {
+ eType1 = bMirrorHorz ? SymbolType::SPIN_RIGHT : SymbolType::SPIN_LEFT;
+ eType2 = bMirrorHorz ? SymbolType::SPIN_LEFT : SymbolType::SPIN_RIGHT;
+ }
+ else
+ {
+ eType1 = SymbolType::SPIN_UP;
+ eType2 = SymbolType::SPIN_DOWN;
+ }
+
+ DrawButtonFlags nStyle = DrawButtonFlags::NoLeftLightBorder;
+ // draw upper/left Button
+ if (bUpperIn)
+ nStyle |= DrawButtonFlags::Pressed;
+
+ tools::Rectangle aUpRect = aDecoView.DrawButton(rUpperRect, nStyle);
+
+ nStyle = DrawButtonFlags::NoLeftLightBorder;
+ // draw lower/right Button
+ if (bLowerIn)
+ nStyle |= DrawButtonFlags::Pressed;
+
+ tools::Rectangle aLowRect = aDecoView.DrawButton(rLowerRect, nStyle);
+
+ // make use of additional default edge
+ aUpRect.AdjustLeft( -1 );
+ aUpRect.AdjustTop( -1 );
+ aUpRect.AdjustRight( 1 );
+ aUpRect.AdjustBottom( 1 );
+ aLowRect.AdjustLeft( -1 );
+ aLowRect.AdjustTop( -1 );
+ aLowRect.AdjustRight( 1 );
+ aLowRect.AdjustBottom( 1 );
+
+ // draw into the edge, so that something is visible if the rectangle is too small
+ if (aUpRect.GetHeight() < 4)
+ {
+ aUpRect.AdjustRight( 1 );
+ aUpRect.AdjustBottom( 1 );
+ aLowRect.AdjustRight( 1 );
+ aLowRect.AdjustBottom( 1 );
+ }
+
+ // calculate Symbol size
+ tools::Long nTempSize1 = aUpRect.GetWidth();
+ tools::Long nTempSize2 = aLowRect.GetWidth();
+ if (std::abs( nTempSize1-nTempSize2 ) == 1)
+ {
+ if (nTempSize1 > nTempSize2)
+ aUpRect.AdjustLeft( 1 );
+ else
+ aLowRect.AdjustLeft( 1 );
+ }
+ nTempSize1 = aUpRect.GetHeight();
+ nTempSize2 = aLowRect.GetHeight();
+ if (std::abs(nTempSize1 - nTempSize2) == 1)
+ {
+ if (nTempSize1 > nTempSize2)
+ aUpRect.AdjustTop( 1 );
+ else
+ aLowRect.AdjustTop( 1 );
+ }
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ DrawSymbolFlags nSymStyle = DrawSymbolFlags::NONE;
+ if (!bUpperEnabled)
+ nSymStyle |= DrawSymbolFlags::Disable;
+ aDecoView.DrawSymbol(aUpRect, eType1, rStyleSettings.GetButtonTextColor(), nSymStyle);
+
+ nSymStyle = DrawSymbolFlags::NONE;
+ if (!bLowerEnabled)
+ nSymStyle |= DrawSymbolFlags::Disable;
+ aDecoView.DrawSymbol(aLowRect, eType2, rStyleSettings.GetButtonTextColor(), nSymStyle);
+}
+
+void SpinField::ImplInitSpinFieldData()
+{
+ mpEdit.disposeAndClear();
+ mbSpin = false;
+ mbRepeat = false;
+ mbUpperIn = false;
+ mbLowerIn = false;
+ mbInitialUp = false;
+ mbInitialDown = false;
+ mbInDropDown = false;
+ mbUpperEnabled = true;
+ mbLowerEnabled = true;
+}
+
+void SpinField::ImplInit(vcl::Window* pParent, WinBits nWinStyle)
+{
+ Edit::ImplInit( pParent, nWinStyle );
+
+ if (!(nWinStyle & (WB_SPIN | WB_DROPDOWN)))
+ return;
+
+ mbSpin = true;
+
+ // Some themes want external spin buttons, therefore the main
+ // spinfield should not overdraw the border between its encapsulated
+ // edit field and the spin buttons
+ if ((nWinStyle & WB_SPIN) && ImplUseNativeBorder(*GetOutDev(), nWinStyle))
+ {
+ SetBackground();
+ mpEdit.set(VclPtr<Edit>::Create(this, WB_NOBORDER));
+ mpEdit->SetBackground();
+ }
+ else
+ mpEdit.set(VclPtr<Edit>::Create(this, WB_NOBORDER));
+
+ mpEdit->EnableRTL(false);
+ mpEdit->SetPosPixel(Point());
+ mpEdit->Show();
+
+ SetSubEdit(mpEdit);
+
+ maRepeatTimer.SetInvokeHandler(LINK( this, SpinField, ImplTimeout));
+ maRepeatTimer.SetTimeout(MouseSettings::GetButtonStartRepeat());
+ if (nWinStyle & WB_REPEAT)
+ mbRepeat = true;
+
+ SetCompoundControl(true);
+}
+
+SpinField::SpinField(vcl::Window* pParent, WinBits nWinStyle, WindowType nType) :
+ Edit(nType), maRepeatTimer("SpinField maRepeatTimer")
+{
+ ImplInitSpinFieldData();
+ ImplInit(pParent, nWinStyle);
+}
+
+SpinField::~SpinField()
+{
+ disposeOnce();
+}
+
+void SpinField::dispose()
+{
+ mpEdit.disposeAndClear();
+
+ Edit::dispose();
+}
+
+void SpinField::Up()
+{
+ ImplCallEventListenersAndHandler( VclEventId::SpinfieldUp, [this] () { maUpHdlLink.Call(*this); } );
+}
+
+void SpinField::Down()
+{
+ ImplCallEventListenersAndHandler( VclEventId::SpinfieldDown, [this] () { maDownHdlLink.Call(*this); } );
+}
+
+void SpinField::First()
+{
+ ImplCallEventListenersAndHandler(VclEventId::SpinfieldFirst, nullptr);
+}
+
+void SpinField::Last()
+{
+ ImplCallEventListenersAndHandler(VclEventId::SpinfieldLast, nullptr);
+}
+
+void SpinField::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if (!HasFocus() && (!mpEdit || !mpEdit->HasFocus()))
+ {
+ GrabFocus();
+ }
+
+ if (!IsReadOnly())
+ {
+ if (maUpperRect.Contains(rMEvt.GetPosPixel()))
+ {
+ mbUpperIn = true;
+ mbInitialUp = true;
+ Invalidate(maUpperRect);
+ }
+ else if (maLowerRect.Contains(rMEvt.GetPosPixel()))
+ {
+ mbLowerIn = true;
+ mbInitialDown = true;
+ Invalidate(maLowerRect);
+ }
+ else if (maDropDownRect.Contains(rMEvt.GetPosPixel()))
+ {
+ // put DropDownButton to the right
+ mbInDropDown = ShowDropDown( !mbInDropDown );
+ Invalidate(tools::Rectangle(Point(), GetOutputSizePixel()));
+ }
+
+ if (mbUpperIn || mbLowerIn)
+ {
+ CaptureMouse();
+ if (mbRepeat)
+ maRepeatTimer.Start();
+ return;
+ }
+ }
+
+ Edit::MouseButtonDown(rMEvt);
+}
+
+void SpinField::MouseButtonUp(const MouseEvent& rMEvt)
+{
+ ReleaseMouse();
+ mbInitialUp = mbInitialDown = false;
+ maRepeatTimer.Stop();
+ maRepeatTimer.SetTimeout(MouseSettings::GetButtonStartRepeat());
+
+ if (mbUpperIn)
+ {
+ mbUpperIn = false;
+ Invalidate(maUpperRect);
+ Up();
+ }
+ else if (mbLowerIn)
+ {
+ mbLowerIn = false;
+ Invalidate(maLowerRect);
+ Down();
+ }
+
+ Edit::MouseButtonUp(rMEvt);
+}
+
+void SpinField::MouseMove(const MouseEvent& rMEvt)
+{
+ if (rMEvt.IsLeft())
+ {
+ if (mbInitialUp)
+ {
+ bool bNewUpperIn = maUpperRect.Contains(rMEvt.GetPosPixel());
+ if (bNewUpperIn != mbUpperIn)
+ {
+ if (bNewUpperIn)
+ {
+ if (mbRepeat)
+ maRepeatTimer.Start();
+ }
+ else
+ maRepeatTimer.Stop();
+
+ mbUpperIn = bNewUpperIn;
+ Invalidate(maUpperRect);
+ }
+ }
+ else if (mbInitialDown)
+ {
+ bool bNewLowerIn = maLowerRect.Contains(rMEvt.GetPosPixel());
+ if (bNewLowerIn != mbLowerIn)
+ {
+ if (bNewLowerIn)
+ {
+ if (mbRepeat)
+ maRepeatTimer.Start();
+ }
+ else
+ maRepeatTimer.Stop();
+
+ mbLowerIn = bNewLowerIn;
+ Invalidate(maLowerRect);
+ }
+ }
+ }
+
+ Edit::MouseMove(rMEvt);
+}
+
+bool SpinField::EventNotify(NotifyEvent& rNEvt)
+{
+ bool bDone = false;
+ if (rNEvt.GetType() == NotifyEventType::KEYINPUT)
+ {
+ const KeyEvent& rKEvt = *rNEvt.GetKeyEvent();
+ if (!IsReadOnly())
+ {
+ sal_uInt16 nMod = rKEvt.GetKeyCode().GetModifier();
+ switch (rKEvt.GetKeyCode().GetCode())
+ {
+ case KEY_UP:
+ {
+ if (!nMod)
+ {
+ Up();
+ bDone = true;
+ }
+ }
+ break;
+ case KEY_DOWN:
+ {
+ if (!nMod)
+ {
+ Down();
+ bDone = true;
+ }
+ else if ((nMod == KEY_MOD2) && !mbInDropDown && (GetStyle() & WB_DROPDOWN))
+ {
+ mbInDropDown = ShowDropDown(true);
+ Invalidate(tools::Rectangle(Point(), GetOutputSizePixel()));
+ bDone = true;
+ }
+ }
+ break;
+ case KEY_PAGEUP:
+ {
+ if (!nMod)
+ {
+ Last();
+ bDone = true;
+ }
+ }
+ break;
+ case KEY_PAGEDOWN:
+ {
+ if (!nMod)
+ {
+ First();
+ bDone = true;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if (rNEvt.GetType() == NotifyEventType::COMMAND)
+ {
+ if ((rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) && !IsReadOnly())
+ {
+ MouseWheelBehaviour nWheelBehavior(GetSettings().GetMouseSettings().GetWheelBehavior());
+ if (nWheelBehavior == MouseWheelBehaviour::ALWAYS
+ || (nWheelBehavior == MouseWheelBehaviour::FocusOnly && HasChildPathFocus()))
+ {
+ const CommandWheelData* pData = rNEvt.GetCommandEvent()->GetWheelData();
+ if (pData->GetMode() == CommandWheelMode::SCROLL)
+ {
+ if (pData->GetDelta() < 0)
+ Down();
+ else
+ Up();
+ bDone = true;
+
+ if (!HasChildPathFocus())
+ GrabFocus();
+ }
+ }
+ else
+ bDone = false; // don't eat this event, let the default handling happen (i.e. scroll the context)
+ }
+ }
+
+ return bDone || Edit::EventNotify(rNEvt);
+}
+
+void SpinField::FillLayoutData() const
+{
+ if (mbSpin)
+ {
+ mxLayoutData.emplace();
+ AppendLayoutData(*GetSubEdit());
+ GetSubEdit()->SetLayoutDataParent(this);
+ }
+ else
+ Edit::FillLayoutData();
+}
+
+void SpinField::SetUpperEnabled(bool bEnabled)
+{
+ if (mbUpperEnabled == bEnabled)
+ return;
+
+ mbUpperEnabled = bEnabled;
+
+ if (mbSpin)
+ Invalidate(maUpperRect);
+}
+
+void SpinField::SetLowerEnabled(bool bEnabled)
+{
+ if (mbLowerEnabled == bEnabled)
+ return;
+
+ mbLowerEnabled = bEnabled;
+
+ if (mbSpin)
+ Invalidate(maLowerRect);
+}
+
+void SpinField::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (mbSpin)
+ {
+ bool bEnabled = IsEnabled();
+ bool bUpperEnabled = bEnabled && IsUpperEnabled();
+ bool bLowerEnabled = bEnabled && IsLowerEnabled();
+ ImplDrawSpinButton(rRenderContext, this, maUpperRect, maLowerRect,
+ mbUpperIn && bUpperEnabled, mbLowerIn && bLowerEnabled,
+ bUpperEnabled, bLowerEnabled);
+ }
+
+ if (GetStyle() & WB_DROPDOWN)
+ {
+ DecorationView aView(&rRenderContext);
+
+ DrawButtonFlags nStyle = DrawButtonFlags::NoLightBorder;
+ if (mbInDropDown)
+ nStyle |= DrawButtonFlags::Pressed;
+ tools::Rectangle aInnerRect = aView.DrawButton(maDropDownRect, nStyle);
+
+ DrawSymbolFlags nSymbolStyle = IsEnabled() ? DrawSymbolFlags::NONE : DrawSymbolFlags::Disable;
+ aView.DrawSymbol(aInnerRect, SymbolType::SPIN_DOWN, rRenderContext.GetSettings().GetStyleSettings().GetButtonTextColor(), nSymbolStyle);
+ }
+
+ Edit::Paint(rRenderContext, rRect);
+}
+
+void SpinField::ImplCalcButtonAreas(const OutputDevice* pDev, const Size& rOutSz, tools::Rectangle& rDDArea,
+ tools::Rectangle& rSpinUpArea, tools::Rectangle& rSpinDownArea)
+{
+ const StyleSettings& rStyleSettings = pDev->GetSettings().GetStyleSettings();
+
+ Size aSize = rOutSz;
+ Size aDropDownSize;
+
+ if (GetStyle() & WB_DROPDOWN)
+ {
+ tools::Long nW = rStyleSettings.GetScrollBarSize();
+ nW = GetDrawPixel( pDev, nW );
+ aDropDownSize = Size( CalcZoom( nW ), aSize.Height() );
+ aSize.AdjustWidth( -(aDropDownSize.Width()) );
+ rDDArea = tools::Rectangle( Point( aSize.Width(), 0 ), aDropDownSize );
+ rDDArea.AdjustTop( -1 );
+ }
+ else
+ rDDArea.SetEmpty();
+
+ // calculate sizes according to the height
+ if (GetStyle() & WB_SPIN)
+ {
+ tools::Long nBottom1 = aSize.Height()/2;
+ tools::Long nBottom2 = aSize.Height()-1;
+ tools::Long nTop2 = nBottom1;
+ if ( !(aSize.Height() & 0x01) )
+ nBottom1--;
+
+ bool bNativeRegionOK = false;
+ tools::Rectangle aContentUp, aContentDown;
+
+ if ((pDev->GetOutDevType() == OUTDEV_WINDOW) &&
+ // there is just no useful native support for spinfields with dropdown
+ ! (GetStyle() & WB_DROPDOWN) &&
+ IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire))
+ {
+ vcl::Window *pWin = pDev->GetOwnerWindow();
+ vcl::Window *pBorder = pWin->GetWindow( GetWindowType::Border );
+
+ // get the system's spin button size
+ ImplControlValue aControlValue;
+ tools::Rectangle aBound;
+ Point aPoint;
+
+ // use the full extent of the control
+ tools::Rectangle aArea( aPoint, pBorder->GetOutputSizePixel() );
+
+ bNativeRegionOK =
+ pWin->GetNativeControlRegion(ControlType::Spinbox, ControlPart::ButtonUp,
+ aArea, ControlState::NONE, aControlValue, aBound, aContentUp) &&
+ pWin->GetNativeControlRegion(ControlType::Spinbox, ControlPart::ButtonDown,
+ aArea, ControlState::NONE, aControlValue, aBound, aContentDown);
+
+ if (bNativeRegionOK)
+ {
+ // convert back from border space to local coordinates
+ aPoint = pBorder->ScreenToOutputPixel( pWin->OutputToScreenPixel( aPoint ) );
+ aContentUp.Move(-aPoint.X(), -aPoint.Y());
+ aContentDown.Move(-aPoint.X(), -aPoint.Y());
+ }
+ }
+
+ if (bNativeRegionOK)
+ {
+ rSpinUpArea = aContentUp;
+ rSpinDownArea = aContentDown;
+ }
+ else
+ {
+ aSize.AdjustWidth( -(CalcZoom( GetDrawPixel( pDev, rStyleSettings.GetSpinSize() ) )) );
+
+ rSpinUpArea = tools::Rectangle( aSize.Width(), 0, rOutSz.Width()-aDropDownSize.Width()-1, nBottom1 );
+ rSpinDownArea = tools::Rectangle( rSpinUpArea.Left(), nTop2, rSpinUpArea.Right(), nBottom2 );
+ }
+ }
+ else
+ {
+ rSpinUpArea.SetEmpty();
+ rSpinDownArea.SetEmpty();
+ }
+}
+
+void SpinField::Resize()
+{
+ if (!mbSpin)
+ return;
+
+ Control::Resize();
+ Size aSize = GetOutputSizePixel();
+ bool bSubEditPositioned = false;
+
+ if (GetStyle() & (WB_SPIN | WB_DROPDOWN))
+ {
+ ImplCalcButtonAreas( GetOutDev(), aSize, maDropDownRect, maUpperRect, maLowerRect );
+
+ ImplControlValue aControlValue;
+ Point aPoint;
+ tools::Rectangle aContent, aBound;
+
+ // use the full extent of the control
+ vcl::Window *pBorder = GetWindow( GetWindowType::Border );
+ tools::Rectangle aArea( aPoint, pBorder->GetOutputSizePixel() );
+
+ // adjust position and size of the edit field
+ if (GetNativeControlRegion(ControlType::Spinbox, ControlPart::SubEdit, aArea, ControlState::NONE,
+ aControlValue, aBound, aContent) &&
+ // there is just no useful native support for spinfields with dropdown
+ !(GetStyle() & WB_DROPDOWN))
+ {
+ // convert back from border space to local coordinates
+ aPoint = pBorder->ScreenToOutputPixel(OutputToScreenPixel(aPoint));
+ aContent.Move(-aPoint.X(), -aPoint.Y());
+
+ // use the themes drop down size
+ mpEdit->SetPosPixel( aContent.TopLeft() );
+ bSubEditPositioned = true;
+ aSize = aContent.GetSize();
+ }
+ else
+ {
+ if (maUpperRect.IsEmpty())
+ {
+ SAL_WARN_IF( maDropDownRect.IsEmpty(), "vcl", "SpinField::Resize: SPIN && DROPDOWN, but all empty rects?" );
+ aSize.setWidth( maDropDownRect.Left() );
+ }
+ else
+ aSize.setWidth( maUpperRect.Left() );
+ }
+ }
+
+ if (!bSubEditPositioned)
+ {
+ // this moves our sub edit if RTL gets switched
+ mpEdit->SetPosPixel(Point());
+ }
+ mpEdit->SetSizePixel(aSize);
+
+ if (GetStyle() & WB_SPIN)
+ Invalidate(tools::Rectangle(maUpperRect.TopLeft(), maLowerRect.BottomRight()));
+ if (GetStyle() & WB_DROPDOWN)
+ Invalidate(maDropDownRect);
+}
+
+void SpinField::StateChanged(StateChangedType nType)
+{
+ Edit::StateChanged(nType);
+
+ if (nType == StateChangedType::Enable)
+ {
+ if (mbSpin || (GetStyle() & WB_DROPDOWN))
+ {
+ mpEdit->Enable(IsEnabled());
+
+ if (mbSpin)
+ {
+ Invalidate(maLowerRect);
+ Invalidate(maUpperRect);
+ }
+ if (GetStyle() & WB_DROPDOWN)
+ Invalidate(maDropDownRect);
+ }
+ }
+ else if (nType == StateChangedType::Style)
+ {
+ if (GetStyle() & WB_REPEAT)
+ mbRepeat = true;
+ else
+ mbRepeat = false;
+ }
+ else if (nType == StateChangedType::Zoom)
+ {
+ Resize();
+ if (mpEdit)
+ mpEdit->SetZoom(GetZoom());
+ Invalidate();
+ }
+ else if (nType == StateChangedType::ControlFont)
+ {
+ if (mpEdit)
+ mpEdit->SetControlFont(GetControlFont());
+ Invalidate();
+ }
+ else if (nType == StateChangedType::ControlForeground)
+ {
+ if (mpEdit)
+ mpEdit->SetControlForeground(GetControlForeground());
+ Invalidate();
+ }
+ else if (nType == StateChangedType::ControlBackground)
+ {
+ if (mpEdit)
+ mpEdit->SetControlBackground(GetControlBackground());
+ Invalidate();
+ }
+ else if( nType == StateChangedType::Mirroring )
+ {
+ if (mpEdit)
+ mpEdit->CompatStateChanged(StateChangedType::Mirroring);
+ Resize();
+ }
+}
+
+void SpinField::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Edit::DataChanged(rDCEvt);
+
+ if ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))
+ {
+ Resize();
+ Invalidate();
+ }
+}
+
+tools::Rectangle* SpinField::ImplFindPartRect(const Point& rPt)
+{
+ if (maUpperRect.Contains(rPt))
+ return &maUpperRect;
+ else if (maLowerRect.Contains(rPt))
+ return &maLowerRect;
+ else
+ return nullptr;
+}
+
+bool SpinField::PreNotify(NotifyEvent& rNEvt)
+{
+ if (rNEvt.GetType() == NotifyEventType::MOUSEMOVE)
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if (pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged())
+ {
+ // trigger redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::Spinbox, ControlPart::Entire) ||
+ IsNativeControlSupported(ControlType::Spinbox, ControlPart::AllButtons) )
+ {
+ tools::Rectangle* pRect = ImplFindPartRect( GetPointerPosPixel() );
+ tools::Rectangle* pLastRect = ImplFindPartRect( GetLastPointerPosPixel() );
+ if( pRect != pLastRect || (pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow()) )
+ {
+ if (!IsNativeWidgetEnabled() ||
+ !IsNativeControlSupported(ControlType::Editbox, ControlPart::Entire))
+ {
+ // paint directly
+ vcl::Region aRgn( GetOutDev()->GetActiveClipRegion() );
+ if (pLastRect)
+ {
+ GetOutDev()->SetClipRegion(vcl::Region(*pLastRect));
+ Invalidate(*pLastRect);
+ GetOutDev()->SetClipRegion( aRgn );
+ }
+ if (pRect)
+ {
+ GetOutDev()->SetClipRegion(vcl::Region(*pRect));
+ Invalidate(*pRect);
+ GetOutDev()->SetClipRegion( aRgn );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return Edit::PreNotify(rNEvt);
+}
+
+void SpinField::EndDropDown()
+{
+ mbInDropDown = false;
+ Invalidate(tools::Rectangle(Point(), GetOutputSizePixel()));
+}
+
+bool SpinField::ShowDropDown( bool )
+{
+ return false;
+}
+
+Size SpinField::CalcMinimumSizeForText(const OUString &rString) const
+{
+ Size aSz = Edit::CalcMinimumSizeForText(rString);
+
+ if ( GetStyle() & WB_DROPDOWN )
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ if ( GetStyle() & WB_SPIN )
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aArea( Point(), Size(100, aSz.Height()));
+ tools::Rectangle aEntireBound, aEntireContent, aEditBound, aEditContent;
+ if (
+ GetNativeControlRegion(ControlType::Spinbox, ControlPart::Entire,
+ aArea, ControlState::NONE, aControlValue, aEntireBound, aEntireContent) &&
+ GetNativeControlRegion(ControlType::Spinbox, ControlPart::SubEdit,
+ aArea, ControlState::NONE, aControlValue, aEditBound, aEditContent)
+ )
+ {
+ aSz.AdjustWidth(aEntireContent.GetWidth() - aEditContent.GetWidth());
+ }
+ else
+ {
+ aSz.AdjustWidth(maUpperRect.GetWidth() );
+ }
+ }
+
+ return aSz;
+}
+
+Size SpinField::CalcMinimumSize() const
+{
+ return CalcMinimumSizeForText(GetText());
+}
+
+Size SpinField::GetOptimalSize() const
+{
+ return CalcMinimumSize();
+}
+
+Size SpinField::CalcSize(sal_Int32 nChars) const
+{
+ Size aSz = Edit::CalcSize( nChars );
+
+ if ( GetStyle() & WB_DROPDOWN )
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize() );
+ if ( GetStyle() & WB_SPIN )
+ aSz.AdjustWidth(GetSettings().GetStyleSettings().GetSpinSize() );
+
+ return aSz;
+}
+
+IMPL_LINK( SpinField, ImplTimeout, Timer*, pTimer, void )
+{
+ if ( pTimer->GetTimeout() == static_cast<sal_uInt64>(MouseSettings::GetButtonStartRepeat()) )
+ {
+ pTimer->SetTimeout( GetSettings().GetMouseSettings().GetButtonRepeat() );
+ pTimer->Start();
+ }
+ else
+ {
+ if ( mbInitialUp )
+ Up();
+ else
+ Down();
+ }
+}
+
+void SpinField::Draw(OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags)
+{
+ Edit::Draw(pDev, rPos, nFlags);
+
+ WinBits nFieldStyle = GetStyle();
+ if ( (nFlags & SystemTextColorFlags::NoControls ) || !( nFieldStyle & (WB_SPIN|WB_DROPDOWN) ) )
+ return;
+
+ Point aPos = pDev->LogicToPixel( rPos );
+ Size aSize = GetSizePixel();
+ AllSettings aOldSettings = pDev->GetSettings();
+
+ pDev->Push();
+ pDev->SetMapMode();
+
+ tools::Rectangle aDD, aUp, aDown;
+ ImplCalcButtonAreas(pDev, aSize, aDD, aUp, aDown);
+ aDD.Move(aPos.X(), aPos.Y());
+ aUp.Move(aPos.X(), aPos.Y());
+ aUp.AdjustTop( 1 );
+ aDown.Move(aPos.X(), aPos.Y());
+
+ Color aButtonTextColor;
+ if (nFlags & SystemTextColorFlags::Mono)
+ aButtonTextColor = COL_BLACK;
+ else
+ aButtonTextColor = GetSettings().GetStyleSettings().GetButtonTextColor();
+
+ if (GetStyle() & WB_DROPDOWN)
+ {
+ DecorationView aView( pDev );
+ tools::Rectangle aInnerRect = aView.DrawButton( aDD, DrawButtonFlags::NoLightBorder );
+ DrawSymbolFlags nSymbolStyle = IsEnabled() ? DrawSymbolFlags::NONE : DrawSymbolFlags::Disable;
+ aView.DrawSymbol(aInnerRect, SymbolType::SPIN_DOWN, aButtonTextColor, nSymbolStyle);
+ }
+
+ if (GetStyle() & WB_SPIN)
+ {
+ ImplDrawSpinButton(*pDev, this, aUp, aDown, false, false);
+ }
+
+ pDev->Pop();
+ pDev->SetSettings(aOldSettings);
+
+}
+
+FactoryFunction SpinField::GetUITestFactory() const
+{
+ return SpinFieldUIObject::create;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/tabctrl.cxx b/vcl/source/control/tabctrl.cxx
new file mode 100644
index 0000000000..cafbae9862
--- /dev/null
+++ b/vcl/source/control/tabctrl.cxx
@@ -0,0 +1,2402 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <vcl/notebookbar/notebookbar.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/help.hxx>
+#include <vcl/event.hxx>
+#include <vcl/menu.hxx>
+#include <vcl/toolkit/button.hxx>
+#include <vcl/tabpage.hxx>
+#include <vcl/tabctrl.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/layout.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/toolkit/lstbox.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <bitmaps.hlst>
+#include <tools/json_writer.hxx>
+
+#include <svdata.hxx>
+#include <window.h>
+
+#include <deque>
+#include <unordered_map>
+#include <vector>
+
+#define TAB_OFFSET 3
+/// Space to the left and right of the tabitem
+#define TAB_ITEM_OFFSET_X 10
+/// Space to the top and bottom of the tabitem
+#define TAB_ITEM_OFFSET_Y 3
+#define TAB_EXTRASPACE_X 6
+#define TAB_BORDER_LEFT 1
+#define TAB_BORDER_TOP 1
+#define TAB_BORDER_RIGHT 2
+#define TAB_BORDER_BOTTOM 2
+
+class ImplTabItem final
+{
+ sal_uInt16 m_nId;
+
+public:
+ VclPtr<TabPage> mpTabPage;
+ OUString maText;
+ OUString maFormatText;
+ OUString maHelpText;
+ OUString maAccessibleName;
+ OUString maAccessibleDescription;
+ OUString maTabName;
+ tools::Rectangle maRect;
+ sal_uInt16 mnLine;
+ bool mbFullVisible;
+ bool m_bEnabled; ///< the tab / page is selectable
+ bool m_bVisible; ///< the tab / page can be visible
+ Image maTabImage;
+
+ ImplTabItem(sal_uInt16 nId);
+
+ sal_uInt16 id() const { return m_nId; }
+};
+
+ImplTabItem::ImplTabItem(sal_uInt16 nId)
+ : m_nId(nId)
+ , mnLine(0)
+ , mbFullVisible(false)
+ , m_bEnabled(true)
+ , m_bVisible(true)
+{
+}
+
+struct ImplTabCtrlData
+{
+ std::vector< ImplTabItem > maItemList;
+ VclPtr<ListBox> mpListBox;
+};
+
+// for the Tab positions
+#define TAB_PAGERECT 0xFFFF
+
+void TabControl::ImplInit( vcl::Window* pParent, WinBits nStyle )
+{
+ mbLayoutDirty = true;
+
+ if ( !(nStyle & WB_NOTABSTOP) )
+ nStyle |= WB_TABSTOP;
+ if ( !(nStyle & WB_NOGROUP) )
+ nStyle |= WB_GROUP;
+ if ( !(nStyle & WB_NODIALOGCONTROL) )
+ nStyle |= WB_DIALOGCONTROL;
+
+ Control::ImplInit( pParent, nStyle, nullptr );
+
+ mnLastWidth = 0;
+ mnLastHeight = 0;
+ mnActPageId = 0;
+ mnCurPageId = 0;
+ mbFormat = true;
+ mbShowTabs = true;
+ mbRestoreHelpId = false;
+ mbSmallInvalidate = false;
+ mpTabCtrlData.reset(new ImplTabCtrlData);
+ mpTabCtrlData->mpListBox = nullptr;
+
+ ImplInitSettings( true );
+
+ if( nStyle & WB_DROPDOWN )
+ {
+ mpTabCtrlData->mpListBox = VclPtr<ListBox>::Create( this, WB_DROPDOWN );
+ mpTabCtrlData->mpListBox->SetPosSizePixel( Point( 0, 0 ), Size( 200, 20 ) );
+ mpTabCtrlData->mpListBox->SetSelectHdl( LINK( this, TabControl, ImplListBoxSelectHdl ) );
+ mpTabCtrlData->mpListBox->Show();
+ }
+
+ // if the tabcontrol 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::TabPane, ControlPart::Entire) )
+ EnableChildTransparentMode();
+
+ if (pParent && pParent->IsDialog())
+ pParent->AddChildEventListener( LINK( this, TabControl, ImplWindowEventListener ) );
+}
+
+const vcl::Font& TabControl::GetCanonicalFont( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetTabFont();
+}
+
+const Color& TabControl::GetCanonicalTextColor( const StyleSettings& _rStyle ) const
+{
+ return _rStyle.GetTabTextColor();
+}
+
+void TabControl::ImplInitSettings( bool bBackground )
+{
+ Control::ImplInitSettings();
+
+ if ( !bBackground )
+ return;
+
+ vcl::Window* pParent = GetParent();
+ if ( !IsControlBackground() &&
+ (pParent->IsChildTransparentModeEnabled()
+ || IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire)
+ || IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire) ) )
+
+ {
+ // set transparent mode for NWF tabcontrols to have
+ // the background always cleared properly
+ EnableChildTransparentMode();
+ SetParentClipMode( ParentClipMode::NoClip );
+ SetPaintTransparent( true );
+ SetBackground();
+ ImplGetWindowImpl()->mbUseNativeFocus = ImplGetSVData()->maNWFData.mbNoFocusRects;
+ }
+ else
+ {
+ EnableChildTransparentMode( false );
+ SetParentClipMode();
+ SetPaintTransparent( false );
+
+ if ( IsControlBackground() )
+ SetBackground( GetControlBackground() );
+ else
+ SetBackground( pParent->GetBackground() );
+ }
+}
+
+TabControl::TabControl( vcl::Window* pParent, WinBits nStyle ) :
+ Control( WindowType::TABCONTROL )
+{
+ ImplInit( pParent, nStyle );
+ SAL_INFO( "vcl", "*** TABCONTROL no notabs? " << (( GetStyle() & WB_NOBORDER ) ? "true" : "false") );
+}
+
+TabControl::~TabControl()
+{
+ disposeOnce();
+}
+
+void TabControl::dispose()
+{
+ Window *pParent = GetParent();
+ if (pParent && pParent->IsDialog())
+ GetParent()->RemoveChildEventListener( LINK( this, TabControl, ImplWindowEventListener ) );
+
+ // delete TabCtrl data
+ if (mpTabCtrlData)
+ mpTabCtrlData->mpListBox.disposeAndClear();
+ mpTabCtrlData.reset();
+ Control::dispose();
+}
+
+ImplTabItem* TabControl::ImplGetItem( sal_uInt16 nId ) const
+{
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (item.id() == nId)
+ return &item;
+ }
+
+ return nullptr;
+}
+
+Size TabControl::ImplGetItemSize( ImplTabItem* pItem, tools::Long nMaxWidth )
+{
+ pItem->maFormatText = pItem->maText;
+ Size aSize( GetOutDev()->GetCtrlTextWidth( pItem->maFormatText ), GetTextHeight() );
+ Size aImageSize( 0, 0 );
+ if( !!pItem->maTabImage )
+ {
+ aImageSize = pItem->maTabImage.GetSizePixel();
+ if( !pItem->maFormatText.isEmpty() )
+ aImageSize.AdjustWidth(GetTextHeight()/4 );
+ }
+ aSize.AdjustWidth(aImageSize.Width() );
+ if( aImageSize.Height() > aSize.Height() )
+ aSize.setHeight( aImageSize.Height() );
+
+ aSize.AdjustWidth(TAB_ITEM_OFFSET_X*2 );
+ aSize.AdjustHeight(TAB_ITEM_OFFSET_Y*2 );
+
+ tools::Rectangle aCtrlRegion( Point( 0, 0 ), aSize );
+ tools::Rectangle aBoundingRgn, aContentRgn;
+ const TabitemValue aControlValue(tools::Rectangle(TAB_ITEM_OFFSET_X, TAB_ITEM_OFFSET_Y,
+ aSize.Width() - TAB_ITEM_OFFSET_X * 2,
+ aSize.Height() - TAB_ITEM_OFFSET_Y * 2));
+ if(GetNativeControlRegion( ControlType::TabItem, ControlPart::Entire, aCtrlRegion,
+ ControlState::ENABLED, aControlValue,
+ aBoundingRgn, aContentRgn ) )
+ {
+ return aContentRgn.GetSize();
+ }
+
+ // For languages with short names (e.g. Chinese), because the space is
+ // normally only one pixel per char
+ if ( pItem->maFormatText.getLength() < TAB_EXTRASPACE_X )
+ aSize.AdjustWidth(TAB_EXTRASPACE_X-pItem->maFormatText.getLength() );
+
+ // shorten Text if needed
+ if ( aSize.Width()+4 >= nMaxWidth )
+ {
+ OUString aAppendStr("...");
+ pItem->maFormatText += aAppendStr;
+ do
+ {
+ if (pItem->maFormatText.getLength() > aAppendStr.getLength())
+ pItem->maFormatText = pItem->maFormatText.replaceAt( pItem->maFormatText.getLength()-aAppendStr.getLength()-1, 1, u"" );
+ aSize.setWidth( GetOutDev()->GetCtrlTextWidth( pItem->maFormatText ) );
+ aSize.AdjustWidth(aImageSize.Width() );
+ aSize.AdjustWidth(TAB_ITEM_OFFSET_X*2 );
+ }
+ while ( (aSize.Width()+4 >= nMaxWidth) && (pItem->maFormatText.getLength() > aAppendStr.getLength()) );
+ if ( aSize.Width()+4 >= nMaxWidth )
+ {
+ pItem->maFormatText = ".";
+ aSize.setWidth( 1 );
+ }
+ }
+
+ if( pItem->maFormatText.isEmpty() )
+ {
+ if( aSize.Height() < aImageSize.Height()+4 ) //leave space for focus rect
+ aSize.setHeight( aImageSize.Height()+4 );
+ }
+
+ return aSize;
+}
+
+// Feel free to move this to some more general place for reuse
+// http://en.wikipedia.org/wiki/Word_wrap#Minimum_raggedness
+// Mostly based on Alexey Frunze's nifty example at
+// http://stackoverflow.com/questions/9071205/balanced-word-wrap-minimum-raggedness-in-php
+namespace MinimumRaggednessWrap
+{
+ static std::deque<size_t> GetEndOfLineIndexes(const std::vector<sal_Int32>& rWidthsOf, sal_Int32 nLineWidth)
+ {
+ ++nLineWidth;
+
+ size_t nWidthsCount = rWidthsOf.size();
+ std::vector<sal_Int32> aCosts(nWidthsCount * nWidthsCount);
+
+ // cost function c(i, j) that computes the cost of a line consisting of
+ // the words Word[i] to Word[j]
+ for (size_t i = 0; i < nWidthsCount; ++i)
+ {
+ for (size_t j = 0; j < nWidthsCount; ++j)
+ {
+ if (j >= i)
+ {
+ sal_Int32 c = nLineWidth - (j - i);
+ for (size_t k = i; k <= j; ++k)
+ c -= rWidthsOf[k];
+ c = (c >= 0) ? c * c : SAL_MAX_INT32;
+ aCosts[j * nWidthsCount + i] = c;
+ }
+ else
+ {
+ aCosts[j * nWidthsCount + i] = SAL_MAX_INT32;
+ }
+ }
+ }
+
+ std::vector<sal_Int32> aFunction(nWidthsCount);
+ std::vector<sal_Int32> aWrapPoints(nWidthsCount);
+
+ // f(j) in aFunction[], collect wrap points in aWrapPoints[]
+ for (size_t j = 0; j < nWidthsCount; ++j)
+ {
+ aFunction[j] = aCosts[j * nWidthsCount];
+ if (aFunction[j] == SAL_MAX_INT32)
+ {
+ for (size_t k = 0; k < j; ++k)
+ {
+ sal_Int32 s;
+ if (aFunction[k] == SAL_MAX_INT32 || aCosts[j * nWidthsCount + k + 1] == SAL_MAX_INT32)
+ s = SAL_MAX_INT32;
+ else
+ s = aFunction[k] + aCosts[j * nWidthsCount + k + 1];
+ if (aFunction[j] > s)
+ {
+ aFunction[j] = s;
+ aWrapPoints[j] = k + 1;
+ }
+ }
+ }
+ }
+
+ std::deque<size_t> aSolution;
+
+ // no solution
+ if (aFunction[nWidthsCount - 1] == SAL_MAX_INT32)
+ return aSolution;
+
+ // optimal solution
+ size_t j = nWidthsCount - 1;
+ while (true)
+ {
+ aSolution.push_front(j);
+ if (!aWrapPoints[j])
+ break;
+ j = aWrapPoints[j] - 1;
+ }
+
+ return aSolution;
+ }
+};
+
+static void lcl_AdjustSingleLineTabs(tools::Long nMaxWidth, ImplTabCtrlData *pTabCtrlData)
+{
+ if (!ImplGetSVData()->maNWFData.mbCenteredTabs)
+ return;
+
+ int nRightSpace = nMaxWidth; // space left on the right by the tabs
+ for (auto const& item : pTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+ nRightSpace -= item.maRect.GetWidth();
+ }
+ nRightSpace /= 2;
+
+ for (auto& item : pTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+ item.maRect.AdjustLeft(nRightSpace);
+ item.maRect.AdjustRight(nRightSpace);
+ }
+}
+
+bool TabControl::ImplPlaceTabs( tools::Long nWidth )
+{
+ if ( nWidth <= 0 )
+ return false;
+ if ( mpTabCtrlData->maItemList.empty() )
+ return false;
+
+ tools::Long nMaxWidth = nWidth;
+
+ const tools::Long nOffsetX = 2;
+ const tools::Long nOffsetY = 2;
+
+ //fdo#66435 throw Knuth/Tex minimum raggedness algorithm at the problem
+ //of ugly bare tabs on lines of their own
+
+ //collect widths
+ std::vector<sal_Int32> aWidths;
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+ aWidths.push_back(ImplGetItemSize(&item, nMaxWidth).Width());
+ }
+
+ //aBreakIndexes will contain the indexes of the last tab on each row
+ std::deque<size_t> aBreakIndexes(MinimumRaggednessWrap::GetEndOfLineIndexes(aWidths, nMaxWidth - nOffsetX - 2));
+
+ tools::Long nX = nOffsetX;
+ tools::Long nY = nOffsetY;
+
+ sal_uInt16 nLines = 0;
+ sal_uInt16 nCurLine = 0;
+
+ tools::Long nLineWidthAry[100];
+ sal_uInt16 nLinePosAry[101];
+ nLineWidthAry[0] = 0;
+ nLinePosAry[0] = 0;
+
+ size_t nIndex = 0;
+
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+
+ Size aSize = ImplGetItemSize( &item, nMaxWidth );
+
+ bool bNewLine = false;
+ if (!aBreakIndexes.empty() && nIndex > aBreakIndexes.front())
+ {
+ aBreakIndexes.pop_front();
+ bNewLine = true;
+ }
+
+ if ( bNewLine && (nWidth > 2+nOffsetX) )
+ {
+ if ( nLines == 99 )
+ break;
+
+ nX = nOffsetX;
+ nY += aSize.Height();
+ nLines++;
+ nLineWidthAry[nLines] = 0;
+ nLinePosAry[nLines] = nIndex;
+ }
+
+ tools::Rectangle aNewRect( Point( nX, nY ), aSize );
+ if ( mbSmallInvalidate && (item.maRect != aNewRect) )
+ mbSmallInvalidate = false;
+ item.maRect = aNewRect;
+ item.mnLine = nLines;
+ item.mbFullVisible = true;
+
+ nLineWidthAry[nLines] += aSize.Width();
+ nX += aSize.Width();
+
+ if (item.id() == mnCurPageId)
+ nCurLine = nLines;
+
+ ++nIndex;
+ }
+
+ if (nLines) // two or more lines
+ {
+ tools::Long nLineHeightAry[100];
+ tools::Long nIH = 0;
+ for (const auto& item : mpTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+ nIH = item.maRect.Bottom() - 1;
+ break;
+ }
+
+ for ( sal_uInt16 i = 0; i < nLines+1; i++ )
+ {
+ if ( i <= nCurLine )
+ nLineHeightAry[i] = nIH*(nLines-(nCurLine-i));
+ else
+ nLineHeightAry[i] = nIH*(i-nCurLine-1);
+ }
+
+ nLinePosAry[nLines+1] = static_cast<sal_uInt16>(mpTabCtrlData->maItemList.size());
+
+ tools::Long nDX = 0;
+ tools::Long nModDX = 0;
+ tools::Long nIDX = 0;
+
+ sal_uInt16 i = 0;
+ sal_uInt16 n = 0;
+
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+
+ if ( i == nLinePosAry[n] )
+ {
+ if ( n == nLines+1 )
+ break;
+
+ nIDX = 0;
+ if( nLinePosAry[n+1]-i > 0 )
+ {
+ nDX = ( nWidth - nOffsetX - nLineWidthAry[n] ) / ( nLinePosAry[n+1] - i );
+ nModDX = ( nWidth - nOffsetX - nLineWidthAry[n] ) % ( nLinePosAry[n+1] - i );
+ }
+ else
+ {
+ // FIXME: this is a case of tabctrl way too small
+ nDX = 0;
+ nModDX = 0;
+ }
+ n++;
+ }
+
+ item.maRect.AdjustLeft(nIDX );
+ item.maRect.AdjustRight(nIDX + nDX );
+ item.maRect.SetTop( nLineHeightAry[n-1] );
+ item.maRect.SetBottom(nLineHeightAry[n-1] + nIH - 1);
+ nIDX += nDX;
+
+ if ( nModDX )
+ {
+ nIDX++;
+ item.maRect.AdjustRight( 1 );
+ nModDX--;
+ }
+
+ i++;
+ }
+ }
+ else // only one line
+ lcl_AdjustSingleLineTabs(nMaxWidth, mpTabCtrlData.get());
+
+ return true;
+}
+
+tools::Rectangle TabControl::ImplGetTabRect( sal_uInt16 nItemPos, tools::Long nWidth, tools::Long nHeight )
+{
+ Size aWinSize = Control::GetOutputSizePixel();
+ if ( nWidth < 0 )
+ nWidth = aWinSize.Width();
+ if ( nHeight < 0 )
+ nHeight = aWinSize.Height();
+
+ if ( mpTabCtrlData->maItemList.empty() )
+ {
+ tools::Long nW = nWidth-TAB_OFFSET*2;
+ tools::Long nH = nHeight-TAB_OFFSET*2;
+ return (nW > 0 && nH > 0)
+ ? tools::Rectangle(Point(TAB_OFFSET, TAB_OFFSET), Size(nW, nH))
+ : tools::Rectangle();
+ }
+
+ if ( nItemPos == TAB_PAGERECT )
+ {
+ sal_uInt16 nLastPos;
+ if ( mnCurPageId )
+ nLastPos = GetPagePos( mnCurPageId );
+ else
+ nLastPos = 0;
+
+ tools::Rectangle aRect = ImplGetTabRect( nLastPos, nWidth, nHeight );
+ if (aRect.IsEmpty())
+ return aRect;
+
+ // with show-tabs of true (the usual) the page rect is from under the
+ // visible tab to the bottom of the TabControl, otherwise it extends
+ // from the top of the TabControl
+ tools::Long nTabBottom = mbShowTabs ? aRect.Bottom() : 0;
+
+ tools::Long nW = nWidth-TAB_OFFSET*2;
+ tools::Long nH = nHeight - nTabBottom - TAB_OFFSET*2;
+ return (nW > 0 && nH > 0)
+ ? tools::Rectangle( Point( TAB_OFFSET, nTabBottom + TAB_OFFSET ), Size( nW, nH ) )
+ : tools::Rectangle();
+ }
+
+ ImplTabItem* const pItem = (nItemPos < mpTabCtrlData->maItemList.size())
+ ? &mpTabCtrlData->maItemList[nItemPos] : nullptr;
+ return ImplGetTabRect(pItem, nWidth, nHeight);
+}
+
+tools::Rectangle TabControl::ImplGetTabRect(const ImplTabItem* pItem, tools::Long nWidth, tools::Long nHeight)
+{
+ if ((nWidth <= 1) || (nHeight <= 0) || !pItem || !pItem->m_bVisible)
+ return tools::Rectangle();
+
+ nWidth -= 1;
+
+ if ( mbFormat || (mnLastWidth != nWidth) || (mnLastHeight != nHeight) )
+ {
+ vcl::Font aFont( GetFont() );
+ aFont.SetTransparent( true );
+ SetFont( aFont );
+
+ bool bRet = ImplPlaceTabs( nWidth );
+ if ( !bRet )
+ return tools::Rectangle();
+
+ mnLastWidth = nWidth;
+ mnLastHeight = nHeight;
+ mbFormat = false;
+ }
+
+ return pItem->maRect;
+}
+
+void TabControl::ImplChangeTabPage( sal_uInt16 nId, sal_uInt16 nOldId )
+{
+ ImplTabItem* pOldItem = ImplGetItem( nOldId );
+ ImplTabItem* pItem = ImplGetItem( nId );
+ TabPage* pOldPage = pOldItem ? pOldItem->mpTabPage.get() : nullptr;
+ TabPage* pPage = pItem ? pItem->mpTabPage.get() : nullptr;
+ vcl::Window* pCtrlParent = GetParent();
+
+ if ( IsReallyVisible() && IsUpdateMode() )
+ {
+ sal_uInt16 nPos = GetPagePos( nId );
+ tools::Rectangle aRect = ImplGetTabRect( nPos );
+
+ if ( !pOldItem || !pItem || (pOldItem->mnLine != pItem->mnLine) )
+ {
+ aRect.SetLeft( 0 );
+ aRect.SetTop( 0 );
+ aRect.SetRight( Control::GetOutputSizePixel().Width() );
+ }
+ else
+ {
+ aRect.AdjustLeft( -3 );
+ aRect.AdjustTop( -2 );
+ aRect.AdjustRight(3 );
+ Invalidate( aRect );
+ nPos = GetPagePos( nOldId );
+ aRect = ImplGetTabRect( nPos );
+ aRect.AdjustLeft( -3 );
+ aRect.AdjustTop( -2 );
+ aRect.AdjustRight(3 );
+ }
+ Invalidate( aRect );
+ }
+
+ if ( pOldPage == pPage )
+ return;
+
+ tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
+
+ if ( pOldPage )
+ {
+ if ( mbRestoreHelpId )
+ pCtrlParent->SetHelpId({});
+ }
+
+ if ( pPage )
+ {
+ if ( GetStyle() & WB_NOBORDER )
+ {
+ tools::Rectangle aRectNoTab(Point(0, 0), GetSizePixel());
+ pPage->SetPosSizePixel( aRectNoTab.TopLeft(), aRectNoTab.GetSize() );
+ }
+ else
+ pPage->SetPosSizePixel( aRect.TopLeft(), aRect.GetSize() );
+
+ // activate page here so the controls can be switched
+ // also set the help id of the parent window to that of the tab page
+ if ( GetHelpId().isEmpty() )
+ {
+ mbRestoreHelpId = true;
+ pCtrlParent->SetHelpId( pPage->GetHelpId() );
+ }
+
+ pPage->Show();
+
+ if ( pOldPage && pOldPage->HasChildPathFocus() )
+ {
+ vcl::Window* pFirstChild = pPage->ImplGetDlgWindow( 0, GetDlgWindowType::First );
+ if ( pFirstChild )
+ pFirstChild->ImplControlFocus( GetFocusFlags::Init );
+ else
+ GrabFocus();
+ }
+ }
+
+ if ( pOldPage )
+ pOldPage->Hide();
+
+ // Invalidate the same region that will be send to NWF
+ // to always allow for bitmap caching
+ // see Window::DrawNativeControl()
+ if( IsNativeControlSupported( ControlType::TabPane, ControlPart::Entire ) )
+ {
+ aRect.AdjustLeft( -(TAB_OFFSET) );
+ aRect.AdjustTop( -(TAB_OFFSET) );
+ aRect.AdjustRight(TAB_OFFSET );
+ aRect.AdjustBottom(TAB_OFFSET );
+ }
+
+ Invalidate( aRect );
+}
+
+bool TabControl::ImplPosCurTabPage()
+{
+ // resize/position current TabPage
+ ImplTabItem* pItem = ImplGetItem( GetCurPageId() );
+ if ( pItem && pItem->mpTabPage )
+ {
+ if ( GetStyle() & WB_NOBORDER )
+ {
+ tools::Rectangle aRectNoTab(Point(0, 0), GetSizePixel());
+ pItem->mpTabPage->SetPosSizePixel( aRectNoTab.TopLeft(), aRectNoTab.GetSize() );
+ return true;
+ }
+ tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
+ pItem->mpTabPage->SetPosSizePixel( aRect.TopLeft(), aRect.GetSize() );
+ return true;
+ }
+
+ return false;
+}
+
+void TabControl::ImplActivateTabPage( bool bNext )
+{
+ sal_uInt16 nCurPos = GetPagePos( GetCurPageId() );
+
+ if ( bNext )
+ nCurPos = (nCurPos + 1) % GetPageCount();
+ else
+ {
+ if ( !nCurPos )
+ nCurPos = GetPageCount()-1;
+ else
+ nCurPos--;
+ }
+
+ SelectTabPage( GetPageId( nCurPos ) );
+}
+
+void TabControl::ImplShowFocus()
+{
+ if ( !GetPageCount() || mpTabCtrlData->mpListBox )
+ return;
+
+ sal_uInt16 nCurPos = GetPagePos( mnCurPageId );
+ tools::Rectangle aRect = ImplGetTabRect( nCurPos );
+ const ImplTabItem& rItem = mpTabCtrlData->maItemList[ nCurPos ];
+ Size aTabSize = aRect.GetSize();
+ Size aImageSize( 0, 0 );
+ tools::Long nTextHeight = GetTextHeight();
+ tools::Long nTextWidth = GetOutDev()->GetCtrlTextWidth( rItem.maFormatText );
+ sal_uInt16 nOff;
+
+ if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::Mono) )
+ nOff = 1;
+ else
+ nOff = 0;
+
+ if( !! rItem.maTabImage )
+ {
+ aImageSize = rItem.maTabImage.GetSizePixel();
+ if( !rItem.maFormatText.isEmpty() )
+ aImageSize.AdjustWidth(GetTextHeight()/4 );
+ }
+
+ if( !rItem.maFormatText.isEmpty() )
+ {
+ // show focus around text
+ aRect.SetLeft( aRect.Left()+aImageSize.Width()+((aTabSize.Width()-nTextWidth-aImageSize.Width())/2)-nOff-1-1 );
+ aRect.SetTop( aRect.Top()+((aTabSize.Height()-nTextHeight)/2)-1-1 );
+ aRect.SetRight( aRect.Left()+nTextWidth+2 );
+ aRect.SetBottom( aRect.Top()+nTextHeight+2 );
+ }
+ else
+ {
+ // show focus around image
+ tools::Long nXPos = aRect.Left()+((aTabSize.Width()-nTextWidth-aImageSize.Width())/2)-nOff-1;
+ tools::Long nYPos = aRect.Top();
+ if( aImageSize.Height() < aRect.GetHeight() )
+ nYPos += (aRect.GetHeight() - aImageSize.Height())/2;
+
+ aRect.SetLeft( nXPos - 2 );
+ aRect.SetTop( nYPos - 2 );
+ aRect.SetRight( aRect.Left() + aImageSize.Width() + 4 );
+ aRect.SetBottom( aRect.Top() + aImageSize.Height() + 4 );
+ }
+ ShowFocus( aRect );
+}
+
+void TabControl::ImplDrawItem(vcl::RenderContext& rRenderContext, ImplTabItem const * pItem, const tools::Rectangle& rCurRect,
+ bool bFirstInGroup, bool bLastInGroup )
+{
+ if (!pItem->m_bVisible || pItem->maRect.IsEmpty())
+ return;
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ tools::Rectangle aRect = pItem->maRect;
+ tools::Long nLeftBottom = aRect.Bottom();
+ tools::Long nRightBottom = aRect.Bottom();
+ bool bLeftBorder = true;
+ bool bRightBorder = true;
+ sal_uInt16 nOff;
+ bool bNativeOK = false;
+
+ sal_uInt16 nOff2 = 0;
+ sal_uInt16 nOff3 = 0;
+
+ if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
+ nOff = 1;
+ else
+ nOff = 0;
+
+ // if this is the active Page, we have to draw a little more
+ if (pItem->id() == mnCurPageId)
+ {
+ nOff2 = 2;
+ if (!ImplGetSVData()->maNWFData.mbNoActiveTabTextRaise)
+ nOff3 = 1;
+ }
+ else
+ {
+ Point aLeftTestPos = aRect.BottomLeft();
+ Point aRightTestPos = aRect.BottomRight();
+ if (aLeftTestPos.Y() == rCurRect.Bottom())
+ {
+ aLeftTestPos.AdjustX( -2 );
+ if (rCurRect.Contains(aLeftTestPos))
+ bLeftBorder = false;
+ aRightTestPos.AdjustX(2 );
+ if (rCurRect.Contains(aRightTestPos))
+ bRightBorder = false;
+ }
+ else
+ {
+ if (rCurRect.Contains(aLeftTestPos))
+ nLeftBottom -= 2;
+ if (rCurRect.Contains(aRightTestPos))
+ nRightBottom -= 2;
+ }
+ }
+
+ ControlState nState = ControlState::NONE;
+
+ if (pItem->id() == mnCurPageId)
+ {
+ nState |= ControlState::SELECTED;
+ // only the selected item can be focused
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+ }
+ if (IsEnabled())
+ nState |= ControlState::ENABLED;
+ if (IsMouseOver() && pItem->maRect.Contains(GetPointerPosPixel()))
+ {
+ nState |= ControlState::ROLLOVER;
+ for (auto const& item : mpTabCtrlData->maItemList)
+ if ((&item != pItem) && item.m_bVisible && item.maRect.Contains(GetPointerPosPixel()))
+ {
+ nState &= ~ControlState::ROLLOVER; // avoid multiple highlighted tabs
+ break;
+ }
+ assert(nState & ControlState::ROLLOVER);
+ }
+
+ bNativeOK = rRenderContext.IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire);
+ if ( bNativeOK )
+ {
+ TabitemValue tiValue(tools::Rectangle(pItem->maRect.Left() + TAB_ITEM_OFFSET_X,
+ pItem->maRect.Top() + TAB_ITEM_OFFSET_Y,
+ pItem->maRect.Right() - TAB_ITEM_OFFSET_X,
+ pItem->maRect.Bottom() - TAB_ITEM_OFFSET_Y));
+ if (pItem->maRect.Left() < 5)
+ tiValue.mnAlignment |= TabitemFlags::LeftAligned;
+ if (pItem->maRect.Right() > mnLastWidth - 5)
+ tiValue.mnAlignment |= TabitemFlags::RightAligned;
+ if (bFirstInGroup)
+ tiValue.mnAlignment |= TabitemFlags::FirstInGroup;
+ if (bLastInGroup)
+ tiValue.mnAlignment |= TabitemFlags::LastInGroup;
+
+ tools::Rectangle aCtrlRegion( pItem->maRect );
+ aCtrlRegion.AdjustBottom(TabPaneValue::m_nOverlap);
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::TabItem, ControlPart::Entire,
+ aCtrlRegion, nState, tiValue, OUString() );
+ }
+
+ if (!bNativeOK)
+ {
+ if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ rRenderContext.DrawPixel(Point(aRect.Left() + 1 - nOff2, aRect.Top() + 1 - nOff2)); // diagonally indented top-left pixel
+ if (bLeftBorder)
+ {
+ rRenderContext.DrawLine(Point(aRect.Left() - nOff2, aRect.Top() + 2 - nOff2),
+ Point(aRect.Left() - nOff2, nLeftBottom - 1));
+ }
+ rRenderContext.DrawLine(Point(aRect.Left() + 2 - nOff2, aRect.Top() - nOff2), // top line starting 2px from left border
+ Point(aRect.Right() + nOff2 - 3, aRect.Top() - nOff2)); // ending 3px from right border
+
+ if (bRightBorder)
+ {
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 2, aRect.Top() + 1 - nOff2),
+ Point(aRect.Right() + nOff2 - 2, nRightBottom - 1));
+
+ rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
+ rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 1, aRect.Top() + 3 - nOff2),
+ Point(aRect.Right() + nOff2 - 1, nRightBottom - 1));
+ }
+ }
+ else
+ {
+ rRenderContext.SetLineColor(COL_BLACK);
+ rRenderContext.DrawPixel(Point(aRect.Left() + 1 - nOff2, aRect.Top() + 1 - nOff2));
+ rRenderContext.DrawPixel(Point(aRect.Right() + nOff2 - 2, aRect.Top() + 1 - nOff2));
+ if (bLeftBorder)
+ {
+ rRenderContext.DrawLine(Point(aRect.Left() - nOff2, aRect.Top() + 2 - nOff2),
+ Point(aRect.Left() - nOff2, nLeftBottom - 1));
+ }
+ rRenderContext.DrawLine(Point(aRect.Left() + 2 - nOff2, aRect.Top() - nOff2),
+ Point(aRect.Right() - 3, aRect.Top() - nOff2));
+ if (bRightBorder)
+ {
+ rRenderContext.DrawLine(Point(aRect.Right() + nOff2 - 1, aRect.Top() + 2 - nOff2),
+ Point(aRect.Right() + nOff2 - 1, nRightBottom - 1));
+ }
+ }
+ }
+
+ // set font accordingly, current item is painted bold
+ // we set the font attributes always before drawing to be re-entrant (DrawNativeControl may trigger additional paints)
+ vcl::Font aFont(rRenderContext.GetFont());
+ aFont.SetTransparent(true);
+ rRenderContext.SetFont(aFont);
+
+ Size aTabSize = aRect.GetSize();
+ Size aImageSize(0, 0);
+ tools::Long nTextHeight = rRenderContext.GetTextHeight();
+ tools::Long nTextWidth = rRenderContext.GetCtrlTextWidth(pItem->maFormatText);
+ if (!!pItem->maTabImage)
+ {
+ aImageSize = pItem->maTabImage.GetSizePixel();
+ if (!pItem->maFormatText.isEmpty())
+ aImageSize.AdjustWidth(GetTextHeight() / 4 );
+ }
+ tools::Long nXPos = aRect.Left() + ((aTabSize.Width() - nTextWidth - aImageSize.Width()) / 2) - nOff - nOff3;
+ tools::Long nYPos = aRect.Top() + ((aTabSize.Height() - nTextHeight) / 2) - nOff3;
+ if (!pItem->maFormatText.isEmpty())
+ {
+ DrawTextFlags nStyle = DrawTextFlags::Mnemonic;
+ if (!pItem->m_bEnabled)
+ nStyle |= DrawTextFlags::Disable;
+
+ Color aColor(rStyleSettings.GetTabTextColor());
+ if (nState & ControlState::SELECTED)
+ aColor = rStyleSettings.GetTabHighlightTextColor();
+ else if (nState & ControlState::ROLLOVER)
+ aColor = rStyleSettings.GetTabRolloverTextColor();
+
+ Color aOldColor(rRenderContext.GetTextColor());
+ rRenderContext.SetTextColor(aColor);
+
+ const tools::Rectangle aOutRect(nXPos + aImageSize.Width(), nYPos,
+ nXPos + aImageSize.Width() + nTextWidth, nYPos + nTextHeight);
+ DrawControlText(rRenderContext, aOutRect, pItem->maFormatText, nStyle,
+ nullptr, nullptr);
+
+ rRenderContext.SetTextColor(aOldColor);
+ }
+
+ if (!!pItem->maTabImage)
+ {
+ Point aImgTL( nXPos, aRect.Top() );
+ if (aImageSize.Height() < aRect.GetHeight())
+ aImgTL.AdjustY((aRect.GetHeight() - aImageSize.Height()) / 2 );
+ rRenderContext.DrawImage(aImgTL, pItem->maTabImage, pItem->m_bEnabled ? DrawImageFlags::NONE : DrawImageFlags::Disable );
+ }
+}
+
+bool TabControl::ImplHandleKeyEvent( const KeyEvent& rKeyEvent )
+{
+ bool bRet = false;
+
+ if ( GetPageCount() > 1 )
+ {
+ vcl::KeyCode aKeyCode = rKeyEvent.GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+
+ if ( aKeyCode.IsMod1() )
+ {
+ if ( aKeyCode.IsShift() || (nKeyCode == KEY_PAGEUP) )
+ {
+ if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEUP) )
+ {
+ ImplActivateTabPage( false );
+ bRet = true;
+ }
+ }
+ else
+ {
+ if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEDOWN) )
+ {
+ ImplActivateTabPage( true );
+ bRet = true;
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+IMPL_LINK_NOARG(TabControl, ImplListBoxSelectHdl, ListBox&, void)
+{
+ SelectTabPage( GetPageId( mpTabCtrlData->mpListBox->GetSelectedEntryPos() ) );
+}
+
+IMPL_LINK( TabControl, ImplWindowEventListener, VclWindowEvent&, rEvent, void )
+{
+ if ( rEvent.GetId() == VclEventId::WindowKeyInput )
+ {
+ // Do not handle events from TabControl or its children, which is done in Notify(), where the events can be consumed.
+ if ( !IsWindowOrChild( rEvent.GetWindow() ) )
+ {
+ KeyEvent* pKeyEvent = static_cast< KeyEvent* >(rEvent.GetData());
+ ImplHandleKeyEvent( *pKeyEvent );
+ }
+ }
+}
+
+void TabControl::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ if (mpTabCtrlData->mpListBox || !rMEvt.IsLeft())
+ return;
+
+ ImplTabItem *pItem = ImplGetItem(rMEvt.GetPosPixel());
+ if (pItem && pItem->m_bEnabled)
+ SelectTabPage(pItem->id());
+}
+
+void TabControl::KeyInput( const KeyEvent& rKEvt )
+{
+ if( mpTabCtrlData->mpListBox )
+ mpTabCtrlData->mpListBox->KeyInput( rKEvt );
+ else if ( GetPageCount() > 1 )
+ {
+ vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+
+ if ( (nKeyCode == KEY_LEFT) || (nKeyCode == KEY_RIGHT) )
+ {
+ bool bNext = (nKeyCode == KEY_RIGHT);
+ ImplActivateTabPage( bNext );
+ }
+ }
+
+ Control::KeyInput( rKEvt );
+}
+
+static bool lcl_canPaint(const vcl::RenderContext& rRenderContext, const tools::Rectangle& rDrawRect,
+ const tools::Rectangle& rItemRect)
+{
+ vcl::Region aClipRgn(rRenderContext.GetActiveClipRegion());
+ aClipRgn.Intersect(rItemRect);
+ if (!rDrawRect.IsEmpty())
+ aClipRgn.Intersect(rDrawRect);
+ return !aClipRgn.IsEmpty();
+}
+
+void TabControl::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ if (GetStyle() & WB_NOBORDER)
+ return;
+
+ Control::Paint(rRenderContext, rRect);
+
+ HideFocus();
+
+ // reformat if needed
+ tools::Rectangle aRect = ImplGetTabRect(TAB_PAGERECT);
+
+ // find current item
+ ImplTabItem* pCurItem = nullptr;
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (item.id() == mnCurPageId)
+ {
+ pCurItem = &item;
+ break;
+ }
+ }
+
+ // Draw the TabPage border
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ tools::Rectangle aCurRect;
+ aRect.AdjustLeft( -(TAB_OFFSET) );
+ aRect.AdjustTop( -(TAB_OFFSET) );
+ aRect.AdjustRight(TAB_OFFSET );
+ aRect.AdjustBottom(TAB_OFFSET );
+
+ // if we have an invisible tabpage or no tabpage at all the tabpage rect should be
+ // increased to avoid round corners that might be drawn by a theme
+ // in this case we're only interested in the top border of the tabpage because the tabitems are used
+ // standalone (eg impress)
+ bool bNoTabPage = false;
+ TabPage* pCurPage = pCurItem ? pCurItem->mpTabPage.get() : nullptr;
+ if (!pCurPage || !pCurPage->IsVisible())
+ {
+ bNoTabPage = true;
+ aRect.AdjustLeft( -10 );
+ aRect.AdjustRight(10 );
+ }
+
+ if (rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::Entire))
+ {
+ const bool bPaneWithHeader = mbShowTabs && rRenderContext.IsNativeControlSupported(ControlType::TabPane, ControlPart::TabPaneWithHeader);
+ tools::Rectangle aHeaderRect(aRect.Left(), 0, aRect.Right(), aRect.Top());
+
+ if (mpTabCtrlData->maItemList.size())
+ {
+ tools::Long nLeft = LONG_MAX;
+ tools::Long nRight = 0;
+ for (const auto &item : mpTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+ nRight = std::max(nRight, item.maRect.Right());
+ nLeft = std::min(nLeft, item.maRect.Left());
+ }
+ aHeaderRect.SetLeft(nLeft);
+ aHeaderRect.SetRight(nRight);
+ }
+
+ if (bPaneWithHeader)
+ aRect.SetTop(0);
+
+ const TabPaneValue aTabPaneValue(aHeaderRect, pCurItem ? pCurItem->maRect : tools::Rectangle());
+
+ ControlState nState = ControlState::ENABLED;
+ if (!IsEnabled())
+ nState &= ~ControlState::ENABLED;
+ if (HasFocus())
+ nState |= ControlState::FOCUSED;
+
+ if (lcl_canPaint(rRenderContext, rRect, aRect))
+ rRenderContext.DrawNativeControl(ControlType::TabPane, ControlPart::Entire,
+ aRect, nState, aTabPaneValue, OUString());
+
+ if (!bPaneWithHeader && rRenderContext.IsNativeControlSupported(ControlType::TabHeader, ControlPart::Entire)
+ && lcl_canPaint(rRenderContext, rRect, aHeaderRect))
+ rRenderContext.DrawNativeControl(ControlType::TabHeader, ControlPart::Entire,
+ aHeaderRect, nState, aTabPaneValue, OUString());
+ }
+ else
+ {
+ tools::Long nTopOff = 1;
+ if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
+ rRenderContext.SetLineColor(rStyleSettings.GetLightColor());
+ else
+ rRenderContext.SetLineColor(COL_BLACK);
+ if (mbShowTabs && pCurItem && !pCurItem->maRect.IsEmpty())
+ {
+ aCurRect = pCurItem->maRect;
+ rRenderContext.DrawLine(aRect.TopLeft(), Point(aCurRect.Left() - 2, aRect.Top()));
+ if (aCurRect.Right() + 1 < aRect.Right())
+ {
+ rRenderContext.DrawLine(Point(aCurRect.Right(), aRect.Top()), aRect.TopRight());
+ }
+ else
+ {
+ nTopOff = 0;
+ }
+ }
+ else
+ rRenderContext.DrawLine(aRect.TopLeft(), aRect.TopRight());
+
+ rRenderContext.DrawLine(aRect.TopLeft(), aRect.BottomLeft());
+
+ if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono))
+ {
+ // if we have not tab page the bottom line of the tab page
+ // directly touches the tab items, so choose a color that fits seamlessly
+ if (bNoTabPage)
+ rRenderContext.SetLineColor(rStyleSettings.GetDialogColor());
+ else
+ rRenderContext.SetLineColor(rStyleSettings.GetShadowColor());
+ rRenderContext.DrawLine(Point(1, aRect.Bottom() - 1), Point(aRect.Right() - 1, aRect.Bottom() - 1));
+ rRenderContext.DrawLine(Point(aRect.Right() - 1, aRect.Top() + nTopOff), Point(aRect.Right() - 1, aRect.Bottom() - 1));
+ if (bNoTabPage)
+ rRenderContext.SetLineColor(rStyleSettings.GetDialogColor());
+ else
+ rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor());
+ rRenderContext.DrawLine(Point(0, aRect.Bottom()), Point(aRect.Right(), aRect.Bottom()));
+ rRenderContext.DrawLine(Point(aRect.Right(), aRect.Top() + nTopOff), Point(aRect.Right(), aRect.Bottom()));
+ }
+ else
+ {
+ rRenderContext.DrawLine(aRect.TopRight(), aRect.BottomRight());
+ rRenderContext.DrawLine(aRect.BottomLeft(), aRect.BottomRight());
+ }
+ }
+
+ if (mbShowTabs && !mpTabCtrlData->maItemList.empty() && mpTabCtrlData->mpListBox == nullptr)
+ {
+ // Some native toolkits (GTK+) draw tabs right-to-left, with an
+ // overlap between adjacent tabs
+ bool bDrawTabsRTL = rRenderContext.IsNativeControlSupported(ControlType::TabItem, ControlPart::TabsDrawRtl);
+ ImplTabItem* pFirstTab = nullptr;
+ ImplTabItem* pLastTab = nullptr;
+ size_t idx;
+
+ // Even though there is a tab overlap with GTK+, the first tab is not
+ // overlapped on the left side. Other toolkits ignore this option.
+ if (bDrawTabsRTL)
+ {
+ pFirstTab = mpTabCtrlData->maItemList.data();
+ pLastTab = pFirstTab + mpTabCtrlData->maItemList.size() - 1;
+ idx = mpTabCtrlData->maItemList.size() - 1;
+ }
+ else
+ {
+ pLastTab = mpTabCtrlData->maItemList.data();
+ pFirstTab = pLastTab + mpTabCtrlData->maItemList.size() - 1;
+ idx = 0;
+ }
+
+ while (idx < mpTabCtrlData->maItemList.size())
+ {
+ ImplTabItem* pItem = &mpTabCtrlData->maItemList[idx];
+
+ if (pItem != pCurItem && pItem->m_bVisible && lcl_canPaint(rRenderContext, rRect, pItem->maRect))
+ ImplDrawItem(rRenderContext, pItem, aCurRect, pItem == pFirstTab, pItem == pLastTab);
+
+ if (bDrawTabsRTL)
+ idx--;
+ else
+ idx++;
+ }
+
+ if (pCurItem && lcl_canPaint(rRenderContext, rRect, pCurItem->maRect))
+ ImplDrawItem(rRenderContext, pCurItem, aCurRect, pCurItem == pFirstTab, pCurItem == pLastTab);
+ }
+
+ if (HasFocus())
+ ImplShowFocus();
+
+ mbSmallInvalidate = true;
+}
+
+void TabControl::setAllocation(const Size &rAllocation)
+{
+ if ( !IsReallyShown() )
+ return;
+
+ if( mpTabCtrlData->mpListBox )
+ {
+ // get the listbox' preferred size
+ Size aTabCtrlSize( GetSizePixel() );
+ tools::Long nPrefWidth = mpTabCtrlData->mpListBox->get_preferred_size().Width();
+ if( nPrefWidth > aTabCtrlSize.Width() )
+ nPrefWidth = aTabCtrlSize.Width();
+ Size aNewSize( nPrefWidth, LogicToPixel( Size( 12, 12 ), MapMode( MapUnit::MapAppFont ) ).Height() );
+ Point aNewPos( (aTabCtrlSize.Width() - nPrefWidth) / 2, 0 );
+ mpTabCtrlData->mpListBox->SetPosSizePixel( aNewPos, aNewSize );
+ }
+
+ mbFormat = true;
+
+ // resize/position active TabPage
+ bool bTabPage = ImplPosCurTabPage();
+
+ // check what needs to be invalidated
+ Size aNewSize = rAllocation;
+ tools::Long nNewWidth = aNewSize.Width();
+ for (auto const& item : mpTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+ if (!item.mbFullVisible || (item.maRect.Right()-2 >= nNewWidth))
+ {
+ mbSmallInvalidate = false;
+ break;
+ }
+ }
+
+ if ( mbSmallInvalidate )
+ {
+ tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT );
+ aRect.AdjustLeft( -(TAB_OFFSET+TAB_BORDER_LEFT) );
+ aRect.AdjustTop( -(TAB_OFFSET+TAB_BORDER_TOP) );
+ aRect.AdjustRight(TAB_OFFSET+TAB_BORDER_RIGHT );
+ aRect.AdjustBottom(TAB_OFFSET+TAB_BORDER_BOTTOM );
+ if ( bTabPage )
+ Invalidate( aRect, InvalidateFlags::NoChildren );
+ else
+ Invalidate( aRect );
+
+ }
+ else
+ {
+ if ( bTabPage )
+ Invalidate( InvalidateFlags::NoChildren );
+ else
+ Invalidate();
+ }
+
+ mbLayoutDirty = false;
+}
+
+void TabControl::SetPosSizePixel(const Point& rNewPos, const Size& rNewSize)
+{
+ Window::SetPosSizePixel(rNewPos, rNewSize);
+ //if size changed, TabControl::Resize got called already
+ if (mbLayoutDirty)
+ setAllocation(rNewSize);
+}
+
+void TabControl::SetSizePixel(const Size& rNewSize)
+{
+ Window::SetSizePixel(rNewSize);
+ //if size changed, TabControl::Resize got called already
+ if (mbLayoutDirty)
+ setAllocation(rNewSize);
+}
+
+void TabControl::SetPosPixel(const Point& rPos)
+{
+ Window::SetPosPixel(rPos);
+ if (mbLayoutDirty)
+ setAllocation(GetOutputSizePixel());
+}
+
+void TabControl::Resize()
+{
+ setAllocation(Control::GetOutputSizePixel());
+}
+
+void TabControl::GetFocus()
+{
+ if( ! mpTabCtrlData->mpListBox )
+ {
+ if (mbShowTabs)
+ {
+ ImplShowFocus();
+ SetInputContext( InputContext( GetFont() ) );
+ }
+ else
+ {
+ // no tabs, focus first thing in current page
+ ImplTabItem* pItem = ImplGetItem(GetCurPageId());
+ if (pItem && pItem->mpTabPage)
+ {
+ vcl::Window* pFirstChild = pItem->mpTabPage->ImplGetDlgWindow(0, GetDlgWindowType::First);
+ if ( pFirstChild )
+ pFirstChild->ImplControlFocus(GetFocusFlags::Init);
+ }
+ }
+ }
+ else
+ {
+ if( mpTabCtrlData->mpListBox->IsReallyVisible() )
+ mpTabCtrlData->mpListBox->GrabFocus();
+ }
+
+ Control::GetFocus();
+}
+
+void TabControl::LoseFocus()
+{
+ if( mpTabCtrlData && ! mpTabCtrlData->mpListBox )
+ HideFocus();
+ Control::LoseFocus();
+}
+
+void TabControl::RequestHelp( const HelpEvent& rHEvt )
+{
+ sal_uInt16 nItemId = rHEvt.KeyboardActivated() ? mnCurPageId : GetPageId( ScreenToOutputPixel( rHEvt.GetMousePosPixel() ) );
+
+ if ( nItemId )
+ {
+ if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
+ {
+ OUString aStr = GetHelpText( nItemId );
+ if ( !aStr.isEmpty() )
+ {
+ tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
+ Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
+ aItemRect.SetLeft( aPt.X() );
+ aItemRect.SetTop( aPt.Y() );
+ aPt = OutputToScreenPixel( aItemRect.BottomRight() );
+ aItemRect.SetRight( aPt.X() );
+ aItemRect.SetBottom( aPt.Y() );
+ Help::ShowBalloon( this, aItemRect.Center(), aItemRect, aStr );
+ return;
+ }
+ }
+
+ // for Quick or Ballon Help, we show the text, if it is cut
+ if ( rHEvt.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON) )
+ {
+ ImplTabItem* pItem = ImplGetItem( nItemId );
+ const OUString& rStr = pItem->maText;
+ if ( rStr != pItem->maFormatText )
+ {
+ tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( 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 ( !rStr.isEmpty() )
+ {
+ if ( rHEvt.GetMode() & HelpEventMode::BALLOON )
+ Help::ShowBalloon( this, aItemRect.Center(), aItemRect, rStr );
+ else
+ Help::ShowQuickHelp( this, aItemRect, rStr );
+ return;
+ }
+ }
+ }
+
+ if ( rHEvt.GetMode() & HelpEventMode::QUICK )
+ {
+ ImplTabItem* pItem = ImplGetItem( nItemId );
+ const OUString& rHelpText = pItem->maHelpText;
+ if (!rHelpText.isEmpty())
+ {
+ tools::Rectangle aItemRect = ImplGetTabRect( GetPagePos( nItemId ) );
+ Point aPt = OutputToScreenPixel( aItemRect.TopLeft() );
+ aItemRect.SetLeft( aPt.X() );
+ aItemRect.SetTop( aPt.Y() );
+ aPt = OutputToScreenPixel( aItemRect.BottomRight() );
+ aItemRect.SetRight( aPt.X() );
+ aItemRect.SetBottom( aPt.Y() );
+ Help::ShowQuickHelp( this, aItemRect, rHelpText );
+ return;
+ }
+ }
+ }
+
+ Control::RequestHelp( rHEvt );
+}
+
+void TabControl::Command( const CommandEvent& rCEvt )
+{
+ if( (mpTabCtrlData->mpListBox == nullptr) && (rCEvt.GetCommand() == CommandEventId::ContextMenu) && (GetPageCount() > 1) )
+ {
+ Point aMenuPos;
+ bool bMenu;
+ if ( rCEvt.IsMouseEvent() )
+ {
+ aMenuPos = rCEvt.GetMousePosPixel();
+ bMenu = GetPageId( aMenuPos ) != 0;
+ }
+ else
+ {
+ aMenuPos = ImplGetTabRect( GetPagePos( mnCurPageId ) ).Center();
+ bMenu = true;
+ }
+
+ if ( bMenu )
+ {
+ ScopedVclPtrInstance<PopupMenu> aMenu;
+ for (auto const& item : mpTabCtrlData->maItemList)
+ {
+ aMenu->InsertItem(item.id(), item.maText, MenuItemBits::CHECKABLE | MenuItemBits::RADIOCHECK);
+ if (item.id() == mnCurPageId)
+ aMenu->CheckItem(item.id());
+ aMenu->SetHelpId(item.id(), {});
+ }
+
+ sal_uInt16 nId = aMenu->Execute( this, aMenuPos );
+ if ( nId && (nId != mnCurPageId) )
+ SelectTabPage( nId );
+ return;
+ }
+ }
+
+ Control::Command( rCEvt );
+}
+
+void TabControl::StateChanged( StateChangedType nType )
+{
+ Control::StateChanged( nType );
+
+ if ( nType == StateChangedType::InitShow )
+ {
+ ImplPosCurTabPage();
+ if( mpTabCtrlData->mpListBox )
+ Resize();
+ }
+ else if ( nType == StateChangedType::UpdateMode )
+ {
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+ else if ( (nType == StateChangedType::Zoom) ||
+ (nType == StateChangedType::ControlFont) )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlForeground )
+ {
+ ImplInitSettings( false );
+ Invalidate();
+ }
+ else if ( nType == StateChangedType::ControlBackground )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+void TabControl::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ Control::DataChanged( rDCEvt );
+
+ if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
+ (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
+ ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
+ (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
+ {
+ ImplInitSettings( true );
+ Invalidate();
+ }
+}
+
+ImplTabItem* TabControl::ImplGetItem(const Point& rPt) const
+{
+ ImplTabItem* pFoundItem = nullptr;
+ int nFound = 0;
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (item.m_bVisible && item.maRect.Contains(rPt))
+ {
+ nFound++;
+ pFoundItem = &item;
+ }
+ }
+
+ // assure that only one tab is highlighted at a time
+ assert(nFound <= 1);
+ return nFound == 1 ? pFoundItem : nullptr;
+}
+
+bool TabControl::PreNotify( NotifyEvent& rNEvt )
+{
+ if( rNEvt.GetType() == NotifyEventType::MOUSEMOVE )
+ {
+ const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
+ if( pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged() )
+ {
+ // trigger redraw if mouse over state has changed
+ if( IsNativeControlSupported(ControlType::TabItem, ControlPart::Entire) )
+ {
+ ImplTabItem *pItem = ImplGetItem(GetPointerPosPixel());
+ ImplTabItem *pLastItem = ImplGetItem(GetLastPointerPosPixel());
+ if ((pItem != pLastItem) || pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
+ {
+ vcl::Region aClipRgn;
+ if (pLastItem)
+ {
+ // allow for slightly bigger tabitems
+ // as used by gtk
+ // TODO: query for the correct sizes
+ tools::Rectangle aRect(pLastItem->maRect);
+ aRect.AdjustLeft( -2 );
+ aRect.AdjustRight(2 );
+ aRect.AdjustTop( -3 );
+ aClipRgn.Union( aRect );
+ }
+
+ if (pItem)
+ {
+ // allow for slightly bigger tabitems
+ // as used by gtk
+ // TODO: query for the correct sizes
+ tools::Rectangle aRect(pItem->maRect);
+ aRect.AdjustLeft( -2 );
+ aRect.AdjustRight(2 );
+ aRect.AdjustTop( -3 );
+ aClipRgn.Union( aRect );
+ }
+
+ if( !aClipRgn.IsEmpty() )
+ Invalidate( aClipRgn );
+ }
+ }
+ }
+ }
+
+ return Control::PreNotify(rNEvt);
+}
+
+bool TabControl::EventNotify( NotifyEvent& rNEvt )
+{
+ bool bRet = false;
+
+ if ( rNEvt.GetType() == NotifyEventType::KEYINPUT )
+ bRet = ImplHandleKeyEvent( *rNEvt.GetKeyEvent() );
+
+ return bRet || Control::EventNotify( rNEvt );
+}
+
+void TabControl::ActivatePage()
+{
+ maActivateHdl.Call( this );
+}
+
+bool TabControl::DeactivatePage()
+{
+ return !maDeactivateHdl.IsSet() || maDeactivateHdl.Call( this );
+}
+
+void TabControl::SetTabPageSizePixel( const Size& rSize )
+{
+ Size aNewSize( rSize );
+ aNewSize.AdjustWidth(TAB_OFFSET*2 );
+ tools::Rectangle aRect = ImplGetTabRect( TAB_PAGERECT,
+ aNewSize.Width(), aNewSize.Height() );
+ aNewSize.AdjustHeight(aRect.Top()+TAB_OFFSET );
+ Window::SetOutputSizePixel( aNewSize );
+}
+
+void TabControl::InsertPage( sal_uInt16 nPageId, const OUString& rText,
+ sal_uInt16 nPos )
+{
+ SAL_WARN_IF( !nPageId, "vcl", "TabControl::InsertPage(): PageId == 0" );
+ SAL_WARN_IF( GetPagePos( nPageId ) != TAB_PAGE_NOTFOUND, "vcl",
+ "TabControl::InsertPage(): PageId already exists" );
+
+ // insert new page item
+ ImplTabItem* pItem = nullptr;
+ if( nPos == TAB_APPEND || size_t(nPos) >= mpTabCtrlData->maItemList.size() )
+ {
+ mpTabCtrlData->maItemList.emplace_back(nPageId);
+ pItem = &mpTabCtrlData->maItemList.back();
+ if( mpTabCtrlData->mpListBox )
+ mpTabCtrlData->mpListBox->InsertEntry( rText );
+ }
+ else
+ {
+ std::vector< ImplTabItem >::iterator new_it =
+ mpTabCtrlData->maItemList.emplace(mpTabCtrlData->maItemList.begin() + nPos, nPageId);
+ pItem = &(*new_it);
+ if( mpTabCtrlData->mpListBox )
+ mpTabCtrlData->mpListBox->InsertEntry( rText, nPos);
+ }
+ if( mpTabCtrlData->mpListBox )
+ {
+ if( ! mnCurPageId )
+ mpTabCtrlData->mpListBox->SelectEntryPos( 0 );
+ mpTabCtrlData->mpListBox->SetDropDownLineCount( mpTabCtrlData->mpListBox->GetEntryCount() );
+ }
+
+ // set current page id
+ if ( !mnCurPageId )
+ mnCurPageId = nPageId;
+
+ // init new page item
+ pItem->maText = rText;
+ pItem->mbFullVisible = false;
+
+ mbFormat = true;
+ if ( IsUpdateMode() )
+ Invalidate();
+
+ if( mpTabCtrlData->mpListBox ) // reposition/resize listbox
+ Resize();
+
+ CallEventListeners( VclEventId::TabpageInserted, reinterpret_cast<void*>(nPageId) );
+}
+
+void TabControl::RemovePage( sal_uInt16 nPageId )
+{
+ sal_uInt16 nPos = GetPagePos( nPageId );
+
+ // does the item exist ?
+ if ( nPos == TAB_PAGE_NOTFOUND )
+ return;
+
+ //remove page item
+ std::vector< ImplTabItem >::iterator it = mpTabCtrlData->maItemList.begin() + nPos;
+ bool bIsCurrentPage = (it->id() == mnCurPageId);
+ mpTabCtrlData->maItemList.erase( it );
+ if( mpTabCtrlData->mpListBox )
+ {
+ mpTabCtrlData->mpListBox->RemoveEntry( nPos );
+ mpTabCtrlData->mpListBox->SetDropDownLineCount( mpTabCtrlData->mpListBox->GetEntryCount() );
+ }
+
+ // If current page is removed, then first page gets the current page
+ if ( bIsCurrentPage )
+ {
+ mnCurPageId = 0;
+
+ if( ! mpTabCtrlData->maItemList.empty() )
+ {
+ // don't do this by simply setting mnCurPageId to pFirstItem->id()
+ // this leaves a lot of stuff (such trivia as _showing_ the new current page) undone
+ // instead, call SetCurPageId
+ // without this, the next (outside) call to SetCurPageId with the id of the first page
+ // will result in doing nothing (as we assume that nothing changed, then), and the page
+ // will never be shown.
+ // 86875 - 05/11/2001 - frank.schoenheit@germany.sun.com
+
+ SetCurPageId(mpTabCtrlData->maItemList[0].id());
+ }
+ }
+
+ mbFormat = true;
+ if ( IsUpdateMode() )
+ Invalidate();
+
+ CallEventListeners( VclEventId::TabpageRemoved, reinterpret_cast<void*>(nPageId) );
+}
+
+void TabControl::SetPageEnabled( sal_uInt16 i_nPageId, bool i_bEnable )
+{
+ ImplTabItem* pItem = ImplGetItem( i_nPageId );
+
+ if (!pItem || pItem->m_bEnabled == i_bEnable)
+ return;
+
+ pItem->m_bEnabled = i_bEnable;
+ if (!pItem->m_bVisible)
+ return;
+
+ mbFormat = true;
+ if( mpTabCtrlData->mpListBox )
+ mpTabCtrlData->mpListBox->SetEntryFlags( GetPagePos( i_nPageId ),
+ i_bEnable ? ListBoxEntryFlags::NONE : (ListBoxEntryFlags::DisableSelection | ListBoxEntryFlags::DrawDisabled) );
+
+ // SetCurPageId will change to a valid page
+ if (pItem->id() == mnCurPageId)
+ SetCurPageId( mnCurPageId );
+ else if ( IsUpdateMode() )
+ Invalidate();
+}
+
+void TabControl::SetPageVisible( sal_uInt16 nPageId, bool bVisible )
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ if (!pItem || pItem->m_bVisible == bVisible)
+ return;
+
+ pItem->m_bVisible = bVisible;
+ if (!bVisible)
+ {
+ if (pItem->mbFullVisible)
+ mbSmallInvalidate = false;
+ pItem->mbFullVisible = false;
+ pItem->maRect.SetEmpty();
+ }
+ mbFormat = true;
+
+ // SetCurPageId will change to a valid page
+ if (pItem->id() == mnCurPageId)
+ SetCurPageId(mnCurPageId);
+ else if (IsUpdateMode())
+ Invalidate();
+}
+
+sal_uInt16 TabControl::GetPageCount() const
+{
+ return static_cast<sal_uInt16>(mpTabCtrlData->maItemList.size());
+}
+
+sal_uInt16 TabControl::GetPageId( sal_uInt16 nPos ) const
+{
+ if( size_t(nPos) < mpTabCtrlData->maItemList.size() )
+ return mpTabCtrlData->maItemList[nPos].id();
+ return 0;
+}
+
+sal_uInt16 TabControl::GetPagePos( sal_uInt16 nPageId ) const
+{
+ sal_uInt16 nPos = 0;
+ for (auto const& item : mpTabCtrlData->maItemList)
+ {
+ if (item.id() == nPageId)
+ return nPos;
+ ++nPos;
+ }
+
+ return TAB_PAGE_NOTFOUND;
+}
+
+sal_uInt16 TabControl::GetPageId( const Point& rPos ) const
+{
+ Size winSize = Control::GetOutputSizePixel();
+ const auto &rList = mpTabCtrlData->maItemList;
+ const auto it = std::find_if(rList.begin(), rList.end(), [&rPos, &winSize, this](const auto &item) {
+ return const_cast<TabControl*>(this)->ImplGetTabRect(&item, winSize.Width(), winSize.Height()).Contains(rPos); });
+ return (it != rList.end()) ? it->id() : 0;
+}
+
+sal_uInt16 TabControl::GetPageId( const OUString& rName ) const
+{
+ const auto &rList = mpTabCtrlData->maItemList;
+ const auto it = std::find_if(rList.begin(), rList.end(), [&rName](const auto &item) {
+ return item.maTabName == rName; });
+ return (it != rList.end()) ? it->id() : 0;
+}
+
+void TabControl::SetCurPageId( sal_uInt16 nPageId )
+{
+ sal_uInt16 nPos = GetPagePos( nPageId );
+ while (nPos != TAB_PAGE_NOTFOUND && !mpTabCtrlData->maItemList[nPos].m_bEnabled)
+ {
+ nPos++;
+ if( size_t(nPos) >= mpTabCtrlData->maItemList.size() )
+ nPos = 0;
+ if (mpTabCtrlData->maItemList[nPos].id() == nPageId)
+ break;
+ }
+
+ if( nPos == TAB_PAGE_NOTFOUND )
+ return;
+
+ nPageId = mpTabCtrlData->maItemList[nPos].id();
+ if ( nPageId == mnCurPageId )
+ {
+ if ( mnActPageId )
+ mnActPageId = nPageId;
+ return;
+ }
+
+ if ( mnActPageId )
+ mnActPageId = nPageId;
+ else
+ {
+ mbFormat = true;
+ sal_uInt16 nOldId = mnCurPageId;
+ mnCurPageId = nPageId;
+ ImplChangeTabPage( nPageId, nOldId );
+ }
+}
+
+sal_uInt16 TabControl::GetCurPageId() const
+{
+ if ( mnActPageId )
+ return mnActPageId;
+ else
+ return mnCurPageId;
+}
+
+void TabControl::SelectTabPage( sal_uInt16 nPageId )
+{
+ if ( !nPageId || (nPageId == mnCurPageId) )
+ return;
+
+ CallEventListeners( VclEventId::TabpageDeactivate, reinterpret_cast<void*>(mnCurPageId) );
+ if ( DeactivatePage() )
+ {
+ mnActPageId = nPageId;
+ ActivatePage();
+ // Page could have been switched by the Activate handler
+ nPageId = mnActPageId;
+ mnActPageId = 0;
+ SetCurPageId( nPageId );
+ if( mpTabCtrlData->mpListBox )
+ mpTabCtrlData->mpListBox->SelectEntryPos( GetPagePos( nPageId ) );
+ CallEventListeners( VclEventId::TabpageActivate, reinterpret_cast<void*>(nPageId) );
+ }
+}
+
+void TabControl::SetTabPage( sal_uInt16 nPageId, TabPage* pTabPage )
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if ( !pItem || (pItem->mpTabPage.get() == pTabPage) )
+ return;
+
+ if ( pTabPage )
+ {
+ if ( IsDefaultSize() )
+ SetTabPageSizePixel( pTabPage->GetSizePixel() );
+
+ // only set here, so that Resize does not reposition TabPage
+ pItem->mpTabPage = pTabPage;
+ queue_resize();
+
+ if (pItem->id() == mnCurPageId)
+ ImplChangeTabPage(pItem->id(), 0);
+ }
+ else
+ {
+ pItem->mpTabPage = nullptr;
+ queue_resize();
+ }
+}
+
+TabPage* TabControl::GetTabPage( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if ( pItem )
+ return pItem->mpTabPage;
+ else
+ return nullptr;
+}
+
+void TabControl::SetPageText( sal_uInt16 nPageId, const OUString& rText )
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if ( !pItem || pItem->maText == rText )
+ return;
+
+ pItem->maText = rText;
+ mbFormat = true;
+ if( mpTabCtrlData->mpListBox )
+ {
+ sal_uInt16 nPos = GetPagePos( nPageId );
+ mpTabCtrlData->mpListBox->RemoveEntry( nPos );
+ mpTabCtrlData->mpListBox->InsertEntry( rText, nPos );
+ }
+ if ( IsUpdateMode() )
+ Invalidate();
+ CallEventListeners( VclEventId::TabpagePageTextChanged, reinterpret_cast<void*>(nPageId) );
+}
+
+OUString const & TabControl::GetPageText( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ assert( pItem );
+
+ return pItem->maText;
+}
+
+void TabControl::SetHelpText( sal_uInt16 nPageId, const OUString& rText )
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ assert( pItem );
+
+ pItem->maHelpText = rText;
+}
+
+const OUString& TabControl::GetHelpText( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ assert( pItem );
+ return pItem->maHelpText;
+}
+
+void TabControl::SetAccessibleName(sal_uInt16 nPageId, const OUString& rName)
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ assert( pItem );
+ pItem->maAccessibleName = rName;
+}
+
+OUString TabControl::GetAccessibleName( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ assert( pItem );
+ if (!pItem->maAccessibleName.isEmpty())
+ return pItem->maAccessibleName;
+ return removeMnemonicFromString(pItem->maText);
+}
+
+void TabControl::SetAccessibleDescription(sal_uInt16 nPageId, const OUString& rDesc)
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ assert( pItem );
+ pItem->maAccessibleDescription = rDesc;
+}
+
+OUString TabControl::GetAccessibleDescription( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ assert( pItem );
+ if (!pItem->maAccessibleDescription.isEmpty())
+ return pItem->maAccessibleDescription;
+ return pItem->maHelpText;
+}
+
+void TabControl::SetPageName( sal_uInt16 nPageId, const OUString& rName ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if ( pItem )
+ pItem->maTabName = rName;
+}
+
+OUString TabControl::GetPageName( sal_uInt16 nPageId ) const
+{
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+
+ if (pItem)
+ return pItem->maTabName;
+
+ return {};
+}
+
+void TabControl::SetPageImage( sal_uInt16 i_nPageId, const Image& i_rImage )
+{
+ ImplTabItem* pItem = ImplGetItem( i_nPageId );
+
+ if ( pItem )
+ {
+ pItem->maTabImage = i_rImage;
+ mbFormat = true;
+ if ( IsUpdateMode() )
+ Invalidate();
+ }
+}
+
+tools::Rectangle TabControl::GetTabBounds( sal_uInt16 nPageId ) const
+{
+ tools::Rectangle aRet;
+
+ ImplTabItem* pItem = ImplGetItem( nPageId );
+ if (pItem && pItem->m_bVisible)
+ aRet = pItem->maRect;
+
+ return aRet;
+}
+
+Size TabControl::ImplCalculateRequisition(sal_uInt16& nHeaderHeight) const
+{
+ Size aOptimalPageSize(0, 0);
+
+ sal_uInt16 nOrigPageId = GetCurPageId();
+ for (auto const& item : mpTabCtrlData->maItemList)
+ {
+ const TabPage *pPage = item.mpTabPage;
+ //it's a real nuisance if the page is not inserted yet :-(
+ //We need to force all tabs to exist to get overall optimal size for dialog
+ if (!pPage)
+ {
+ TabControl *pThis = const_cast<TabControl*>(this);
+ pThis->SetCurPageId(item.id());
+ pThis->ActivatePage();
+ pPage = item.mpTabPage;
+ }
+
+ if (!pPage)
+ continue;
+
+ Size aPageSize(VclContainer::getLayoutRequisition(*pPage));
+
+ if (aPageSize.Width() > aOptimalPageSize.Width())
+ aOptimalPageSize.setWidth( aPageSize.Width() );
+ if (aPageSize.Height() > aOptimalPageSize.Height())
+ aOptimalPageSize.setHeight( aPageSize.Height() );
+ }
+
+ //fdo#61940 If we were forced to activate pages in order to on-demand
+ //create them to get their optimal size, then switch back to the original
+ //page and re-activate it
+ if (nOrigPageId != GetCurPageId())
+ {
+ TabControl *pThis = const_cast<TabControl*>(this);
+ pThis->SetCurPageId(nOrigPageId);
+ pThis->ActivatePage();
+ }
+
+ tools::Long nTabLabelsBottom = 0, nTabLabelsRight = 0;
+ if (mbShowTabs)
+ {
+ for (sal_uInt16 nPos(0), sizeList(static_cast <sal_uInt16> (mpTabCtrlData->maItemList.size()));
+ nPos < sizeList; ++nPos)
+ {
+ TabControl* pThis = const_cast<TabControl*>(this);
+
+ tools::Rectangle aTabRect = pThis->ImplGetTabRect(nPos, aOptimalPageSize.Width(), LONG_MAX);
+ if (aTabRect.Bottom() > nTabLabelsBottom)
+ {
+ nTabLabelsBottom = aTabRect.Bottom();
+ nHeaderHeight = nTabLabelsBottom;
+ }
+ if (!aTabRect.IsEmpty() && aTabRect.Right() > nTabLabelsRight)
+ nTabLabelsRight = aTabRect.Right();
+ }
+ }
+
+ Size aOptimalSize(aOptimalPageSize);
+ aOptimalSize.AdjustHeight(nTabLabelsBottom );
+ aOptimalSize.setWidth( std::max(nTabLabelsRight, aOptimalSize.Width()) );
+
+ aOptimalSize.AdjustWidth(TAB_OFFSET * 2 );
+ aOptimalSize.AdjustHeight(TAB_OFFSET * 2 );
+
+ return aOptimalSize;
+}
+
+Size TabControl::calculateRequisition() const
+{
+ sal_uInt16 nHeaderHeight;
+ return ImplCalculateRequisition(nHeaderHeight);
+}
+
+Size TabControl::GetOptimalSize() const
+{
+ return calculateRequisition();
+}
+
+void TabControl::queue_resize(StateChangedType eReason)
+{
+ mbLayoutDirty = true;
+ Window::queue_resize(eReason);
+}
+
+std::vector<sal_uInt16> TabControl::GetPageIDs() const
+{
+ std::vector<sal_uInt16> aIDs;
+ for (auto const& item : mpTabCtrlData->maItemList)
+ {
+ aIDs.push_back(item.id());
+ }
+
+ return aIDs;
+}
+
+bool TabControl::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "show-tabs")
+ {
+ mbShowTabs = toBool(rValue);
+ queue_resize();
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+FactoryFunction TabControl::GetUITestFactory() const
+{
+ return TabControlUIObject::create;
+}
+
+void TabControl::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
+{
+ rJsonWriter.put("id", get_id());
+ rJsonWriter.put("type", "tabcontrol");
+ rJsonWriter.put("selected", GetCurPageId());
+
+ {
+ auto childrenNode = rJsonWriter.startArray("children");
+ for (auto id : GetPageIDs())
+ {
+ TabPage* pChild = GetTabPage(id);
+
+ if (pChild)
+ {
+ auto childNode = rJsonWriter.startStruct();
+ pChild->DumpAsPropertyTree(rJsonWriter);
+
+ if (!pChild->IsVisible())
+ rJsonWriter.put("hidden", true);
+ }
+ }
+ }
+ {
+ auto tabsNode = rJsonWriter.startArray("tabs");
+ for(auto id : GetPageIDs())
+ {
+ auto tabNode = rJsonWriter.startStruct();
+ rJsonWriter.put("text", GetPageText(id));
+ rJsonWriter.put("id", id);
+ rJsonWriter.put("name", GetPageName(id));
+ }
+ }
+}
+
+sal_uInt16 NotebookbarTabControlBase::m_nHeaderHeight = 0;
+
+IMPL_LINK_NOARG(NotebookbarTabControlBase, OpenMenu, Button*, void)
+{
+ m_aIconClickHdl.Call(static_cast<NotebookBar*>(GetParent()->GetParent()));
+}
+
+NotebookbarTabControlBase::NotebookbarTabControlBase(vcl::Window* pParent)
+ : TabControl(pParent, WB_STDTABCONTROL)
+ , bLastContextWasSupported(true)
+ , eLastContext(vcl::EnumContext::Context::Any)
+{
+ m_pOpenMenu = VclPtr<PushButton>::Create( this , WB_CENTER | WB_VCENTER );
+ m_pOpenMenu->SetClickHdl(LINK(this, NotebookbarTabControlBase, OpenMenu));
+ m_pOpenMenu->SetModeImage(Image(StockImage::Yes, SV_RESID_BITMAP_NOTEBOOKBAR));
+ m_pOpenMenu->SetSizePixel(m_pOpenMenu->GetOptimalSize());
+ m_pOpenMenu->Show();
+}
+
+NotebookbarTabControlBase::~NotebookbarTabControlBase()
+{
+ disposeOnce();
+}
+
+void NotebookbarTabControlBase::SetContext( vcl::EnumContext::Context eContext )
+{
+ if (eLastContext == eContext)
+ return;
+
+ bool bHandled = false;
+
+ TabPage* pPage = GetTabPage(mnCurPageId);
+ // Try to stay on the current tab (unless the new context has a special tab)
+ if (pPage && eLastContext != vcl::EnumContext::Context::Any
+ && pPage->HasContext(vcl::EnumContext::Context::Any) && pPage->IsEnabled())
+ {
+ bHandled = true;
+ }
+
+ for (int nChild = 0; nChild < GetPageCount(); ++nChild)
+ {
+ sal_uInt16 nPageId = TabControl::GetPageId(nChild);
+ pPage = GetTabPage(nPageId);
+
+ if (!pPage)
+ continue;
+
+ SetPageVisible(nPageId, pPage->HasContext(eContext) || pPage->HasContext(vcl::EnumContext::Context::Any));
+
+ if (eContext != vcl::EnumContext::Context::Any
+ && (!bHandled || !pPage->HasContext(vcl::EnumContext::Context::Any))
+ && pPage->HasContext(eContext))
+ {
+ SetCurPageId(nPageId);
+ bHandled = true;
+ bLastContextWasSupported = true;
+ }
+
+ if (!bHandled && bLastContextWasSupported
+ && pPage->HasContext(vcl::EnumContext::Context::Default))
+ {
+ SetCurPageId(nPageId);
+ }
+ }
+
+ if (!bHandled)
+ bLastContextWasSupported = false;
+ eLastContext = eContext;
+
+ // tdf#152908 Tabbed compact toolbar does not repaint itself when tabs getting removed
+ // For unknown reason this is needed by the tabbed compact toolbar for other than gtk
+ // vcl backends.
+ Resize();
+}
+
+void NotebookbarTabControlBase::dispose()
+{
+ m_pShortcuts.disposeAndClear();
+ m_pOpenMenu.disposeAndClear();
+ TabControl::dispose();
+}
+
+void NotebookbarTabControlBase::SetToolBox( ToolBox* pToolBox )
+{
+ m_pShortcuts.set( pToolBox );
+}
+
+void NotebookbarTabControlBase::SetIconClickHdl( Link<NotebookBar*, void> aHdl )
+{
+ m_aIconClickHdl = aHdl;
+}
+
+static bool lcl_isValidPage(const ImplTabItem& rItem, bool& bFound)
+{
+ if (rItem.m_bVisible && rItem.m_bEnabled)
+ bFound = true;
+ return bFound;
+}
+
+void NotebookbarTabControlBase::ImplActivateTabPage( bool bNext )
+{
+ const sal_uInt16 nOldPos = GetPagePos(GetCurPageId());
+ bool bFound = false;
+ sal_Int32 nCurPos = nOldPos;
+
+ if (bNext)
+ {
+ for (nCurPos++; nCurPos < GetPageCount(); nCurPos++)
+ if (lcl_isValidPage(mpTabCtrlData->maItemList[nCurPos], bFound))
+ break;
+ }
+ else
+ {
+ for (nCurPos--; nCurPos >= 0; nCurPos--)
+ if (lcl_isValidPage(mpTabCtrlData->maItemList[nCurPos], bFound))
+ break;
+ }
+
+ if (!bFound)
+ nCurPos = nOldPos;
+ SelectTabPage( TabControl::GetPageId( nCurPos ) );
+}
+
+sal_uInt16 NotebookbarTabControlBase::GetHeaderHeight()
+{
+ return m_nHeaderHeight;
+}
+
+bool NotebookbarTabControlBase::ImplPlaceTabs( tools::Long nWidth )
+{
+ if ( nWidth <= 0 )
+ return false;
+ if ( mpTabCtrlData->maItemList.empty() )
+ return false;
+ if (!m_pOpenMenu || m_pOpenMenu->isDisposed())
+ return false;
+
+ const tools::Long nHamburgerWidth = m_pOpenMenu->GetSizePixel().Width();
+ tools::Long nMaxWidth = nWidth - nHamburgerWidth;
+ tools::Long nShortcutsWidth = m_pShortcuts != nullptr ? m_pShortcuts->GetSizePixel().getWidth() + 1 : 0;
+ tools::Long nFullWidth = nShortcutsWidth;
+
+ const tools::Long nOffsetX = 2 + nShortcutsWidth;
+ const tools::Long nOffsetY = 2;
+
+ //fdo#66435 throw Knuth/Tex minimum raggedness algorithm at the problem
+ //of ugly bare tabs on lines of their own
+
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ tools::Long nTabWidth = 0;
+ if (item.m_bVisible)
+ {
+ nTabWidth = ImplGetItemSize(&item, nMaxWidth).getWidth();
+ if (!item.maText.isEmpty() && nTabWidth < 100)
+ nTabWidth = 100;
+ }
+ nFullWidth += nTabWidth;
+ }
+
+ tools::Long nX = nOffsetX;
+ tools::Long nY = nOffsetY;
+
+ tools::Long nLineWidthAry[100];
+ nLineWidthAry[0] = 0;
+
+ for (auto & item : mpTabCtrlData->maItemList)
+ {
+ if (!item.m_bVisible)
+ continue;
+
+ Size aSize = ImplGetItemSize( &item, nMaxWidth );
+
+ // set minimum tab size
+ if( nFullWidth < nMaxWidth && !item.maText.isEmpty() && aSize.getWidth() < 100)
+ aSize.setWidth( 100 );
+
+ if( !item.maText.isEmpty() && aSize.getHeight() < 28 )
+ aSize.setHeight( 28 );
+
+ tools::Rectangle aNewRect( Point( nX, nY ), aSize );
+ if ( mbSmallInvalidate && (item.maRect != aNewRect) )
+ mbSmallInvalidate = false;
+
+ item.maRect = aNewRect;
+ item.mnLine = 0;
+ item.mbFullVisible = true;
+
+ nLineWidthAry[0] += aSize.Width();
+ nX += aSize.Width();
+ }
+
+ // we always have only one line of tabs
+ lcl_AdjustSingleLineTabs(nMaxWidth, mpTabCtrlData.get());
+
+ // position the shortcutbox
+ if (m_pShortcuts)
+ {
+ tools::Long nPosY = (m_nHeaderHeight - m_pShortcuts->GetSizePixel().getHeight()) / 2;
+ m_pShortcuts->SetPosPixel(Point(0, nPosY));
+ }
+
+ tools::Long nPosY = (m_nHeaderHeight - m_pOpenMenu->GetSizePixel().getHeight()) / 2;
+ // position the menu
+ m_pOpenMenu->SetPosPixel(Point(nWidth - nHamburgerWidth, nPosY));
+
+ return true;
+}
+
+Size NotebookbarTabControlBase::calculateRequisition() const
+{
+ return TabControl::ImplCalculateRequisition(m_nHeaderHeight);
+}
+
+Control* NotebookbarTabControlBase::GetOpenMenu()
+{
+ return m_pOpenMenu;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/throbber.cxx b/vcl/source/control/throbber.cxx
new file mode 100644
index 0000000000..075e40d2de
--- /dev/null
+++ b/vcl/source/control/throbber.cxx
@@ -0,0 +1,234 @@
+/* -*- 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/throbber.hxx>
+#include <vcl/svapp.hxx>
+
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/graphic/GraphicProvider.hpp>
+#include <com/sun/star/graphic/XGraphicProvider.hpp>
+#include <com/sun/star/awt/ImageScaleMode.hpp>
+
+#include <comphelper/namedvaluecollection.hxx>
+#include <comphelper/processfactory.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/urlobj.hxx>
+
+#include <limits>
+
+using ::com::sun::star::uno::Reference;
+using ::com::sun::star::graphic::XGraphic;
+using ::com::sun::star::graphic::XGraphicProvider;
+using ::com::sun::star::uno::Exception;
+namespace ImageScaleMode = ::com::sun::star::awt::ImageScaleMode;
+
+Throbber::Throbber( vcl::Window* i_parentWindow, WinBits i_style )
+ :ImageControl( i_parentWindow, i_style )
+ ,mbRepeat( true )
+ ,mnStepTime( 100 )
+ ,mnCurStep( 0 )
+ ,maWaitTimer("Throbber maWaitTimer")
+{
+ maWaitTimer.SetTimeout( mnStepTime );
+ maWaitTimer.SetInvokeHandler( LINK( this, Throbber, TimeOutHdl ) );
+
+ SetScaleMode( ImageScaleMode::NONE );
+ initImages();
+}
+
+Throbber::~Throbber()
+{
+ disposeOnce();
+}
+
+void Throbber::dispose()
+{
+ maWaitTimer.Stop();
+ ImageControl::dispose();
+}
+
+namespace
+{
+ ::std::vector< Image > lcl_loadImageSet( const Throbber::ImageSet i_imageSet )
+ {
+ ::std::vector< Image > aImages;
+
+ const Reference< css::uno::XComponentContext > aContext( ::comphelper::getProcessComponentContext() );
+ const Reference< XGraphicProvider > xGraphicProvider( css::graphic::GraphicProvider::create(aContext) );
+
+ ::std::vector< OUString > aImageURLs( Throbber::getDefaultImageURLs( i_imageSet ) );
+ aImages.reserve( aImageURLs.size() );
+
+ ::comphelper::NamedValueCollection aMediaProperties;
+ for ( const auto& rImageURL : aImageURLs )
+ {
+ Reference< XGraphic > xGraphic;
+ aMediaProperties.put( "URL", rImageURL );
+ xGraphic = xGraphicProvider->queryGraphic( aMediaProperties.getPropertyValues() );
+ aImages.emplace_back( xGraphic );
+ }
+
+ return aImages;
+ }
+}
+
+void Throbber::Resize()
+{
+ ImageControl::Resize();
+ initImages();
+}
+
+void Throbber::initImages()
+{
+ try
+ {
+ ::std::vector< ::std::vector< Image > > aImageSets
+ {
+ lcl_loadImageSet( ImageSet::N16px ),
+ lcl_loadImageSet( ImageSet::N32px ),
+ lcl_loadImageSet( ImageSet::N64px )
+ };
+
+ // find the best matching image set (size-wise)
+ const ::Size aWindowSizePixel = GetSizePixel();
+ size_t nPreferredSet = 0;
+ if ( aImageSets.size() > 1 )
+ {
+ tools::Long nMinimalDistance = ::std::numeric_limits< tools::Long >::max();
+ for ( ::std::vector< ::std::vector< Image > >::const_iterator check = aImageSets.begin();
+ check != aImageSets.end();
+ ++check
+ )
+ {
+ if ( check->empty() )
+ {
+ SAL_WARN( "vcl.control", "Throbber::initImages: illegal image!" );
+ continue;
+ }
+
+ const Size aImageSize = (*check)[0].GetSizePixel();
+
+ if ( ( aImageSize.Width() > aWindowSizePixel.Width() )
+ || ( aImageSize.Height() > aWindowSizePixel.Height() )
+ )
+ // do not use an image set which doesn't fit into the window
+ continue;
+
+ const sal_Int64 distance =
+ ( aWindowSizePixel.Width() - aImageSize.Width() ) * ( aWindowSizePixel.Width() - aImageSize.Width() )
+ + ( aWindowSizePixel.Height() - aImageSize.Height() ) * ( aWindowSizePixel.Height() - aImageSize.Height() );
+ if ( distance < nMinimalDistance )
+ {
+ nMinimalDistance = distance;
+ nPreferredSet = check - aImageSets.begin();
+ }
+ }
+ }
+
+ if ( nPreferredSet < aImageSets.size() )
+ setImageList( std::vector(aImageSets[nPreferredSet]) );
+ }
+ catch( const Exception& )
+ {
+ }
+}
+
+void Throbber::start()
+{
+ maWaitTimer.SetTimeout(mnStepTime);
+ maWaitTimer.Start();
+}
+
+void Throbber::stop()
+{
+ maWaitTimer.Stop();
+}
+
+bool Throbber::isRunning() const
+{
+ return maWaitTimer.IsActive();
+}
+
+void Throbber::setImageList( ::std::vector< Image > && i_images )
+{
+ SAL_WARN_IF( i_images.size()>=SAL_MAX_INT32, "vcl.control", "Throbber::setImageList: too many images!" );
+
+ maImageList = std::move(i_images);
+
+ const Image aInitialImage( !maImageList.empty() ? maImageList[ 0 ] : Image() );
+ SetImage( aInitialImage );
+}
+
+::std::vector< OUString > Throbber::getDefaultImageURLs( const ImageSet i_imageSet )
+{
+ ::std::vector< OUString > aImageURLs;
+
+ sal_Unicode const* const pResolutions[] = { u"16", u"32", u"64" };
+ size_t const nImageCounts[] = { 6, 12, 12 };
+
+ size_t index = 0;
+ switch ( i_imageSet )
+ {
+ case ImageSet::N16px: index = 0; break;
+ case ImageSet::N32px: index = 1; break;
+ case ImageSet::N64px: index = 2; break;
+ }
+
+ aImageURLs.reserve( nImageCounts[index] );
+ for ( size_t i=0; i<nImageCounts[index]; ++i )
+ {
+ OUStringBuffer aURL( OUString::Concat("private:graphicrepository/vcl/res/spinner-")
+ + pResolutions[index]
+ + "-" );
+ if ( i < 9 )
+ aURL.append( "0" );
+ aURL.append( OUString::number( sal_Int32( i + 1 ) ) + ".png" );
+
+ aImageURLs.push_back( aURL.makeStringAndClear() );
+ }
+
+ return aImageURLs;
+}
+
+IMPL_LINK_NOARG(Throbber, TimeOutHdl, Timer *, void)
+{
+ SolarMutexGuard aGuard;
+ if ( maImageList.empty() )
+ return;
+
+ if ( mnCurStep < static_cast<sal_Int32>(maImageList.size()-1) )
+ ++mnCurStep;
+ else
+ {
+ if ( mbRepeat )
+ {
+ // start over
+ mnCurStep = 0;
+ }
+ else
+ {
+ stop();
+ }
+ }
+
+ SetImage( maImageList[ mnCurStep ] );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/thumbpos.hxx b/vcl/source/control/thumbpos.hxx
new file mode 100644
index 0000000000..33d3d55f31
--- /dev/null
+++ b/vcl/source/control/thumbpos.hxx
@@ -0,0 +1,21 @@
+/* -*- 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
+
+inline tools::Long ImplMulDiv(tools::Long nNumber, tools::Long nNumerator, tools::Long nDenominator)
+{
+ if (!nDenominator)
+ return 0;
+ double n = (static_cast<double>(nNumber) * static_cast<double>(nNumerator))
+ / static_cast<double>(nDenominator);
+ return static_cast<tools::Long>(n);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/wizardmachine.cxx b/vcl/source/control/wizardmachine.cxx
new file mode 100644
index 0000000000..68dd66004a
--- /dev/null
+++ b/vcl/source/control/wizardmachine.cxx
@@ -0,0 +1,1436 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <comphelper/lok.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <vcl/event.hxx>
+#include <tools/debug.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <strings.hrc>
+#include <svdata.hxx>
+#include <wizdlg.hxx>
+#include <stack>
+#include "wizimpldata.hxx"
+
+constexpr OUString HID_WIZARD_NEXT = u"SVT_HID_WIZARD_NEXT"_ustr;
+constexpr OUString HID_WIZARD_PREVIOUS = u"SVT_HID_WIZARD_PREVIOUS"_ustr;
+
+#define WIZARDDIALOG_BUTTON_OFFSET_Y 6
+#define WIZARDDIALOG_BUTTON_DLGOFFSET_X 6
+#define WIZARDDIALOG_VIEW_DLGOFFSET_X 6
+#define WIZARDDIALOG_VIEW_DLGOFFSET_Y 6
+
+namespace vcl
+{
+ //= WizardPageImplData
+ OWizardPage::OWizardPage(weld::Container* pPage, weld::DialogController* pController, const OUString& rUIXMLDescription, const OUString& rID)
+ : BuilderPage(pPage, pController, rUIXMLDescription, rID)
+ {
+ }
+
+ OWizardPage::~OWizardPage()
+ {
+ }
+
+ void OWizardPage::initializePage()
+ {
+ }
+
+ void OWizardPage::Activate()
+ {
+ BuilderPage::Activate();
+ updateDialogTravelUI();
+ }
+
+ void OWizardPage::updateDialogTravelUI()
+ {
+ auto pWizardMachine = dynamic_cast<RoadmapWizardMachine*>(m_pDialogController);
+ if (pWizardMachine)
+ pWizardMachine->updateTravelUI();
+ }
+
+ bool OWizardPage::canAdvance() const
+ {
+ return true;
+ }
+
+ bool OWizardPage::commitPage( WizardTypes::CommitPageReason )
+ {
+ return true;
+ }
+
+ void RoadmapWizard::SetLeftAlignedButtonCount( sal_Int16 _nCount )
+ {
+ mnLeftAlignCount = _nCount;
+ }
+
+ void RoadmapWizard::ImplCalcSize( Size& rSize )
+ {
+ // calculate ButtonBar height and width
+ tools::Long nMaxHeight = 0;
+ tools::Long nBarWidth = WIZARDDIALOG_BUTTON_DLGOFFSET_X * 2 + LogicalCoordinateToPixel(6);
+ ImplWizButtonData* pBtnData = mpFirstBtn;
+ while (pBtnData)
+ {
+ auto nBtnHeight = pBtnData->mpButton->GetSizePixel().Height();
+ auto nBtnWidth = pBtnData->mpButton->GetSizePixel().Width();
+ if (pBtnData->mpButton->IsVisible())
+ {
+ nBarWidth += nBtnWidth;
+ nBarWidth += pBtnData->mnOffset;
+ }
+ if ( nBtnHeight > nMaxHeight )
+ nMaxHeight = nBtnHeight;
+ pBtnData = pBtnData->mpNext;
+ }
+ if ( nMaxHeight )
+ nMaxHeight += WIZARDDIALOG_BUTTON_OFFSET_Y*2;
+ rSize.AdjustHeight(nMaxHeight);
+
+ // add in the view window size
+ if ( mpViewWindow && mpViewWindow->IsVisible() )
+ {
+ Size aViewSize = mpViewWindow->GetSizePixel();
+ // align left
+ rSize.AdjustWidth(aViewSize.Width() );
+ }
+
+ if (nBarWidth > rSize.Width())
+ rSize.setWidth(nBarWidth);
+ }
+
+ void RoadmapWizard::queue_resize(StateChangedType /*eReason*/)
+ {
+ if (maWizardLayoutIdle.IsActive())
+ return;
+ if (IsInClose())
+ return;
+ maWizardLayoutIdle.Start();
+ }
+
+ IMPL_LINK_NOARG(RoadmapWizard, ImplHandleWizardLayoutTimerHdl, Timer*, void)
+ {
+ ImplPosCtrls();
+ ImplPosTabPage();
+ }
+
+ void RoadmapWizard::ImplPosCtrls()
+ {
+ Size aDlgSize = GetOutputSizePixel();
+ tools::Long nBtnWidth = 0;
+ tools::Long nMaxHeight = 0;
+ tools::Long nOffY = aDlgSize.Height();
+
+ ImplWizButtonData* pBtnData = mpFirstBtn;
+ int j = 0;
+ while ( pBtnData )
+ {
+ if (j >= mnLeftAlignCount)
+ {
+ Size aBtnSize = pBtnData->mpButton->GetSizePixel();
+ tools::Long nBtnHeight = aBtnSize.Height();
+ if ( nBtnHeight > nMaxHeight )
+ nMaxHeight = nBtnHeight;
+ nBtnWidth += aBtnSize.Width();
+ nBtnWidth += pBtnData->mnOffset;
+ }
+ pBtnData = pBtnData->mpNext;
+ j++;
+ }
+
+ if ( nMaxHeight )
+ {
+ tools::Long nOffX = aDlgSize.Width()-nBtnWidth-WIZARDDIALOG_BUTTON_DLGOFFSET_X;
+ tools::Long nOffLeftAlignX = LogicalCoordinateToPixel(6);
+ nOffY -= WIZARDDIALOG_BUTTON_OFFSET_Y+nMaxHeight;
+
+ pBtnData = mpFirstBtn;
+ int i = 0;
+ while ( pBtnData )
+ {
+ Size aBtnSize = pBtnData->mpButton->GetSizePixel();
+ if (i >= mnLeftAlignCount)
+ {
+ Point aPos( nOffX, nOffY+((nMaxHeight-aBtnSize.Height())/2) );
+ pBtnData->mpButton->SetPosPixel( aPos );
+ nOffX += aBtnSize.Width();
+ nOffX += pBtnData->mnOffset;
+ }
+ else
+ {
+ Point aPos( nOffLeftAlignX, nOffY+((nMaxHeight-aBtnSize.Height())/2) );
+ pBtnData->mpButton->SetPosPixel( aPos );
+ nOffLeftAlignX += aBtnSize.Width();
+ nOffLeftAlignX += pBtnData->mnOffset;
+ }
+
+ pBtnData = pBtnData->mpNext;
+ i++;
+ }
+
+ nOffY -= WIZARDDIALOG_BUTTON_OFFSET_Y;
+ }
+
+ if ( !(mpViewWindow && mpViewWindow->IsVisible()) )
+ return;
+
+ tools::Long nViewOffX = 0;
+ tools::Long nViewOffY = 0;
+ tools::Long nViewWidth = 0;
+ tools::Long nViewHeight = 0;
+ tools::Long nDlgHeight = nOffY;
+ PosSizeFlags nViewPosFlags = PosSizeFlags::Pos;
+ // align left
+ {
+ if ( mbEmptyViewMargin )
+ {
+ nViewOffX = 0;
+ nViewOffY = 0;
+ nViewHeight = nDlgHeight;
+ }
+ else
+ {
+ nViewOffX = WIZARDDIALOG_VIEW_DLGOFFSET_X;
+ nViewOffY = WIZARDDIALOG_VIEW_DLGOFFSET_Y;
+ nViewHeight = nDlgHeight-(WIZARDDIALOG_VIEW_DLGOFFSET_Y*2);
+ }
+ nViewPosFlags |= PosSizeFlags::Height;
+ }
+ mpViewWindow->setPosSizePixel( nViewOffX, nViewOffY,
+ nViewWidth, nViewHeight,
+ nViewPosFlags );
+ }
+
+ tools::Long RoadmapWizard::LogicalCoordinateToPixel(int iCoordinate) const
+ {
+ Size aLocSize = LogicToPixel(Size(iCoordinate, 0), MapMode(MapUnit::MapAppFont));
+ int iPixelCoordinate = aLocSize.Width();
+ return iPixelCoordinate;
+ }
+
+ void RoadmapWizard::ImplPosTabPage()
+ {
+ if ( !mpCurTabPage )
+ return;
+
+ if ( !IsInInitShow() )
+ {
+ // #100199# - On Unix initial size is equal to screen size, on Windows
+ // it's 0,0. One cannot calculate the size unless dialog is visible.
+ if ( !IsReallyVisible() )
+ return;
+ }
+
+ // calculate height of ButtonBar
+ tools::Long nMaxHeight = 0;
+ ImplWizButtonData* pBtnData = mpFirstBtn;
+ while ( pBtnData )
+ {
+ tools::Long nBtnHeight = pBtnData->mpButton->GetSizePixel().Height();
+ if ( nBtnHeight > nMaxHeight )
+ nMaxHeight = nBtnHeight;
+ pBtnData = pBtnData->mpNext;
+ }
+ if ( nMaxHeight )
+ nMaxHeight += WIZARDDIALOG_BUTTON_OFFSET_Y*2;
+
+ // position TabPage
+ Size aDlgSize = GetOutputSizePixel();
+ aDlgSize.AdjustHeight( -nMaxHeight );
+ tools::Long nOffX = 0;
+ tools::Long nOffY = 0;
+ if ( mpViewWindow && mpViewWindow->IsVisible() )
+ {
+ Size aViewSize = mpViewWindow->GetSizePixel();
+ // align left
+ tools::Long nViewOffset = mbEmptyViewMargin ? 0 : WIZARDDIALOG_VIEW_DLGOFFSET_X;
+ nOffX += aViewSize.Width() + nViewOffset;
+ aDlgSize.AdjustWidth( -nOffX );
+ }
+ Point aPos( nOffX, nOffY );
+ mpCurTabPage->SetPosSizePixel( aPos, aDlgSize );
+ }
+
+ void RoadmapWizard::ImplShowTabPage( TabPage* pTabPage )
+ {
+ if ( mpCurTabPage == pTabPage )
+ return;
+
+ TabPage* pOldTabPage = mpCurTabPage;
+
+ mpCurTabPage = pTabPage;
+ if ( pTabPage )
+ {
+ ImplPosTabPage();
+ pTabPage->Show();
+ }
+
+ if ( pOldTabPage )
+ pOldTabPage->Hide();
+ }
+
+ TabPage* RoadmapWizard::ImplGetPage( sal_uInt16 nLevel ) const
+ {
+ sal_uInt16 nTempLevel = 0;
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData )
+ {
+ if ( (nTempLevel == nLevel) || !pPageData->mpNext )
+ break;
+
+ nTempLevel++;
+ pPageData = pPageData->mpNext;
+ }
+
+ if ( pPageData )
+ return pPageData->mpPage;
+ return nullptr;
+ }
+
+ void RoadmapWizard::AddButtonResponse( Button* pButton, int response)
+ {
+ m_xRoadmapImpl->maResponses[pButton] = response;
+ }
+
+ void RoadmapWizard::implConstruct( const WizardButtonFlags _nButtonFlags )
+ {
+ m_xWizardImpl->sTitleBase = GetText();
+
+ // create the buttons according to the wizard button flags
+ // the help button
+ if (_nButtonFlags & WizardButtonFlags::HELP)
+ {
+ m_pHelp= VclPtr<HelpButton>::Create(this, WB_TABSTOP);
+ m_pHelp->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pHelp->Show();
+ m_pHelp->set_id("help");
+ AddButtonResponse(m_pHelp, RET_HELP);
+ AddButton( m_pHelp, WIZARDDIALOG_BUTTON_STDOFFSET_X);
+ }
+
+ // the previous button
+ if (_nButtonFlags & WizardButtonFlags::PREVIOUS)
+ {
+ m_pPrevPage = VclPtr<PushButton>::Create(this, WB_TABSTOP);
+ m_pPrevPage->SetHelpId( HID_WIZARD_PREVIOUS );
+ m_pPrevPage->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pPrevPage->SetText(VclResId(STR_WIZDLG_PREVIOUS));
+ m_pPrevPage->Show();
+ m_pPrevPage->set_id("previous");
+
+ if (_nButtonFlags & WizardButtonFlags::NEXT)
+ AddButton( m_pPrevPage, ( WIZARDDIALOG_BUTTON_SMALLSTDOFFSET_X) ); // half x-offset to the next button
+ else
+ AddButton( m_pPrevPage, WIZARDDIALOG_BUTTON_STDOFFSET_X );
+ mpPrevBtn = m_pPrevPage;
+ m_pPrevPage->SetClickHdl( LINK( this, RoadmapWizard, OnPrevPage ) );
+ }
+
+ // the next button
+ if (_nButtonFlags & WizardButtonFlags::NEXT)
+ {
+ m_pNextPage = VclPtr<PushButton>::Create(this, WB_TABSTOP);
+ m_pNextPage->SetHelpId( HID_WIZARD_NEXT );
+ m_pNextPage->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pNextPage->SetText(VclResId(STR_WIZDLG_NEXT));
+ m_pNextPage->Show();
+ m_pNextPage->set_id("next");
+
+ AddButton( m_pNextPage, WIZARDDIALOG_BUTTON_STDOFFSET_X );
+ mpNextBtn = m_pNextPage;
+ m_pNextPage->SetClickHdl( LINK( this, RoadmapWizard, OnNextPage ) );
+ }
+
+ // the finish button
+ if (_nButtonFlags & WizardButtonFlags::FINISH)
+ {
+ m_pFinish = VclPtr<OKButton>::Create(this, WB_TABSTOP);
+ m_pFinish->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pFinish->SetText(VclResId(STR_WIZDLG_FINISH));
+ m_pFinish->Show();
+ m_pFinish->set_id("finish");
+
+ AddButton( m_pFinish, WIZARDDIALOG_BUTTON_STDOFFSET_X );
+ m_pFinish->SetClickHdl( LINK( this, RoadmapWizard, OnFinish ) );
+ }
+
+ // the cancel button
+ if (_nButtonFlags & WizardButtonFlags::CANCEL)
+ {
+ m_pCancel = VclPtr<CancelButton>::Create(this, WB_TABSTOP);
+ m_pCancel->SetSizePixel(LogicToPixel(Size(50, 14), MapMode(MapUnit::MapAppFont)));
+ m_pCancel->Show();
+
+ AddButton( m_pCancel, WIZARDDIALOG_BUTTON_STDOFFSET_X );
+ }
+ }
+
+ void RoadmapWizard::Resize()
+ {
+ if ( IsReallyShown() && !IsInInitShow() )
+ {
+ ImplPosCtrls();
+ ImplPosTabPage();
+ }
+
+ Dialog::Resize();
+ }
+
+ void RoadmapWizard::CalcAndSetSize()
+ {
+ Size aDlgSize = GetPageSizePixel();
+ if ( !aDlgSize.Width() || !aDlgSize.Height() )
+ {
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData )
+ {
+ if ( pPageData->mpPage )
+ {
+ Size aPageSize = pPageData->mpPage->GetSizePixel();
+ if ( aPageSize.Width() > aDlgSize.Width() )
+ aDlgSize.setWidth( aPageSize.Width() );
+ if ( aPageSize.Height() > aDlgSize.Height() )
+ aDlgSize.setHeight( aPageSize.Height() );
+ }
+
+ pPageData = pPageData->mpNext;
+ }
+ }
+ ImplCalcSize( aDlgSize );
+ SetMinOutputSizePixel( aDlgSize );
+ SetOutputSizePixel( aDlgSize );
+ }
+
+ void RoadmapWizard::StateChanged( StateChangedType nType )
+ {
+ if ( nType == StateChangedType::InitShow )
+ {
+ if ( IsDefaultSize() )
+ {
+ CalcAndSetSize();
+ }
+
+ ImplPosCtrls();
+ ImplPosTabPage();
+ ImplShowTabPage( ImplGetPage( mnCurLevel ) );
+ }
+
+ Dialog::StateChanged( nType );
+ }
+
+ bool RoadmapWizard::EventNotify( NotifyEvent& rNEvt )
+ {
+ if ( (rNEvt.GetType() == NotifyEventType::KEYINPUT) && mpPrevBtn && mpNextBtn )
+ {
+ const KeyEvent* pKEvt = rNEvt.GetKeyEvent();
+ vcl::KeyCode aKeyCode = pKEvt->GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+
+ if ( aKeyCode.IsMod1() )
+ {
+ if ( aKeyCode.IsShift() || (nKeyCode == KEY_PAGEUP) )
+ {
+ if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEUP) )
+ {
+ if ( mpPrevBtn->IsVisible() &&
+ mpPrevBtn->IsEnabled() && mpPrevBtn->IsInputEnabled() )
+ {
+ mpPrevBtn->SetPressed( true );
+ mpPrevBtn->SetPressed( false );
+ mpPrevBtn->Click();
+ }
+ return true;
+ }
+ }
+ else
+ {
+ if ( (nKeyCode == KEY_TAB) || (nKeyCode == KEY_PAGEDOWN) )
+ {
+ if ( mpNextBtn->IsVisible() &&
+ mpNextBtn->IsEnabled() && mpNextBtn->IsInputEnabled() )
+ {
+ mpNextBtn->SetPressed( true );
+ mpNextBtn->SetPressed( false );
+ mpNextBtn->Click();
+ }
+ return true;
+ }
+ }
+ }
+ }
+
+ return Dialog::EventNotify( rNEvt );
+ }
+
+ void RoadmapWizard::GetOrCreatePage( const WizardTypes::WizardState i_nState )
+ {
+ if ( nullptr != GetPage( i_nState ) )
+ return;
+
+ VclPtr<TabPage> pNewPage = createPage( i_nState );
+ DBG_ASSERT( pNewPage, "RoadmapWizard::GetOrCreatePage: invalid new page (NULL)!" );
+
+ // fill up the page sequence of our base class (with dummies)
+ while ( m_xWizardImpl->nFirstUnknownPage < i_nState )
+ {
+ AddPage( nullptr );
+ ++m_xWizardImpl->nFirstUnknownPage;
+ }
+
+ if ( m_xWizardImpl->nFirstUnknownPage == i_nState )
+ {
+ // encountered this page number the first time
+ AddPage( pNewPage );
+ ++m_xWizardImpl->nFirstUnknownPage;
+ }
+ else
+ // already had this page - just change it
+ SetPage( i_nState, pNewPage );
+ }
+
+ void RoadmapWizard::ActivatePage()
+ {
+ WizardTypes::WizardState nCurrentLevel = GetCurLevel();
+ GetOrCreatePage( nCurrentLevel );
+
+ enterState( nCurrentLevel );
+ }
+
+ bool RoadmapWizard::ShowPage( sal_uInt16 nLevel )
+ {
+ mnCurLevel = nLevel;
+ ActivatePage();
+ ImplShowTabPage( ImplGetPage( mnCurLevel ) );
+ return true;
+ }
+
+ void RoadmapWizard::Finish( tools::Long nResult )
+ {
+ if ( IsInExecute() )
+ EndDialog( nResult );
+ else if ( GetStyle() & WB_CLOSEABLE )
+ Close();
+ }
+
+ void RoadmapWizard::AddPage( TabPage* pPage )
+ {
+ ImplWizPageData* pNewPageData = new ImplWizPageData;
+ pNewPageData->mpNext = nullptr;
+ pNewPageData->mpPage = pPage;
+
+ if ( !mpFirstPage )
+ mpFirstPage = pNewPageData;
+ else
+ {
+ pPage->Hide();
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData->mpNext )
+ pPageData = pPageData->mpNext;
+ pPageData->mpNext = pNewPageData;
+ }
+ }
+
+ void RoadmapWizard::RemovePage( TabPage* pPage )
+ {
+ ImplWizPageData* pPrevPageData = nullptr;
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData )
+ {
+ if ( pPageData->mpPage == pPage )
+ {
+ if ( pPrevPageData )
+ pPrevPageData->mpNext = pPageData->mpNext;
+ else
+ mpFirstPage = pPageData->mpNext;
+ if ( pPage == mpCurTabPage )
+ mpCurTabPage = nullptr;
+ delete pPageData;
+ return;
+ }
+
+ pPrevPageData = pPageData;
+ pPageData = pPageData->mpNext;
+ }
+
+ OSL_FAIL( "RoadmapWizard::RemovePage() - Page not in list" );
+ }
+
+ void RoadmapWizard::SetPage( sal_uInt16 nLevel, TabPage* pPage )
+ {
+ sal_uInt16 nTempLevel = 0;
+ ImplWizPageData* pPageData = mpFirstPage;
+ while ( pPageData )
+ {
+ if ( (nTempLevel == nLevel) || !pPageData->mpNext )
+ break;
+
+ nTempLevel++;
+ pPageData = pPageData->mpNext;
+ }
+
+ if ( pPageData )
+ {
+ if ( pPageData->mpPage == mpCurTabPage )
+ mpCurTabPage = nullptr;
+ pPageData->mpPage = pPage;
+ }
+ }
+
+ TabPage* RoadmapWizard::GetPage( sal_uInt16 nLevel ) const
+ {
+ sal_uInt16 nTempLevel = 0;
+
+ for (ImplWizPageData* pPageData = mpFirstPage; pPageData;
+ pPageData = pPageData->mpNext)
+ {
+ if ( nTempLevel == nLevel )
+ return pPageData->mpPage;
+ nTempLevel++;
+ }
+
+ return nullptr;
+ }
+
+ void RoadmapWizard::AddButton( Button* pButton, tools::Long nOffset )
+ {
+ ImplWizButtonData* pNewBtnData = new ImplWizButtonData;
+ pNewBtnData->mpNext = nullptr;
+ pNewBtnData->mpButton = pButton;
+ pNewBtnData->mnOffset = nOffset;
+
+ if ( !mpFirstBtn )
+ mpFirstBtn = pNewBtnData;
+ else
+ {
+ ImplWizButtonData* pBtnData = mpFirstBtn;
+ while ( pBtnData->mpNext )
+ pBtnData = pBtnData->mpNext;
+ pBtnData->mpNext = pNewBtnData;
+ }
+ }
+
+ void RoadmapWizard::RemoveButton( Button* pButton )
+ {
+ ImplWizButtonData* pPrevBtnData = nullptr;
+ ImplWizButtonData* pBtnData = mpFirstBtn;
+ while ( pBtnData )
+ {
+ if ( pBtnData->mpButton == pButton )
+ {
+ if ( pPrevBtnData )
+ pPrevBtnData->mpNext = pBtnData->mpNext;
+ else
+ mpFirstBtn = pBtnData->mpNext;
+ delete pBtnData;
+ return;
+ }
+
+ pPrevBtnData = pBtnData;
+ pBtnData = pBtnData->mpNext;
+ }
+
+ OSL_FAIL( "RoadmapWizard::RemoveButton() - Button not in list" );
+ }
+
+ IMPL_LINK_NOARG(RoadmapWizard, OnFinish, Button*, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+ RoadmapWizardTravelSuspension aTravelGuard( *this );
+ Finish( RET_OK );
+ }
+
+ bool RoadmapWizard::skipBackwardUntil( WizardTypes::WizardState _nTargetState )
+ {
+ // don't travel directly on m_xWizardImpl->aStateHistory, in case something goes wrong
+ std::stack< WizardTypes::WizardState > aTravelVirtually = m_xWizardImpl->aStateHistory;
+ std::stack< WizardTypes::WizardState > aOldStateHistory = m_xWizardImpl->aStateHistory;
+
+ WizardTypes::WizardState nCurrentRollbackState = getCurrentState();
+ while ( nCurrentRollbackState != _nTargetState )
+ {
+ DBG_ASSERT( !aTravelVirtually.empty(), "RoadmapWizard::skipBackwardUntil: this target state does not exist in the history!" );
+ nCurrentRollbackState = aTravelVirtually.top();
+ aTravelVirtually.pop();
+ }
+ m_xWizardImpl->aStateHistory = aTravelVirtually;
+ if ( !ShowPage( _nTargetState ) )
+ {
+ m_xWizardImpl->aStateHistory = aOldStateHistory;
+ return false;
+ }
+ return true;
+ }
+
+ bool RoadmapWizard::skipUntil( WizardTypes::WizardState _nTargetState )
+ {
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+
+ // don't travel directly on m_xWizardImpl->aStateHistory, in case something goes wrong
+ std::stack< WizardTypes::WizardState > aTravelVirtually = m_xWizardImpl->aStateHistory;
+ std::stack< WizardTypes::WizardState > aOldStateHistory = m_xWizardImpl->aStateHistory;
+ while ( nCurrentState != _nTargetState )
+ {
+ WizardTypes::WizardState nNextState = determineNextState( nCurrentState );
+ if ( WZS_INVALID_STATE == nNextState )
+ {
+ OSL_FAIL( "RoadmapWizard::skipUntil: the given target state does not exist!" );
+ return false;
+ }
+
+ // remember the skipped state in the history
+ aTravelVirtually.push( nCurrentState );
+
+ // get the next state
+ nCurrentState = nNextState;
+ }
+ m_xWizardImpl->aStateHistory = aTravelVirtually;
+ // show the target page
+ if ( !ShowPage( nCurrentState ) )
+ {
+ // argh! prepareLeaveCurrentPage succeeded, determineNextState succeeded,
+ // but ShowPage doesn't? Somebody behaves very strange here...
+ OSL_FAIL( "RoadmapWizard::skipUntil: very unpolite..." );
+ m_xWizardImpl->aStateHistory = aOldStateHistory;
+ return false;
+ }
+ return true;
+ }
+
+ void RoadmapWizard::travelNext()
+ {
+ // determine the next state to travel to
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+ WizardTypes::WizardState nNextState = determineNextState(nCurrentState);
+ if (WZS_INVALID_STATE == nNextState)
+ return;
+
+ // the state history is used by the enterState method
+ // all fine
+ m_xWizardImpl->aStateHistory.push(nCurrentState);
+ if (!ShowPage(nNextState))
+ {
+ m_xWizardImpl->aStateHistory.pop();
+ }
+ }
+
+ void RoadmapWizard::travelPrevious()
+ {
+ DBG_ASSERT(!m_xWizardImpl->aStateHistory.empty(), "RoadmapWizard::travelPrevious: have no previous page!");
+
+ // the next state to switch to
+ WizardTypes::WizardState nPreviousState = m_xWizardImpl->aStateHistory.top();
+
+ // the state history is used by the enterState method
+ m_xWizardImpl->aStateHistory.pop();
+ // show this page
+ if (!ShowPage(nPreviousState))
+ {
+ m_xWizardImpl->aStateHistory.push(nPreviousState);
+ }
+
+ // all fine
+ }
+
+ void RoadmapWizard::removePageFromHistory( WizardTypes::WizardState nToRemove )
+ {
+
+ std::stack< WizardTypes::WizardState > aTemp;
+ while(!m_xWizardImpl->aStateHistory.empty())
+ {
+ WizardTypes::WizardState nPreviousState = m_xWizardImpl->aStateHistory.top();
+ m_xWizardImpl->aStateHistory.pop();
+ if(nPreviousState != nToRemove)
+ aTemp.push( nPreviousState );
+ else
+ break;
+ }
+ while(!aTemp.empty())
+ {
+ m_xWizardImpl->aStateHistory.push( aTemp.top() );
+ aTemp.pop();
+ }
+ }
+
+ IMPL_LINK_NOARG(RoadmapWizard, OnPrevPage, Button*, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+ RoadmapWizardTravelSuspension aTravelGuard( *this );
+ travelPrevious();
+ }
+
+ IMPL_LINK_NOARG(RoadmapWizard, OnNextPage, Button*, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+ RoadmapWizardTravelSuspension aTravelGuard( *this );
+ travelNext();
+ }
+
+ bool RoadmapWizard::isTravelingSuspended() const
+ {
+ return m_xWizardImpl->m_bTravelingSuspended;
+ }
+
+ void RoadmapWizard::suspendTraveling( AccessGuard )
+ {
+ DBG_ASSERT( !m_xWizardImpl->m_bTravelingSuspended, "RoadmapWizard::suspendTraveling: already suspended!" );
+ m_xWizardImpl->m_bTravelingSuspended = true;
+ }
+
+ void RoadmapWizard::resumeTraveling( AccessGuard )
+ {
+ DBG_ASSERT( m_xWizardImpl->m_bTravelingSuspended, "RoadmapWizard::resumeTraveling: nothing to resume!" );
+ m_xWizardImpl->m_bTravelingSuspended = false;
+ }
+
+ WizardMachine::WizardMachine(weld::Window* pParent, WizardButtonFlags nButtonFlags)
+ : AssistantController(pParent, "vcl/ui/wizard.ui", "Wizard")
+ , m_pCurTabPage(nullptr)
+ , m_nCurState(0)
+ , m_pFirstPage(nullptr)
+ , m_xFinish(m_xAssistant->weld_widget_for_response(RET_OK))
+ , m_xCancel(m_xAssistant->weld_widget_for_response(RET_CANCEL))
+ , m_xNextPage(m_xAssistant->weld_widget_for_response(RET_YES))
+ , m_xPrevPage(m_xAssistant->weld_widget_for_response(RET_NO))
+ , m_xHelp(m_xAssistant->weld_widget_for_response(RET_HELP))
+ , m_pImpl(new WizardMachineImplData)
+ {
+ implConstruct(nButtonFlags);
+ }
+
+ void WizardMachine::implConstruct(const WizardButtonFlags nButtonFlags)
+ {
+ m_pImpl->sTitleBase = m_xAssistant->get_title();
+
+ const bool bHideHelp = comphelper::LibreOfficeKit::isActive() &&
+ officecfg::Office::Common::Help::HelpRootURL::get().isEmpty();
+ // create the buttons according to the wizard button flags
+ // the help button
+ if (nButtonFlags & WizardButtonFlags::HELP && !bHideHelp)
+ m_xHelp->show();
+ else
+ m_xHelp->hide();
+
+ // the previous button
+ if (nButtonFlags & WizardButtonFlags::PREVIOUS)
+ {
+ m_xPrevPage->set_help_id( HID_WIZARD_PREVIOUS );
+ m_xPrevPage->show();
+
+ m_xPrevPage->connect_clicked( LINK( this, WizardMachine, OnPrevPage ) );
+ }
+ else
+ m_xPrevPage->hide();
+
+ // the next button
+ if (nButtonFlags & WizardButtonFlags::NEXT)
+ {
+ m_xNextPage->set_help_id( HID_WIZARD_NEXT );
+ m_xNextPage->show();
+
+ m_xNextPage->connect_clicked( LINK( this, WizardMachine, OnNextPage ) );
+ }
+ else
+ m_xNextPage->hide();
+
+ // the finish button
+ if (nButtonFlags & WizardButtonFlags::FINISH)
+ {
+ m_xFinish->show();
+
+ m_xFinish->connect_clicked( LINK( this, WizardMachine, OnFinish ) );
+ }
+ else
+ m_xFinish->hide();
+
+ // the cancel button
+ if (nButtonFlags & WizardButtonFlags::CANCEL)
+ {
+ m_xCancel->show();
+ m_xCancel->connect_clicked( LINK( this, WizardMachine, OnCancel ) );
+ }
+ else
+ m_xCancel->hide();
+ }
+
+ WizardMachine::~WizardMachine()
+ {
+ if (m_pImpl)
+ {
+ while (m_pFirstPage)
+ RemovePage(m_pFirstPage->mxPage.get());
+ m_pImpl.reset();
+ }
+ }
+
+ void WizardMachine::implUpdateTitle()
+ {
+ OUString sCompleteTitle(m_pImpl->sTitleBase);
+
+ // append the page title
+ BuilderPage* pCurrentPage = GetPage(getCurrentState());
+ if ( pCurrentPage && !pCurrentPage->GetPageTitle().isEmpty() )
+ {
+ sCompleteTitle += " - " + pCurrentPage->GetPageTitle();
+ }
+
+ m_xAssistant->set_title(sCompleteTitle);
+ }
+
+ void WizardMachine::setTitleBase(const OUString& _rTitleBase)
+ {
+ m_pImpl->sTitleBase = _rTitleBase;
+ implUpdateTitle();
+ }
+
+ OUString WizardMachine::getPageIdentForState(WizardTypes::WizardState nState) const
+ {
+ return OUString::number(nState);
+ }
+
+ WizardTypes::WizardState WizardMachine::getStateFromPageIdent(const OUString& rIdent) const
+ {
+ return rIdent.toInt32();
+ }
+
+ BuilderPage* WizardMachine::GetOrCreatePage( const WizardTypes::WizardState i_nState )
+ {
+ if ( nullptr == GetPage( i_nState ) )
+ {
+ std::unique_ptr<BuilderPage> xNewPage = createPage( i_nState );
+ DBG_ASSERT( xNewPage, "WizardMachine::GetOrCreatePage: invalid new page (NULL)!" );
+
+ // fill up the page sequence of our base class (with dummies)
+ while ( m_pImpl->nFirstUnknownPage < i_nState )
+ {
+ AddPage( nullptr );
+ ++m_pImpl->nFirstUnknownPage;
+ }
+
+ if ( m_pImpl->nFirstUnknownPage == i_nState )
+ {
+ // encountered this page number the first time
+ AddPage(std::move(xNewPage));
+ ++m_pImpl->nFirstUnknownPage;
+ }
+ else
+ // already had this page - just change it
+ SetPage(i_nState, std::move(xNewPage));
+ }
+ return GetPage( i_nState );
+ }
+
+ void WizardMachine::ActivatePage()
+ {
+ WizardTypes::WizardState nCurrentLevel = m_nCurState;
+ GetOrCreatePage( nCurrentLevel );
+
+ enterState( nCurrentLevel );
+ }
+
+ bool WizardMachine::DeactivatePage()
+ {
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+ return leaveState(nCurrentState);
+ }
+
+ void WizardMachine::defaultButton(WizardButtonFlags _nWizardButtonFlags)
+ {
+ // the new default button
+ weld::Button* pNewDefButton = nullptr;
+ if (_nWizardButtonFlags & WizardButtonFlags::FINISH)
+ pNewDefButton = m_xFinish.get();
+ if (_nWizardButtonFlags & WizardButtonFlags::NEXT)
+ pNewDefButton = m_xNextPage.get();
+ if (_nWizardButtonFlags & WizardButtonFlags::PREVIOUS)
+ pNewDefButton = m_xPrevPage.get();
+ if (_nWizardButtonFlags & WizardButtonFlags::HELP)
+ pNewDefButton = m_xHelp.get();
+ if (_nWizardButtonFlags & WizardButtonFlags::CANCEL)
+ pNewDefButton = m_xCancel.get();
+
+ defaultButton(pNewDefButton);
+ }
+
+ void WizardMachine::defaultButton(weld::Button* _pNewDefButton)
+ {
+ // loop through all (direct and indirect) descendants which participate in our tabbing order, and
+ // reset the WB_DEFBUTTON for every window which is a button and set _pNewDefButton as the new
+ // WB_DEFBUTTON
+ m_xAssistant->change_default_widget(nullptr, _pNewDefButton);
+ }
+
+ void WizardMachine::enableButtons(WizardButtonFlags _nWizardButtonFlags, bool _bEnable)
+ {
+ if (_nWizardButtonFlags & WizardButtonFlags::FINISH)
+ m_xFinish->set_sensitive(_bEnable);
+ if (_nWizardButtonFlags & WizardButtonFlags::NEXT)
+ m_xNextPage->set_sensitive(_bEnable);
+ if (_nWizardButtonFlags & WizardButtonFlags::PREVIOUS)
+ m_xPrevPage->set_sensitive(_bEnable);
+ if (_nWizardButtonFlags & WizardButtonFlags::HELP)
+ m_xHelp->set_sensitive(_bEnable);
+ if (_nWizardButtonFlags & WizardButtonFlags::CANCEL)
+ m_xCancel->set_sensitive(_bEnable);
+ }
+
+ void WizardMachine::enterState(WizardTypes::WizardState _nState)
+ {
+ // tell the page
+ IWizardPageController* pController = getPageController( GetPage( _nState ) );
+ OSL_ENSURE( pController, "WizardMachine::enterState: no controller for the given page!" );
+ if ( pController )
+ pController->initializePage();
+
+ if ( isAutomaticNextButtonStateEnabled() )
+ enableButtons( WizardButtonFlags::NEXT, canAdvance() );
+
+ enableButtons( WizardButtonFlags::PREVIOUS, !m_pImpl->aStateHistory.empty() );
+
+ // set the new title - it depends on the current page (i.e. state)
+ implUpdateTitle();
+ }
+
+ bool WizardMachine::leaveState(WizardTypes::WizardState)
+ {
+ // no need to ask the page here.
+ // If we reach this point, we already gave the current page the chance to commit it's data,
+ // and it was allowed to commit it's data
+
+ return true;
+ }
+
+ bool WizardMachine::onFinish()
+ {
+ return Finish(RET_OK);
+ }
+
+ IMPL_LINK_NOARG(WizardMachine, OnFinish, weld::Button&, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+
+ // prevent WizardTravelSuspension from using this instance
+ // after will be destructed due to onFinish and async response call
+ {
+ WizardTravelSuspension aTravelGuard( *this );
+ if (!prepareLeaveCurrentState(WizardTypes::eFinish))
+ {
+ return;
+ }
+ }
+
+ onFinish();
+ }
+
+ IMPL_LINK_NOARG(WizardMachine, OnCancel, weld::Button&, void)
+ {
+ m_xAssistant->response(RET_CANCEL);
+ }
+
+ WizardTypes::WizardState WizardMachine::determineNextState(WizardTypes::WizardState _nCurrentState ) const
+ {
+ return _nCurrentState + 1;
+ }
+
+ bool WizardMachine::prepareLeaveCurrentState( WizardTypes::CommitPageReason _eReason )
+ {
+ IWizardPageController* pController = getPageController( GetPage( getCurrentState() ) );
+ ENSURE_OR_RETURN( pController != nullptr, "WizardMachine::prepareLeaveCurrentState: no controller for the current page!", true );
+ return pController->commitPage( _eReason );
+ }
+
+ bool WizardMachine::skipBackwardUntil(WizardTypes::WizardState _nTargetState)
+ {
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( WizardTypes::eTravelBackward ) )
+ return false;
+
+ // don't travel directly on m_pImpl->aStateHistory, in case something goes wrong
+ std::stack< WizardTypes::WizardState > aTravelVirtually = m_pImpl->aStateHistory;
+ std::stack< WizardTypes::WizardState > aOldStateHistory = m_pImpl->aStateHistory;
+
+ WizardTypes::WizardState nCurrentRollbackState = getCurrentState();
+ while ( nCurrentRollbackState != _nTargetState )
+ {
+ DBG_ASSERT( !aTravelVirtually.empty(), "WizardMachine::skipBackwardUntil: this target state does not exist in the history!" );
+ nCurrentRollbackState = aTravelVirtually.top();
+ aTravelVirtually.pop();
+ }
+ m_pImpl->aStateHistory = aTravelVirtually;
+ if ( !ShowPage( _nTargetState ) )
+ {
+ m_pImpl->aStateHistory = aOldStateHistory;
+ return false;
+ }
+ return true;
+ }
+
+ bool WizardMachine::skipUntil( WizardTypes::WizardState _nTargetState )
+ {
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( nCurrentState < _nTargetState ? WizardTypes::eTravelForward : WizardTypes::eTravelBackward ) )
+ return false;
+
+ // don't travel directly on m_pImpl->aStateHistory, in case something goes wrong
+ std::stack< WizardTypes::WizardState > aTravelVirtually = m_pImpl->aStateHistory;
+ std::stack< WizardTypes::WizardState > aOldStateHistory = m_pImpl->aStateHistory;
+ while ( nCurrentState != _nTargetState )
+ {
+ WizardTypes::WizardState nNextState = determineNextState( nCurrentState );
+ if ( WZS_INVALID_STATE == nNextState )
+ {
+ OSL_FAIL( "WizardMachine::skipUntil: the given target state does not exist!" );
+ return false;
+ }
+
+ // remember the skipped state in the history
+ aTravelVirtually.push( nCurrentState );
+
+ // get the next state
+ nCurrentState = nNextState;
+ }
+ m_pImpl->aStateHistory = aTravelVirtually;
+ // show the target page
+ if ( !ShowPage( nCurrentState ) )
+ {
+ // argh! prepareLeaveCurrentPage succeeded, determineNextState succeeded,
+ // but ShowPage doesn't? Somebody behaves very strange here...
+ OSL_FAIL( "WizardMachine::skipUntil: very unpolite..." );
+ m_pImpl->aStateHistory = aOldStateHistory;
+ return false;
+ }
+ return true;
+ }
+
+ void WizardMachine::skip()
+ {
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( WizardTypes::eTravelForward ) )
+ return;
+
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+ WizardTypes::WizardState nNextState = determineNextState(nCurrentState);
+
+ if (WZS_INVALID_STATE == nNextState)
+ return;
+
+ // remember the skipped state in the history
+ m_pImpl->aStateHistory.push(nCurrentState);
+
+ // get the next state
+ nCurrentState = nNextState;
+
+ // show the (n+1)th page
+ if (!ShowPage(nCurrentState))
+ {
+ // TODO: this leaves us in a state where we have no current page and an inconsistent state history.
+ // Perhaps we should rollback the skipping here...
+ OSL_FAIL("RoadmapWizard::skip: very unpolite...");
+ // if somebody does a skip and then does not allow to leave...
+ // (can't be a commit error, as we've already committed the current page. So if ShowPage fails here,
+ // somebody behaves really strange ...)
+ return;
+ }
+
+ // all fine
+ }
+
+ bool WizardMachine::travelNext()
+ {
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( WizardTypes::eTravelForward ) )
+ return false;
+
+ // determine the next state to travel to
+ WizardTypes::WizardState nCurrentState = getCurrentState();
+ WizardTypes::WizardState nNextState = determineNextState(nCurrentState);
+ if (WZS_INVALID_STATE == nNextState)
+ return false;
+
+ // the state history is used by the enterState method
+ // all fine
+ m_pImpl->aStateHistory.push(nCurrentState);
+ if (!ShowPage(nNextState))
+ {
+ m_pImpl->aStateHistory.pop();
+ return false;
+ }
+
+ return true;
+ }
+
+ bool WizardMachine::ShowPage(WizardTypes::WizardState nState)
+ {
+ if (DeactivatePage())
+ {
+ BuilderPage* pOldTabPage = m_pCurTabPage;
+
+ m_nCurState = nState;
+ ActivatePage();
+
+ if (pOldTabPage)
+ pOldTabPage->Deactivate();
+
+ m_xAssistant->set_current_page(getPageIdentForState(nState));
+
+ m_pCurTabPage = GetPage(m_nCurState);
+ m_pCurTabPage->Activate();
+
+ return true;
+ }
+ return false;
+ }
+
+ bool WizardMachine::ShowNextPage()
+ {
+ return ShowPage(m_nCurState + 1);
+ }
+
+ bool WizardMachine::ShowPrevPage()
+ {
+ if (!m_nCurState)
+ return false;
+ return ShowPage(m_nCurState - 1);
+ }
+
+ bool WizardMachine::travelPrevious()
+ {
+ DBG_ASSERT(!m_pImpl->aStateHistory.empty(), "WizardMachine::travelPrevious: have no previous page!");
+
+ // allowed to leave the current page?
+ if ( !prepareLeaveCurrentState( WizardTypes::eTravelBackward ) )
+ return false;
+
+ // the next state to switch to
+ WizardTypes::WizardState nPreviousState = m_pImpl->aStateHistory.top();
+
+ // the state history is used by the enterState method
+ m_pImpl->aStateHistory.pop();
+ // show this page
+ if (!ShowPage(nPreviousState))
+ {
+ m_pImpl->aStateHistory.push(nPreviousState);
+ return false;
+ }
+
+ // all fine
+ return true;
+ }
+
+
+ void WizardMachine::removePageFromHistory( WizardTypes::WizardState nToRemove )
+ {
+
+ std::stack< WizardTypes::WizardState > aTemp;
+ while(!m_pImpl->aStateHistory.empty())
+ {
+ WizardTypes::WizardState nPreviousState = m_pImpl->aStateHistory.top();
+ m_pImpl->aStateHistory.pop();
+ if(nPreviousState != nToRemove)
+ aTemp.push( nPreviousState );
+ else
+ break;
+ }
+ while(!aTemp.empty())
+ {
+ m_pImpl->aStateHistory.push( aTemp.top() );
+ aTemp.pop();
+ }
+ }
+
+
+ void WizardMachine::enableAutomaticNextButtonState()
+ {
+ m_pImpl->m_bAutoNextButtonState = true;
+ }
+
+
+ bool WizardMachine::isAutomaticNextButtonStateEnabled() const
+ {
+ return m_pImpl->m_bAutoNextButtonState;
+ }
+
+ IMPL_LINK_NOARG(WizardMachine, OnPrevPage, weld::Button&, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+ WizardTravelSuspension aTravelGuard( *this );
+ travelPrevious();
+ }
+
+ IMPL_LINK_NOARG(WizardMachine, OnNextPage, weld::Button&, void)
+ {
+ if ( isTravelingSuspended() )
+ return;
+ WizardTravelSuspension aTravelGuard( *this );
+ travelNext();
+ }
+
+ IWizardPageController* WizardMachine::getPageController(BuilderPage* pCurrentPage) const
+ {
+ IWizardPageController* pController = dynamic_cast<IWizardPageController*>(pCurrentPage);
+ return pController;
+ }
+
+ void WizardMachine::getStateHistory( std::vector< WizardTypes::WizardState >& _out_rHistory )
+ {
+ std::stack< WizardTypes::WizardState > aHistoryCopy( m_pImpl->aStateHistory );
+ while ( !aHistoryCopy.empty() )
+ {
+ _out_rHistory.push_back( aHistoryCopy.top() );
+ aHistoryCopy.pop();
+ }
+ }
+
+ bool WizardMachine::canAdvance() const
+ {
+ return WZS_INVALID_STATE != determineNextState( getCurrentState() );
+ }
+
+ void WizardMachine::updateTravelUI()
+ {
+ const IWizardPageController* pController = getPageController( GetPage( getCurrentState() ) );
+ OSL_ENSURE( pController != nullptr, "RoadmapWizard::updateTravelUI: no controller for the current page!" );
+
+ bool bCanAdvance =
+ ( !pController || pController->canAdvance() ) // the current page allows to advance
+ && canAdvance(); // the dialog as a whole allows to advance
+ enableButtons( WizardButtonFlags::NEXT, bCanAdvance );
+ }
+
+ bool WizardMachine::isTravelingSuspended() const
+ {
+ return m_pImpl->m_bTravelingSuspended;
+ }
+
+ void WizardMachine::suspendTraveling( AccessGuard )
+ {
+ DBG_ASSERT( !m_pImpl->m_bTravelingSuspended, "WizardMachine::suspendTraveling: already suspended!" );
+ m_pImpl->m_bTravelingSuspended = true;
+ }
+
+ void WizardMachine::resumeTraveling( AccessGuard )
+ {
+ if (!m_pImpl)
+ return;
+
+ DBG_ASSERT( m_pImpl->m_bTravelingSuspended, "WizardMachine::resumeTraveling: nothing to resume!" );
+ m_pImpl->m_bTravelingSuspended = false;
+ }
+
+ bool WizardMachine::Finish(short nResult)
+ {
+ if ( DeactivatePage() )
+ {
+ if (m_pCurTabPage)
+ m_pCurTabPage->Deactivate();
+
+ m_xAssistant->response(nResult);
+ return true;
+ }
+ else
+ return false;
+ }
+
+ void WizardMachine::AddPage(std::unique_ptr<BuilderPage> xPage)
+ {
+ WizPageData* pNewPageData = new WizPageData;
+ pNewPageData->mpNext = nullptr;
+ pNewPageData->mxPage = std::move(xPage);
+
+ if ( !m_pFirstPage )
+ m_pFirstPage = pNewPageData;
+ else
+ {
+ WizPageData* pPageData = m_pFirstPage;
+ while ( pPageData->mpNext )
+ pPageData = pPageData->mpNext;
+ pPageData->mpNext = pNewPageData;
+ }
+ }
+
+ void WizardMachine::RemovePage(const BuilderPage* pPage)
+ {
+ WizPageData* pPrevPageData = nullptr;
+ WizPageData* pPageData = m_pFirstPage;
+ while ( pPageData )
+ {
+ if (pPageData->mxPage.get() == pPage)
+ {
+ if (pPrevPageData)
+ pPrevPageData->mpNext = pPageData->mpNext;
+ else
+ m_pFirstPage = pPageData->mpNext;
+ if (pPage == m_pCurTabPage)
+ m_pCurTabPage = nullptr;
+ delete pPageData;
+ return;
+ }
+
+ pPrevPageData = pPageData;
+ pPageData = pPageData->mpNext;
+ }
+
+ OSL_FAIL( "WizardMachine::RemovePage() - Page not in list" );
+ }
+
+ void WizardMachine::SetPage(WizardTypes::WizardState nLevel, std::unique_ptr<BuilderPage> xPage)
+ {
+ sal_uInt16 nTempLevel = 0;
+ WizPageData* pPageData = m_pFirstPage;
+ while ( pPageData )
+ {
+ if ( (nTempLevel == nLevel) || !pPageData->mpNext )
+ break;
+
+ nTempLevel++;
+ pPageData = pPageData->mpNext;
+ }
+
+ if ( pPageData )
+ {
+ if (pPageData->mxPage.get() == m_pCurTabPage)
+ m_pCurTabPage = nullptr;
+ pPageData->mxPage = std::move(xPage);
+ }
+ }
+
+ BuilderPage* WizardMachine::GetPage(WizardTypes::WizardState nLevel) const
+ {
+ sal_uInt16 nTempLevel = 0;
+
+ for (WizPageData* pPageData = m_pFirstPage; pPageData;
+ pPageData = pPageData->mpNext)
+ {
+ if ( nTempLevel == nLevel )
+ return pPageData->mxPage.get();
+ nTempLevel++;
+ }
+
+ return nullptr;
+ }
+} // namespace svt
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/control/wizimpldata.hxx b/vcl/source/control/wizimpldata.hxx
new file mode 100644
index 0000000000..9502e8ba4e
--- /dev/null
+++ b/vcl/source/control/wizimpldata.hxx
@@ -0,0 +1,108 @@
+/* -*- 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 <stack>
+#include <map>
+#include <set>
+#include <vcl/toolkit/roadmap.hxx>
+
+struct WizPageData
+{
+ WizPageData* mpNext;
+ std::unique_ptr<BuilderPage> mxPage;
+};
+
+struct ImplWizButtonData
+{
+ ImplWizButtonData* mpNext;
+ VclPtr<Button> mpButton;
+ tools::Long mnOffset;
+};
+
+namespace vcl
+{
+ struct WizardMachineImplData
+ {
+ OUString sTitleBase; // the base for the title
+ std::stack<WizardTypes::WizardState> aStateHistory; // the history of all states (used for implementing "Back")
+
+ WizardTypes::WizardState nFirstUnknownPage;
+ // the WizardDialog does not allow non-linear transitions (e.g. it's
+ // not possible to add pages in a non-linear order), so we need some own maintenance data
+
+ bool m_bAutoNextButtonState;
+
+ bool m_bTravelingSuspended;
+
+ WizardMachineImplData()
+ :nFirstUnknownPage( 0 )
+ ,m_bAutoNextButtonState( false )
+ ,m_bTravelingSuspended( false )
+ {
+ }
+ };
+
+ using namespace RoadmapWizardTypes;
+ namespace
+ {
+ typedef ::std::set< WizardTypes::WizardState > StateSet;
+
+ typedef ::std::map<
+ PathId,
+ WizardPath
+ > Paths;
+
+ typedef ::std::map<
+ WizardTypes::WizardState,
+ ::std::pair<
+ OUString,
+ RoadmapPageFactory
+ >
+ > StateDescriptions;
+ }
+
+ struct RoadmapWizardImpl
+ {
+ ScopedVclPtr<ORoadmap> pRoadmap;
+ std::map<VclPtr<vcl::Window>, short> maResponses;
+ Paths aPaths;
+ PathId nActivePath;
+ StateDescriptions aStateDescriptors;
+ StateSet aDisabledStates;
+ bool bActivePathIsDefinite;
+
+ RoadmapWizardImpl()
+ :pRoadmap( nullptr )
+ ,nActivePath( -1 )
+ ,bActivePathIsDefinite( false )
+ {
+ }
+
+ /// returns the index of the current state in given path, or -1
+ static sal_Int32 getStateIndexInPath( WizardTypes::WizardState _nState, const WizardPath& _rPath );
+ /// returns the index of the current state in the path with the given id, or -1
+ sal_Int32 getStateIndexInPath( WizardTypes::WizardState _nState, PathId _nPathId );
+ /// returns the index of the first state in which the two given paths differ
+ static sal_Int32 getFirstDifferentIndex( const WizardPath& _rLHS, const WizardPath& _rRHS );
+ };
+} // namespace svt
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */