In the past, weld.hxx used to contain the class definitions for weld::Widget and all subclasses. By now, weld::Widget and almost all subclasses have been moved to separate headers, to make it easier to keep the overview and to avoid having to recompile ~all UI code when one class changes. By now, only weld::Button is left in weld.hxx. Rename the header to Button.hxx. Drop weld.hxx includes and instead add Button.hxx includes (or weld::Button forward declarations) where weld::Button is used. (Don't simply replace all weld.hxx includes with Button.hxx ones, since many of them were for the use of other widgets that have since been moved to their own headers.) Add further includes where needed now that the (direct or transitive) includes of weld.hxx were removed. Change-Id: I8ea6201446a418d5e38b46337059dcd517ee29d7 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/205937 Reviewed-by: Michael Weghorn <m.weghorn@posteo.de> Tested-by: Jenkins
1652 lines
58 KiB
C++
1652 lines
58 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.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 "propcontroller.hxx"
|
|
#include "handlerhelper.hxx"
|
|
#include "standardcontrol.hxx"
|
|
#include "linedescriptor.hxx"
|
|
#include <strings.hrc>
|
|
#include "propertyeditor.hxx"
|
|
#include "modulepcr.hxx"
|
|
#include "formstrings.hxx"
|
|
#include "formbrowsertools.hxx"
|
|
#include "propertycomposer.hxx"
|
|
|
|
#include <com/sun/star/awt/XWindow.hpp>
|
|
#include <com/sun/star/beans/XPropertySet.hpp>
|
|
#include <com/sun/star/lang/NoSupportException.hpp>
|
|
#include <com/sun/star/inspection/PropertyControlType.hpp>
|
|
#include <com/sun/star/ucb/AlreadyInitializedException.hpp>
|
|
#include <com/sun/star/lang/XSingleComponentFactory.hpp>
|
|
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
|
|
#include <com/sun/star/util/VetoException.hpp>
|
|
#include <tools/debug.hxx>
|
|
#include <tools/fldunit.hxx>
|
|
#include <comphelper/diagnose_ex.hxx>
|
|
#include <toolkit/helper/vclunohelper.hxx>
|
|
#include <vcl/svapp.hxx>
|
|
#include <vcl/weld/MessageDialog.hxx>
|
|
#include <vcl/weld/weldutils.hxx>
|
|
#include <osl/mutex.hxx>
|
|
#include <cppuhelper/queryinterface.hxx>
|
|
#include <cppuhelper/supportsservice.hxx>
|
|
|
|
#include <algorithm>
|
|
#include <sal/log.hxx>
|
|
|
|
namespace pcr
|
|
{
|
|
using namespace ::com::sun::star;
|
|
using namespace ::com::sun::star::uno;
|
|
using namespace ::com::sun::star::awt;
|
|
using namespace ::com::sun::star::beans;
|
|
using namespace ::com::sun::star::lang;
|
|
using namespace ::com::sun::star::frame;
|
|
using namespace ::com::sun::star::util;
|
|
using namespace ::com::sun::star::inspection;
|
|
using namespace ::com::sun::star::ucb;
|
|
using namespace ::comphelper;
|
|
|
|
//= OPropertyBrowserController
|
|
OPropertyBrowserController::OPropertyBrowserController(
|
|
const Reference<XComponentContext>& _rxContext)
|
|
: m_xContext(_rxContext)
|
|
, m_aDisposeListeners(m_aMutex)
|
|
, m_aControlObservers(m_aMutex)
|
|
, m_bContainerFocusListening(false)
|
|
, m_bSuspendingPropertyHandlers(false)
|
|
, m_bConstructed(false)
|
|
, m_bBindingIntrospectee(false)
|
|
{
|
|
}
|
|
|
|
OPropertyBrowserController::~OPropertyBrowserController()
|
|
{
|
|
// stop listening for property changes
|
|
acquire();
|
|
stopInspection(true);
|
|
}
|
|
|
|
IMPLEMENT_FORWARD_REFCOUNT(OPropertyBrowserController, OPropertyBrowserController_Base)
|
|
|
|
Any SAL_CALL OPropertyBrowserController::queryInterface(const Type& _rType)
|
|
{
|
|
Any aReturn = OPropertyBrowserController_Base::queryInterface(_rType);
|
|
if (!aReturn.hasValue())
|
|
aReturn = ::cppu::queryInterface(_rType, static_cast<XObjectInspectorUI*>(this));
|
|
return aReturn;
|
|
}
|
|
|
|
void OPropertyBrowserController::startContainerWindowListening()
|
|
{
|
|
if (m_bContainerFocusListening)
|
|
return;
|
|
|
|
if (m_xFrame.is())
|
|
{
|
|
Reference<XWindow> xContainerWindow = m_xFrame->getContainerWindow();
|
|
if (xContainerWindow.is())
|
|
{
|
|
xContainerWindow->addFocusListener(this);
|
|
m_bContainerFocusListening = true;
|
|
}
|
|
}
|
|
|
|
DBG_ASSERT(m_bContainerFocusListening, "OPropertyBrowserController::"
|
|
"startContainerWindowListening: unable to start "
|
|
"listening (inconsistence)!");
|
|
}
|
|
|
|
void OPropertyBrowserController::stopContainerWindowListening()
|
|
{
|
|
if (!m_bContainerFocusListening)
|
|
return;
|
|
|
|
if (m_xFrame.is())
|
|
{
|
|
Reference<XWindow> xContainerWindow = m_xFrame->getContainerWindow();
|
|
if (xContainerWindow.is())
|
|
{
|
|
xContainerWindow->removeFocusListener(this);
|
|
m_bContainerFocusListening = false;
|
|
}
|
|
}
|
|
|
|
DBG_ASSERT(!m_bContainerFocusListening, "OPropertyBrowserController::"
|
|
"stopContainerWindowListening: unable to stop "
|
|
"listening (inconsistence)!");
|
|
}
|
|
|
|
Reference<XObjectInspectorModel> SAL_CALL OPropertyBrowserController::getInspectorModel()
|
|
{
|
|
return m_xModel;
|
|
}
|
|
|
|
void OPropertyBrowserController::impl_initializeView_nothrow()
|
|
{
|
|
OSL_PRECOND(haveView(), "OPropertyBrowserController::impl_initializeView_nothrow: not to be "
|
|
"called when we have no view!");
|
|
if (!haveView())
|
|
return;
|
|
|
|
if (!m_xModel.is())
|
|
// allowed
|
|
return;
|
|
|
|
try
|
|
{
|
|
getPropertyBox().EnableHelpSection(m_xModel->getHasHelpSection());
|
|
}
|
|
catch (const Exception&)
|
|
{
|
|
DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
|
|
}
|
|
}
|
|
|
|
bool OPropertyBrowserController::impl_isReadOnlyModel_throw() const
|
|
{
|
|
if (!m_xModel.is())
|
|
return false;
|
|
|
|
return m_xModel->getIsReadOnly();
|
|
}
|
|
|
|
void OPropertyBrowserController::impl_startOrStopModelListening_nothrow(bool _bDoListen) const
|
|
{
|
|
try
|
|
{
|
|
Reference<XPropertySet> xModelProperties(m_xModel, UNO_QUERY);
|
|
if (!xModelProperties.is())
|
|
// okay, so the model doesn't want to change its properties
|
|
// dynamically - fine with us
|
|
return;
|
|
|
|
void (SAL_CALL XPropertySet::*pListenerOperation)(const OUString&,
|
|
const Reference<XPropertyChangeListener>&)
|
|
= _bDoListen ? &XPropertySet::addPropertyChangeListener
|
|
: &XPropertySet::removePropertyChangeListener;
|
|
|
|
(xModelProperties.get()->*pListenerOperation)(
|
|
u"IsReadOnly"_ustr, const_cast<OPropertyBrowserController*>(this));
|
|
}
|
|
catch (const Exception&)
|
|
{
|
|
DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
|
|
}
|
|
}
|
|
|
|
void OPropertyBrowserController::impl_bindToNewModel_nothrow(
|
|
const Reference<XObjectInspectorModel>& _rxInspectorModel)
|
|
{
|
|
impl_startOrStopModelListening_nothrow(false);
|
|
m_xModel = _rxInspectorModel;
|
|
impl_startOrStopModelListening_nothrow(true);
|
|
|
|
// initialize the view, if we already have one
|
|
if (haveView())
|
|
impl_initializeView_nothrow();
|
|
|
|
// inspect again, if we already have inspectees
|
|
if (!m_aInspectedObjects.empty())
|
|
impl_rebindToInspectee_nothrow(std::vector(m_aInspectedObjects));
|
|
}
|
|
|
|
void SAL_CALL OPropertyBrowserController::setInspectorModel(
|
|
const Reference<XObjectInspectorModel>& _inspectorModel)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
if (m_xModel == _inspectorModel)
|
|
return;
|
|
|
|
impl_bindToNewModel_nothrow(_inspectorModel);
|
|
}
|
|
|
|
Reference<XObjectInspectorUI> SAL_CALL OPropertyBrowserController::getInspectorUI()
|
|
{
|
|
// we're derived from this interface, though we do not expose it in queryInterface and getTypes.
|
|
return this;
|
|
}
|
|
|
|
void SAL_CALL OPropertyBrowserController::inspect(const Sequence<Reference<XInterface>>& _rObjects)
|
|
{
|
|
SolarMutexGuard aSolarGuard;
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
if (m_bSuspendingPropertyHandlers || !suspendAll_nothrow())
|
|
{
|
|
// we already are trying to suspend the component (this is somewhere up the stack)
|
|
// OR one of our property handlers raised a veto against closing. Well, we *need* to close
|
|
// it in order to inspect another object.
|
|
throw VetoException();
|
|
}
|
|
if (m_bBindingIntrospectee)
|
|
throw VetoException();
|
|
|
|
m_bBindingIntrospectee = true;
|
|
impl_rebindToInspectee_nothrow(InterfaceArray(_rObjects.begin(), _rObjects.end()));
|
|
m_bBindingIntrospectee = false;
|
|
}
|
|
|
|
Reference<XDispatch> SAL_CALL OPropertyBrowserController::queryDispatch(
|
|
const URL& /*URL*/, const OUString& /*TargetFrameName*/, ::sal_Int32 /*SearchFlags*/)
|
|
{
|
|
// we don't have any dispatches at all, right now
|
|
return Reference<XDispatch>();
|
|
}
|
|
|
|
Sequence<Reference<XDispatch>> SAL_CALL
|
|
OPropertyBrowserController::queryDispatches(const Sequence<DispatchDescriptor>& Requests)
|
|
{
|
|
Sequence<Reference<XDispatch>> aReturn(Requests.getLength());
|
|
std::transform(Requests.begin(), Requests.end(), aReturn.getArray(),
|
|
[this](const DispatchDescriptor& d) {
|
|
return queryDispatch(d.FeatureURL, d.FrameName, d.SearchFlags);
|
|
});
|
|
return aReturn;
|
|
}
|
|
|
|
void SAL_CALL OPropertyBrowserController::initialize(const Sequence<Any>& _arguments)
|
|
{
|
|
if (m_bConstructed)
|
|
throw AlreadyInitializedException();
|
|
|
|
if (!_arguments.hasElements())
|
|
{
|
|
// constructor: "createDefault()"
|
|
m_bConstructed = true;
|
|
return;
|
|
}
|
|
|
|
Reference<XObjectInspectorModel> xModel;
|
|
if (_arguments.size() == 1)
|
|
{
|
|
// constructor: "createWithModel( XObjectInspectorModel )"
|
|
if (!(_arguments[0] >>= xModel))
|
|
throw IllegalArgumentException(OUString(), *this, 0);
|
|
createWithModel(xModel);
|
|
return;
|
|
}
|
|
|
|
throw IllegalArgumentException(OUString(), *this, 0);
|
|
}
|
|
|
|
void OPropertyBrowserController::createWithModel(const Reference<XObjectInspectorModel>& _rxModel)
|
|
{
|
|
osl_atomic_increment(&m_refCount);
|
|
{
|
|
setInspectorModel(_rxModel);
|
|
}
|
|
osl_atomic_decrement(&m_refCount);
|
|
|
|
m_bConstructed = true;
|
|
}
|
|
|
|
void SAL_CALL OPropertyBrowserController::attachFrame(const Reference<XFrame>& _rxFrame)
|
|
{
|
|
SolarMutexGuard aSolarGuard;
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
if (_rxFrame.is() && haveView())
|
|
throw RuntimeException(u"Unable to attach to a second frame."_ustr, *this);
|
|
|
|
// revoke as focus listener from the old container window
|
|
stopContainerWindowListening();
|
|
|
|
m_xPropView.reset();
|
|
m_xBuilder.reset();
|
|
|
|
m_xFrame = _rxFrame;
|
|
if (!m_xFrame.is())
|
|
return;
|
|
|
|
// TODO: this construction perhaps should be done outside. Don't know the exact meaning of attachFrame.
|
|
// Maybe it is intended to only announce the frame to the controller, and the instance doing this
|
|
// announcement is responsible for calling setComponent, too.
|
|
Reference<XWindow> xContainerWindow = m_xFrame->getContainerWindow();
|
|
|
|
OUString sUIFile(u"modules/spropctrlr/ui/formproperties.ui"_ustr);
|
|
std::unique_ptr<weld::Builder> xBuilder;
|
|
|
|
if (weld::TransportAsXWindow* pTunnel
|
|
= dynamic_cast<weld::TransportAsXWindow*>(xContainerWindow.get()))
|
|
{
|
|
xBuilder = Application::CreateBuilder(pTunnel->getWidget(), sUIFile);
|
|
}
|
|
else
|
|
{
|
|
VclPtr<vcl::Window> pParentWin = VCLUnoHelper::GetWindow(xContainerWindow);
|
|
if (!pParentWin)
|
|
throw RuntimeException(
|
|
u"The frame is invalid. Unable to extract the container window."_ustr, *this);
|
|
xBuilder = Application::CreateInterimBuilder(pParentWin, sUIFile, true);
|
|
}
|
|
|
|
Construct(xContainerWindow, std::move(xBuilder));
|
|
|
|
startContainerWindowListening();
|
|
|
|
UpdateUI();
|
|
}
|
|
|
|
sal_Bool SAL_CALL OPropertyBrowserController::attachModel(const Reference<XModel>& _rxModel)
|
|
{
|
|
Reference<XObjectInspectorModel> xModel(_rxModel, UNO_QUERY);
|
|
if (!xModel.is())
|
|
return false;
|
|
|
|
setInspectorModel(xModel);
|
|
return getInspectorModel() == _rxModel;
|
|
}
|
|
|
|
bool OPropertyBrowserController::suspendAll_nothrow()
|
|
{
|
|
// if there is a handle inside its "onInteractivePropertySelection" method,
|
|
// then veto
|
|
// Normally, we could expect every handler to do this itself, but being
|
|
// realistic, it's safer to handle this here in general.
|
|
if (m_xInteractiveHandler.is())
|
|
return false;
|
|
|
|
m_bSuspendingPropertyHandlers = true;
|
|
bool bHandlerVeto = !suspendPropertyHandlers_nothrow(true);
|
|
m_bSuspendingPropertyHandlers = false;
|
|
return !bHandlerVeto;
|
|
}
|
|
|
|
bool OPropertyBrowserController::suspendPropertyHandlers_nothrow(bool _bSuspend)
|
|
{
|
|
PropertyHandlerArray aAllHandlers; // will contain every handler exactly once
|
|
for (auto const& propertyHandler : m_aPropertyHandlers)
|
|
{
|
|
if (std::find(aAllHandlers.begin(), aAllHandlers.end(), propertyHandler.second)
|
|
!= aAllHandlers.end())
|
|
// already visited this particular handler (m_aPropertyHandlers usually contains
|
|
// the same handler more than once)
|
|
continue;
|
|
aAllHandlers.push_back(propertyHandler.second);
|
|
}
|
|
|
|
for (auto const& handler : aAllHandlers)
|
|
{
|
|
try
|
|
{
|
|
if (!handler->suspend(_bSuspend))
|
|
if (_bSuspend)
|
|
// if we're not suspending, but reactivating, ignore the error
|
|
return false;
|
|
}
|
|
catch (const Exception&)
|
|
{
|
|
TOOLS_WARN_EXCEPTION("extensions.propctrlr",
|
|
"OPropertyBrowserController::suspendPropertyHandlers_nothrow");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
sal_Bool SAL_CALL OPropertyBrowserController::suspend(sal_Bool _bSuspend)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
OSL_ENSURE(haveView(), "OPropertyBrowserController::suspend: don't have a view anymore!");
|
|
|
|
if (!_bSuspend)
|
|
{
|
|
// this means a "suspend" is to be "revoked"
|
|
suspendPropertyHandlers_nothrow(false);
|
|
// we ourself cannot revoke our suspend
|
|
return false;
|
|
}
|
|
|
|
if (!suspendAll_nothrow())
|
|
return false;
|
|
|
|
// commit the editor's content
|
|
if (haveView())
|
|
getPropertyBox().CommitModified();
|
|
|
|
// stop listening
|
|
stopContainerWindowListening();
|
|
|
|
// outta here
|
|
return true;
|
|
}
|
|
|
|
Any SAL_CALL OPropertyBrowserController::getViewData() { return Any(m_sPageSelection); }
|
|
|
|
void SAL_CALL OPropertyBrowserController::restoreViewData(const Any& Data)
|
|
{
|
|
OUString sPageSelection;
|
|
if ((Data >>= sPageSelection) && !sPageSelection.isEmpty())
|
|
{
|
|
m_sPageSelection = sPageSelection;
|
|
selectPageFromViewData();
|
|
}
|
|
}
|
|
|
|
Reference<XModel> SAL_CALL OPropertyBrowserController::getModel()
|
|
{
|
|
// have no model
|
|
return Reference<XModel>();
|
|
}
|
|
|
|
Reference<XFrame> SAL_CALL OPropertyBrowserController::getFrame() { return m_xFrame; }
|
|
|
|
void SAL_CALL OPropertyBrowserController::dispose()
|
|
{
|
|
SolarMutexGuard aSolarGuard;
|
|
|
|
// stop inspecting the current object
|
|
stopInspection(false);
|
|
|
|
// say our dispose listeners goodbye
|
|
css::lang::EventObject aEvt;
|
|
aEvt.Source = static_cast<::cppu::OWeakObject*>(this);
|
|
m_aDisposeListeners.disposeAndClear(aEvt);
|
|
m_aControlObservers.disposeAndClear(aEvt);
|
|
|
|
m_xPropView.reset();
|
|
m_xBuilder.reset();
|
|
|
|
if (m_xView.is())
|
|
m_xView->removeEventListener(static_cast<XPropertyChangeListener*>(this));
|
|
m_xView.clear();
|
|
|
|
m_aInspectedObjects.clear();
|
|
impl_bindToNewModel_nothrow(nullptr);
|
|
m_xModel.clear();
|
|
m_xInteractiveHandler.clear();
|
|
m_xFrame.clear();
|
|
}
|
|
|
|
void SAL_CALL
|
|
OPropertyBrowserController::addEventListener(const Reference<XEventListener>& _rxListener)
|
|
{
|
|
m_aDisposeListeners.addInterface(_rxListener);
|
|
}
|
|
|
|
void SAL_CALL
|
|
OPropertyBrowserController::removeEventListener(const Reference<XEventListener>& _rxListener)
|
|
{
|
|
m_aDisposeListeners.removeInterface(_rxListener);
|
|
}
|
|
|
|
OUString SAL_CALL OPropertyBrowserController::getImplementationName()
|
|
{
|
|
return u"org.openoffice.comp.extensions.ObjectInspector"_ustr;
|
|
}
|
|
|
|
sal_Bool SAL_CALL OPropertyBrowserController::supportsService(const OUString& ServiceName)
|
|
{
|
|
return cppu::supportsService(this, ServiceName);
|
|
}
|
|
|
|
Sequence<OUString> SAL_CALL OPropertyBrowserController::getSupportedServiceNames()
|
|
{
|
|
return { u"com.sun.star.inspection.ObjectInspector"_ustr };
|
|
}
|
|
|
|
void SAL_CALL OPropertyBrowserController::focusGained(const FocusEvent& _rSource)
|
|
{
|
|
Reference<XWindow> xSourceWindow(_rSource.Source, UNO_QUERY);
|
|
Reference<XWindow> xContainerWindow;
|
|
if (m_xFrame.is())
|
|
xContainerWindow = m_xFrame->getContainerWindow();
|
|
|
|
if (xContainerWindow.get() == xSourceWindow.get())
|
|
{
|
|
// our container window got the focus
|
|
if (haveView())
|
|
getPropertyBox().GrabFocus();
|
|
}
|
|
}
|
|
|
|
void SAL_CALL OPropertyBrowserController::focusLost(const FocusEvent& /*_rSource*/)
|
|
{
|
|
// not interested in
|
|
}
|
|
|
|
void SAL_CALL OPropertyBrowserController::disposing(const EventObject& _rSource)
|
|
{
|
|
if (m_xView.is() && (m_xView == _rSource.Source))
|
|
{
|
|
m_xView = nullptr;
|
|
m_xPropView.reset();
|
|
m_xBuilder.reset();
|
|
}
|
|
|
|
auto it = std::find_if(
|
|
m_aInspectedObjects.begin(), m_aInspectedObjects.end(),
|
|
[&_rSource](const InterfaceArray::value_type& rxObj) { return rxObj == _rSource.Source; });
|
|
if (it != m_aInspectedObjects.end())
|
|
m_aInspectedObjects.erase(it);
|
|
}
|
|
|
|
IMPL_LINK_NOARG(OPropertyBrowserController, OnPageActivation, LinkParamNone*, void)
|
|
{
|
|
updateViewDataFromActivePage();
|
|
}
|
|
|
|
void OPropertyBrowserController::updateViewDataFromActivePage()
|
|
{
|
|
if (!haveView())
|
|
return;
|
|
|
|
OUString sOldSelection = m_sPageSelection;
|
|
m_sPageSelection.clear();
|
|
|
|
const sal_uInt16 nCurrentPage = m_xPropView->getActivePage();
|
|
if (sal_uInt16(-1) != nCurrentPage)
|
|
{
|
|
for (auto const& pageId : m_aPageIds)
|
|
{
|
|
if (nCurrentPage == pageId.second)
|
|
{
|
|
m_sPageSelection = pageId.first;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!m_sPageSelection.isEmpty())
|
|
m_sLastValidPageSelection = m_sPageSelection;
|
|
else if (!sOldSelection.isEmpty())
|
|
m_sLastValidPageSelection = sOldSelection;
|
|
}
|
|
|
|
sal_uInt16
|
|
OPropertyBrowserController::impl_getPageIdForCategory_nothrow(const OUString& _rCategoryName) const
|
|
{
|
|
sal_uInt16 nPageId = sal_uInt16(-1);
|
|
HashString2Int16::const_iterator pagePos = m_aPageIds.find(_rCategoryName);
|
|
if (pagePos != m_aPageIds.end())
|
|
nPageId = pagePos->second;
|
|
return nPageId;
|
|
}
|
|
|
|
void OPropertyBrowserController::selectPageFromViewData()
|
|
{
|
|
sal_uInt16 nNewPage = impl_getPageIdForCategory_nothrow(m_sPageSelection);
|
|
|
|
if (haveView() && (nNewPage != sal_uInt16(-1)))
|
|
m_xPropView->activatePage(nNewPage);
|
|
|
|
// just in case ...
|
|
updateViewDataFromActivePage();
|
|
}
|
|
|
|
void OPropertyBrowserController::Construct(const Reference<XWindow>& rContainerWindow,
|
|
std::unique_ptr<weld::Builder> xBuilder)
|
|
{
|
|
DBG_ASSERT(!haveView(), "OPropertyBrowserController::Construct: already have a view!");
|
|
assert(xBuilder && "OPropertyBrowserController::Construct: invalid parent window!");
|
|
|
|
m_xBuilder = std::move(xBuilder);
|
|
|
|
m_xPropView.reset(new OPropertyBrowserView(m_xContext, *m_xBuilder));
|
|
m_xPropView->setPageActivationHandler(LINK(this, OPropertyBrowserController, OnPageActivation));
|
|
|
|
// add as dispose listener for our view. The view is disposed by the frame we're plugged into,
|
|
// and this disposal _deletes_ the view, so it would be deadly if we use our m_xPropView member
|
|
// after that
|
|
m_xView = rContainerWindow;
|
|
if (m_xView.is())
|
|
m_xView->addEventListener(static_cast<XPropertyChangeListener*>(this));
|
|
|
|
getPropertyBox().SetLineListener(this);
|
|
getPropertyBox().SetControlObserver(this);
|
|
impl_initializeView_nothrow();
|
|
}
|
|
|
|
void SAL_CALL OPropertyBrowserController::propertyChange(const PropertyChangeEvent& _rEvent)
|
|
{
|
|
if (_rEvent.Source == m_xModel)
|
|
{
|
|
if (_rEvent.PropertyName == "IsReadOnly")
|
|
// this is a huge cudgel, admitted.
|
|
// The problem is that in case we were previously read-only, all our controls
|
|
// were created read-only, too. We cannot simply switch them to not-read-only.
|
|
// Even if they had an API for this, we do not know whether they were
|
|
// originally created read-only, or if they are read-only just because
|
|
// the model was.
|
|
impl_rebindToInspectee_nothrow(std::vector(m_aInspectedObjects));
|
|
return;
|
|
}
|
|
|
|
if (m_sCommittingProperty == _rEvent.PropertyName)
|
|
return;
|
|
|
|
if (!haveView())
|
|
return;
|
|
|
|
Any aNewValue(_rEvent.NewValue);
|
|
if (impl_hasPropertyHandlerFor_nothrow(_rEvent.PropertyName))
|
|
{
|
|
// forward the new value to the property box, to reflect the change in the UI
|
|
aNewValue = impl_getPropertyValue_throw(_rEvent.PropertyName);
|
|
|
|
// check whether the state is ambiguous. This is interesting in case we display the properties
|
|
// for multiple objects at once: In this case, we'll get a notification from one of the objects,
|
|
// but need to care for the "composed" value, which can be "ambiguous".
|
|
PropertyHandlerRef xHandler(impl_getHandlerForProperty_throw(_rEvent.PropertyName),
|
|
UNO_SET_THROW);
|
|
PropertyState ePropertyState(xHandler->getPropertyState(_rEvent.PropertyName));
|
|
bool bAmbiguousValue = (PropertyState_AMBIGUOUS_VALUE == ePropertyState);
|
|
|
|
getPropertyBox().SetPropertyValue(_rEvent.PropertyName, aNewValue, bAmbiguousValue);
|
|
}
|
|
|
|
// if it's an actuating property, then update the UI for any dependent
|
|
// properties
|
|
if (impl_isActuatingProperty_nothrow(_rEvent.PropertyName))
|
|
impl_broadcastPropertyChange_nothrow(_rEvent.PropertyName, aNewValue, _rEvent.OldValue,
|
|
false);
|
|
}
|
|
|
|
Reference<XPropertyControl> SAL_CALL
|
|
OPropertyBrowserController::createPropertyControl(::sal_Int16 ControlType, sal_Bool bCreateReadOnly)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
Reference<XPropertyControl> xControl;
|
|
|
|
// read-only-ness
|
|
bCreateReadOnly |= impl_isReadOnlyModel_throw() ? 1 : 0;
|
|
|
|
switch (ControlType)
|
|
{
|
|
case PropertyControlType::MultiLineTextField:
|
|
case PropertyControlType::StringListField:
|
|
{
|
|
bool bMultiLineTextField = ControlType == PropertyControlType::MultiLineTextField;
|
|
std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder(
|
|
u"modules/spropctrlr/ui/multiline.ui"_ustr, m_xContext));
|
|
auto pContainer = xBuilder->weld_container(u"multiline"_ustr);
|
|
rtl::Reference<OMultilineEditControl> pControl = new OMultilineEditControl(
|
|
std::move(pContainer), std::move(xBuilder),
|
|
bMultiLineTextField ? eMultiLineText : eStringList, bCreateReadOnly);
|
|
pControl->SetModifyHandler();
|
|
xControl = pControl;
|
|
break;
|
|
}
|
|
|
|
case PropertyControlType::ListBox:
|
|
{
|
|
std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder(
|
|
u"modules/spropctrlr/ui/listbox.ui"_ustr, m_xContext));
|
|
auto pComboBox = xBuilder->weld_combo_box(u"listbox"_ustr);
|
|
rtl::Reference<OListboxControl> pControl
|
|
= new OListboxControl(std::move(pComboBox), std::move(xBuilder), bCreateReadOnly);
|
|
pControl->SetModifyHandler();
|
|
xControl = pControl;
|
|
break;
|
|
}
|
|
|
|
case PropertyControlType::ComboBox:
|
|
{
|
|
std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder(
|
|
u"modules/spropctrlr/ui/combobox.ui"_ustr, m_xContext));
|
|
auto pComboBox = xBuilder->weld_combo_box(u"combobox"_ustr);
|
|
rtl::Reference<OComboboxControl> pControl
|
|
= new OComboboxControl(std::move(pComboBox), std::move(xBuilder), bCreateReadOnly);
|
|
pControl->SetModifyHandler();
|
|
xControl = pControl;
|
|
break;
|
|
}
|
|
|
|
case PropertyControlType::TextField:
|
|
case PropertyControlType::CharacterField:
|
|
{
|
|
bool bCharacterField = ControlType == PropertyControlType::CharacterField;
|
|
std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder(
|
|
u"modules/spropctrlr/ui/textfield.ui"_ustr, m_xContext));
|
|
auto pEntry = xBuilder->weld_entry(u"textfield"_ustr);
|
|
rtl::Reference<OEditControl> pControl = new OEditControl(
|
|
std::move(pEntry), std::move(xBuilder), bCharacterField, bCreateReadOnly);
|
|
pControl->SetModifyHandler();
|
|
xControl = pControl;
|
|
break;
|
|
}
|
|
|
|
case PropertyControlType::NumericField:
|
|
{
|
|
std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder(
|
|
u"modules/spropctrlr/ui/numericfield.ui"_ustr, m_xContext));
|
|
auto pSpinButton
|
|
= xBuilder->weld_metric_spin_button(u"numericfield"_ustr, FieldUnit::NONE);
|
|
rtl::Reference<ONumericControl> pControl
|
|
= new ONumericControl(std::move(pSpinButton), std::move(xBuilder), bCreateReadOnly);
|
|
pControl->SetModifyHandler();
|
|
xControl = pControl;
|
|
break;
|
|
}
|
|
|
|
case PropertyControlType::DateTimeField:
|
|
{
|
|
std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder(
|
|
u"modules/spropctrlr/ui/datetimefield.ui"_ustr, m_xContext));
|
|
auto pContainer = xBuilder->weld_container(u"datetimefield"_ustr);
|
|
rtl::Reference<ODateTimeControl> pControl
|
|
= new ODateTimeControl(std::move(pContainer), std::move(xBuilder), bCreateReadOnly);
|
|
pControl->SetModifyHandler();
|
|
xControl = pControl;
|
|
break;
|
|
}
|
|
|
|
case PropertyControlType::DateField:
|
|
{
|
|
std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder(
|
|
u"modules/spropctrlr/ui/datefield.ui"_ustr, m_xContext));
|
|
auto pContainer = xBuilder->weld_container(u"datefield"_ustr);
|
|
rtl::Reference<ODateControl> pControl
|
|
= new ODateControl(std::move(pContainer), std::move(xBuilder), bCreateReadOnly);
|
|
pControl->SetModifyHandler();
|
|
xControl = pControl;
|
|
break;
|
|
}
|
|
|
|
case PropertyControlType::TimeField:
|
|
{
|
|
std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder(
|
|
u"modules/spropctrlr/ui/timefield.ui"_ustr, m_xContext));
|
|
auto pTimeSpinButton = xBuilder->weld_formatted_spin_button(u"timefield"_ustr);
|
|
rtl::Reference<OTimeControl> pControl = new OTimeControl(
|
|
std::move(pTimeSpinButton), std::move(xBuilder), bCreateReadOnly);
|
|
pControl->SetModifyHandler();
|
|
xControl = pControl;
|
|
break;
|
|
}
|
|
|
|
case PropertyControlType::ColorListBox:
|
|
{
|
|
auto lambda
|
|
= [this] { return PropertyHandlerHelper::getDialogParentFrame(m_xContext); };
|
|
std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder(
|
|
u"modules/spropctrlr/ui/colorlistbox.ui"_ustr, m_xContext));
|
|
auto pMenuButton = xBuilder->weld_menu_button(u"colorlistbox"_ustr);
|
|
rtl::Reference<OColorControl> pControl
|
|
= new OColorControl(std::make_unique<ColorListBox>(std::move(pMenuButton), lambda),
|
|
std::move(xBuilder), bCreateReadOnly);
|
|
pControl->SetModifyHandler();
|
|
xControl = pControl;
|
|
break;
|
|
}
|
|
|
|
case PropertyControlType::HyperlinkField:
|
|
{
|
|
std::unique_ptr<weld::Builder> xBuilder(PropertyHandlerHelper::makeBuilder(
|
|
u"modules/spropctrlr/ui/hyperlinkfield.ui"_ustr, m_xContext));
|
|
auto pContainer = xBuilder->weld_container(u"hyperlinkfield"_ustr);
|
|
rtl::Reference<OHyperlinkControl> pControl = new OHyperlinkControl(
|
|
std::move(pContainer), std::move(xBuilder), bCreateReadOnly);
|
|
pControl->SetModifyHandler();
|
|
xControl = pControl;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw IllegalArgumentException(OUString(), *this, 1);
|
|
}
|
|
|
|
return xControl;
|
|
}
|
|
|
|
void OPropertyBrowserController::impl_toggleInspecteeListening_nothrow(bool _bOn)
|
|
{
|
|
for (auto const& inspectedObject : m_aInspectedObjects)
|
|
{
|
|
try
|
|
{
|
|
Reference<XComponent> xComp(inspectedObject, UNO_QUERY);
|
|
if (xComp.is())
|
|
{
|
|
if (_bOn)
|
|
xComp->addEventListener(static_cast<XPropertyChangeListener*>(this));
|
|
else
|
|
xComp->removeEventListener(static_cast<XPropertyChangeListener*>(this));
|
|
}
|
|
}
|
|
catch (const Exception&)
|
|
{
|
|
DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
|
|
}
|
|
}
|
|
}
|
|
|
|
void OPropertyBrowserController::stopInspection(bool _bCommitModified)
|
|
{
|
|
if (haveView())
|
|
{
|
|
if (_bCommitModified)
|
|
// commit the editor's content
|
|
getPropertyBox().CommitModified();
|
|
|
|
// hide the property box so that it does not flicker
|
|
getPropertyBox().Hide();
|
|
|
|
// clear the property box
|
|
getPropertyBox().ClearAll();
|
|
}
|
|
|
|
// destroy the view first
|
|
if (haveView())
|
|
{
|
|
// remove the pages
|
|
for (auto const& pageId : m_aPageIds)
|
|
getPropertyBox().RemovePage(pageId.second);
|
|
clearContainer(m_aPageIds);
|
|
}
|
|
|
|
clearContainer(m_aProperties);
|
|
|
|
// de-register as dispose-listener from our inspected objects
|
|
impl_toggleInspecteeListening_nothrow(false);
|
|
|
|
// handlers are obsolete, so is our "composer" for their UI requests
|
|
if (m_pUIRequestComposer)
|
|
m_pUIRequestComposer->dispose();
|
|
m_pUIRequestComposer.reset();
|
|
|
|
// clean up the property handlers
|
|
PropertyHandlerArray aAllHandlers; // will contain every handler exactly once
|
|
for (auto const& propertyHandler : m_aPropertyHandlers)
|
|
if (std::find(aAllHandlers.begin(), aAllHandlers.end(), propertyHandler.second)
|
|
== aAllHandlers.end())
|
|
aAllHandlers.push_back(propertyHandler.second);
|
|
|
|
for (auto const& handler : aAllHandlers)
|
|
{
|
|
try
|
|
{
|
|
handler->removePropertyChangeListener(this);
|
|
handler->dispose();
|
|
}
|
|
catch (const DisposedException&)
|
|
{
|
|
}
|
|
catch (const Exception&)
|
|
{
|
|
DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
|
|
}
|
|
}
|
|
|
|
clearContainer(m_aPropertyHandlers);
|
|
clearContainer(m_aDependencyHandlers);
|
|
}
|
|
|
|
bool OPropertyBrowserController::impl_hasPropertyHandlerFor_nothrow(
|
|
const OUString& _rPropertyName) const
|
|
{
|
|
PropertyHandlerRepository::const_iterator handlerPos = m_aPropertyHandlers.find(_rPropertyName);
|
|
return (handlerPos != m_aPropertyHandlers.end());
|
|
}
|
|
|
|
OPropertyBrowserController::PropertyHandlerRef const&
|
|
OPropertyBrowserController::impl_getHandlerForProperty_throw(const OUString& _rPropertyName) const
|
|
{
|
|
PropertyHandlerRepository::const_iterator handlerPos = m_aPropertyHandlers.find(_rPropertyName);
|
|
if (handlerPos == m_aPropertyHandlers.end())
|
|
throw RuntimeException();
|
|
return handlerPos->second;
|
|
}
|
|
|
|
Any OPropertyBrowserController::impl_getPropertyValue_throw(const OUString& _rPropertyName)
|
|
{
|
|
PropertyHandlerRef handler = impl_getHandlerForProperty_throw(_rPropertyName);
|
|
return handler->getPropertyValue(_rPropertyName);
|
|
}
|
|
|
|
void OPropertyBrowserController::impl_rebindToInspectee_nothrow(InterfaceArray&& _rObjects)
|
|
{
|
|
try
|
|
{
|
|
// stop inspecting the old object(s)
|
|
stopInspection(true);
|
|
|
|
// inspect the new object(s)
|
|
m_aInspectedObjects = std::move(_rObjects);
|
|
doInspection();
|
|
|
|
// update the user interface
|
|
UpdateUI();
|
|
}
|
|
|
|
catch (const Exception&)
|
|
{
|
|
TOOLS_WARN_EXCEPTION("extensions.propctrlr", "");
|
|
}
|
|
}
|
|
|
|
void OPropertyBrowserController::doInspection()
|
|
{
|
|
try
|
|
{
|
|
// obtain the properties of the object
|
|
std::vector<Property> aProperties;
|
|
|
|
PropertyHandlerArray aPropertyHandlers = getPropertyHandlers(m_aInspectedObjects);
|
|
|
|
PropertyHandlerArray::iterator aHandler(aPropertyHandlers.begin());
|
|
while (aHandler != aPropertyHandlers.end())
|
|
{
|
|
DBG_ASSERT(aHandler->get(),
|
|
"OPropertyBrowserController::doInspection: invalid handler!");
|
|
|
|
Sequence<Property> aThisHandlersProperties((*aHandler)->getSupportedProperties());
|
|
|
|
if (!aThisHandlersProperties.hasElements())
|
|
{
|
|
// this handler doesn't know anything about the current inspectee -> ignore it
|
|
(*aHandler)->dispose();
|
|
aHandler = aPropertyHandlers.erase(aHandler);
|
|
continue;
|
|
}
|
|
|
|
// append these properties to our "all properties" array
|
|
aProperties.reserve(std::max<size_t>(
|
|
aProperties.size() + aThisHandlersProperties.size(), aProperties.size() * 2));
|
|
for (const auto& aThisHandlersProperty : aThisHandlersProperties)
|
|
{
|
|
auto noPrevious = std::none_of(aProperties.begin(), aProperties.end(),
|
|
FindPropertyByName(aThisHandlersProperty.Name));
|
|
if (noPrevious)
|
|
{
|
|
aProperties.push_back(aThisHandlersProperty);
|
|
continue;
|
|
}
|
|
|
|
// there already was another (previous) handler which supported this property.
|
|
// Don't add it to aProperties, again.
|
|
|
|
// Also, ensure that handlers which previously expressed interest in *changes*
|
|
// of this property are not notified.
|
|
// This is 'cause we have a new handler which is responsible for this property,
|
|
// which means it can give it a completely different meaning than the previous
|
|
// handler for this property is prepared for.
|
|
std::pair<PropertyHandlerMultiRepository::iterator,
|
|
PropertyHandlerMultiRepository::iterator>
|
|
aDepHandlers = m_aDependencyHandlers.equal_range(aThisHandlersProperty.Name);
|
|
m_aDependencyHandlers.erase(aDepHandlers.first, aDepHandlers.second);
|
|
}
|
|
|
|
// determine the superseded properties
|
|
Sequence<OUString> aSupersededByThisHandler((*aHandler)->getSupersededProperties());
|
|
for (const auto& superseded : aSupersededByThisHandler)
|
|
{
|
|
std::vector<Property>::iterator existent = std::find_if(
|
|
aProperties.begin(), aProperties.end(), FindPropertyByName(superseded));
|
|
if (existent != aProperties.end())
|
|
// one of the properties superseded by this handler was supported by a previous
|
|
// one -> erase
|
|
aProperties.erase(existent);
|
|
}
|
|
|
|
// be notified of changes which this handler is responsible for
|
|
(*aHandler)->addPropertyChangeListener(this);
|
|
|
|
// remember this handler for every of the properties which it is responsible
|
|
// for
|
|
for (const auto& aThisHandlersProperty : aThisHandlersProperties)
|
|
{
|
|
m_aPropertyHandlers[aThisHandlersProperty.Name] = *aHandler;
|
|
// note that this implies that if two handlers support the same property,
|
|
// the latter wins
|
|
}
|
|
|
|
// see if the handler expresses interest in any actuating properties
|
|
Sequence<OUString> aInterestingActuations((*aHandler)->getActuatingProperties());
|
|
for (const auto& aInterestingActuation : aInterestingActuations)
|
|
{
|
|
m_aDependencyHandlers.emplace(aInterestingActuation, *aHandler);
|
|
}
|
|
|
|
++aHandler;
|
|
}
|
|
|
|
// create a new composer for UI requests coming from the handlers
|
|
m_pUIRequestComposer.reset(new ComposedPropertyUIUpdate(getInspectorUI(), this));
|
|
|
|
// sort the properties by relative position, as indicated by the model
|
|
sal_Int32 nPos = 0;
|
|
for (auto const& sourceProps : aProperties)
|
|
{
|
|
sal_Int32 nRelativePropertyOrder = nPos;
|
|
if (m_xModel.is())
|
|
nRelativePropertyOrder = m_xModel->getPropertyOrderIndex(sourceProps.Name);
|
|
m_aProperties.emplace(nRelativePropertyOrder, sourceProps);
|
|
++nPos;
|
|
}
|
|
|
|
// be notified when one of our inspectees dies
|
|
impl_toggleInspecteeListening_nothrow(true);
|
|
}
|
|
catch (const Exception&)
|
|
{
|
|
TOOLS_WARN_EXCEPTION("extensions.propctrlr", "");
|
|
}
|
|
}
|
|
|
|
css::awt::Size SAL_CALL OPropertyBrowserController::getMinimumSize()
|
|
{
|
|
css::awt::Size aSize;
|
|
if (m_xPropView)
|
|
return m_xPropView->getMinimumSize();
|
|
else
|
|
return aSize;
|
|
}
|
|
|
|
css::awt::Size SAL_CALL OPropertyBrowserController::getPreferredSize() { return getMinimumSize(); }
|
|
|
|
css::awt::Size SAL_CALL
|
|
OPropertyBrowserController::calcAdjustedSize(const css::awt::Size& _rNewSize)
|
|
{
|
|
awt::Size aMinSize = getMinimumSize();
|
|
awt::Size aAdjustedSize(_rNewSize);
|
|
if (aAdjustedSize.Width < aMinSize.Width)
|
|
aAdjustedSize.Width = aMinSize.Width;
|
|
if (aAdjustedSize.Height < aMinSize.Height)
|
|
aAdjustedSize.Height = aMinSize.Height;
|
|
return aAdjustedSize;
|
|
}
|
|
|
|
void OPropertyBrowserController::describePropertyLine(const Property& _rProperty,
|
|
OLineDescriptor& _rDescriptor)
|
|
{
|
|
try
|
|
{
|
|
PropertyHandlerRepository::const_iterator handler
|
|
= m_aPropertyHandlers.find(_rProperty.Name);
|
|
if (handler == m_aPropertyHandlers.end())
|
|
throw RuntimeException(); // caught below
|
|
|
|
_rDescriptor.assignFrom(handler->second->describePropertyLine(_rProperty.Name, this));
|
|
|
|
_rDescriptor.xPropertyHandler = handler->second;
|
|
_rDescriptor.sName = _rProperty.Name;
|
|
_rDescriptor.aValue = _rDescriptor.xPropertyHandler->getPropertyValue(_rProperty.Name);
|
|
|
|
if (_rDescriptor.DisplayName.isEmpty())
|
|
{
|
|
#ifdef DBG_UTIL
|
|
SAL_WARN("extensions.propctrlr", "OPropertyBrowserController::describePropertyLine: "
|
|
"handler did not provide a display name for '"
|
|
<< _rProperty.Name << "'!");
|
|
#endif
|
|
_rDescriptor.DisplayName = _rProperty.Name;
|
|
}
|
|
|
|
PropertyState ePropertyState(
|
|
_rDescriptor.xPropertyHandler->getPropertyState(_rProperty.Name));
|
|
if (PropertyState_AMBIGUOUS_VALUE == ePropertyState)
|
|
{
|
|
_rDescriptor.bUnknownValue = true;
|
|
_rDescriptor.aValue.clear();
|
|
}
|
|
|
|
_rDescriptor.bReadOnly = impl_isReadOnlyModel_throw();
|
|
|
|
// for ui-testing try and distinguish different instances of the controls
|
|
auto xWindow = _rDescriptor.Control->getControlWindow();
|
|
if (weld::TransportAsXWindow* pTunnel
|
|
= dynamic_cast<weld::TransportAsXWindow*>(xWindow.get()))
|
|
{
|
|
weld::Widget* m_pControlWindow = pTunnel->getWidget();
|
|
if (m_pControlWindow)
|
|
m_pControlWindow->set_buildable_name(m_pControlWindow->get_buildable_name() + "-"
|
|
+ _rDescriptor.DisplayName);
|
|
}
|
|
}
|
|
catch (const Exception&)
|
|
{
|
|
TOOLS_WARN_EXCEPTION("extensions.propctrlr",
|
|
"OPropertyBrowserController::describePropertyLine");
|
|
}
|
|
}
|
|
|
|
void OPropertyBrowserController::impl_buildCategories_throw()
|
|
{
|
|
OSL_PRECOND(m_aPageIds.empty(),
|
|
"OPropertyBrowserController::impl_buildCategories_throw: duplicate call!");
|
|
|
|
Sequence<PropertyCategoryDescriptor> aCategories;
|
|
if (m_xModel.is())
|
|
aCategories = m_xModel->describeCategories();
|
|
|
|
for (auto const& category : aCategories)
|
|
{
|
|
OSL_ENSURE(
|
|
m_aPageIds.find(category.ProgrammaticName) == m_aPageIds.end(),
|
|
"OPropertyBrowserController::impl_buildCategories_throw: duplicate programmatic name!");
|
|
|
|
m_aPageIds[category.ProgrammaticName]
|
|
= getPropertyBox().AppendPage(category.UIName, HelpIdUrl::getHelpId(category.HelpURL));
|
|
}
|
|
}
|
|
|
|
void OPropertyBrowserController::UpdateUI()
|
|
{
|
|
try
|
|
{
|
|
if (!haveView())
|
|
// too early, will return later
|
|
return;
|
|
|
|
// create our tab pages
|
|
impl_buildCategories_throw();
|
|
// (and allow for pages to be actually unused)
|
|
std::set<sal_uInt16> aUsedPages;
|
|
|
|
// when building the UI below, remember which properties are actuating,
|
|
// to allow for an initial actuatingPropertyChanged call
|
|
std::vector<OUString> aActuatingProperties;
|
|
std::vector<Any> aActuatingPropertyValues;
|
|
|
|
// ask the handlers to describe the property UI, and insert the resulting
|
|
// entries into our list boxes
|
|
for (auto const& property : m_aProperties)
|
|
{
|
|
OLineDescriptor aDescriptor;
|
|
describePropertyLine(property.second, aDescriptor);
|
|
|
|
bool bIsActuatingProperty = impl_isActuatingProperty_nothrow(property.second.Name);
|
|
|
|
SAL_WARN_IF(
|
|
aDescriptor.Category.isEmpty(), "extensions.propctrlr",
|
|
"OPropertyBrowserController::UpdateUI: empty category provided for property '"
|
|
<< property.second.Name << "'!");
|
|
// finally insert this property control
|
|
sal_uInt16 nTargetPageId = impl_getPageIdForCategory_nothrow(aDescriptor.Category);
|
|
if (nTargetPageId == sal_uInt16(-1))
|
|
{
|
|
// this category does not yet exist. This is allowed, as an inspector model might be lazy, and not provide
|
|
// any category information of its own. In this case, we have a fallback ...
|
|
m_aPageIds[aDescriptor.Category]
|
|
= getPropertyBox().AppendPage(aDescriptor.Category, {});
|
|
nTargetPageId = impl_getPageIdForCategory_nothrow(aDescriptor.Category);
|
|
}
|
|
|
|
getPropertyBox().InsertEntry(aDescriptor, nTargetPageId);
|
|
aUsedPages.insert(nTargetPageId);
|
|
|
|
// if it's an actuating property, remember it
|
|
if (bIsActuatingProperty)
|
|
{
|
|
aActuatingProperties.push_back(property.second.Name);
|
|
aActuatingPropertyValues.push_back(
|
|
impl_getPropertyValue_throw(property.second.Name));
|
|
}
|
|
}
|
|
|
|
// update any dependencies for the actuating properties which we encountered
|
|
{
|
|
std::vector<Any>::const_iterator aPropertyValue = aActuatingPropertyValues.begin();
|
|
for (auto const& actuatingProperty : aActuatingProperties)
|
|
{
|
|
impl_broadcastPropertyChange_nothrow(actuatingProperty, *aPropertyValue,
|
|
*aPropertyValue, true);
|
|
++aPropertyValue;
|
|
}
|
|
}
|
|
|
|
// remove any unused pages (which we did not encounter properties for)
|
|
HashString2Int16 aSurvivingPageIds;
|
|
for (auto const& pageId : m_aPageIds)
|
|
{
|
|
if (aUsedPages.find(pageId.second) == aUsedPages.end())
|
|
getPropertyBox().RemovePage(pageId.second);
|
|
else
|
|
aSurvivingPageIds.insert(pageId);
|
|
}
|
|
m_aPageIds.swap(aSurvivingPageIds);
|
|
|
|
getPropertyBox().Show();
|
|
|
|
// activate the first page
|
|
if (!m_aPageIds.empty())
|
|
{
|
|
Sequence<PropertyCategoryDescriptor> aCategories(m_xModel->describeCategories());
|
|
if (aCategories.hasElements())
|
|
m_xPropView->activatePage(m_aPageIds[aCategories[0].ProgrammaticName]);
|
|
else
|
|
// allowed: if we default-created the pages ...
|
|
m_xPropView->activatePage(m_aPageIds.begin()->second);
|
|
}
|
|
|
|
// activate the previously active page (if possible)
|
|
if (!m_sLastValidPageSelection.isEmpty())
|
|
m_sPageSelection = m_sLastValidPageSelection;
|
|
selectPageFromViewData();
|
|
}
|
|
catch (const Exception&)
|
|
{
|
|
DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
|
|
}
|
|
}
|
|
|
|
void OPropertyBrowserController::Clicked(const OUString& _rName, bool _bPrimary)
|
|
{
|
|
try
|
|
{
|
|
// since the browse buttons do not get the focus when clicked with the mouse,
|
|
// we need to commit the changes in the current property field
|
|
getPropertyBox().CommitModified();
|
|
|
|
PropertyHandlerRepository::const_iterator handler = m_aPropertyHandlers.find(_rName);
|
|
DBG_ASSERT(
|
|
handler != m_aPropertyHandlers.end(),
|
|
"OPropertyBrowserController::Clicked: a property without handler? This will crash!");
|
|
|
|
ComposedUIAutoFireGuard aAutoFireGuard(*m_pUIRequestComposer);
|
|
|
|
Any aData;
|
|
m_xInteractiveHandler = handler->second;
|
|
InteractiveSelectionResult eResult = handler->second->onInteractivePropertySelection(
|
|
_rName, _bPrimary, aData,
|
|
m_pUIRequestComposer->getUIForPropertyHandler(handler->second));
|
|
|
|
switch (eResult)
|
|
{
|
|
case InteractiveSelectionResult_Cancelled:
|
|
case InteractiveSelectionResult_Success:
|
|
// okay, nothing to do
|
|
break;
|
|
case InteractiveSelectionResult_ObtainedValue:
|
|
handler->second->setPropertyValue(_rName, aData);
|
|
break;
|
|
case InteractiveSelectionResult_Pending:
|
|
// also okay, we expect that the handler has disabled the UI as necessary
|
|
break;
|
|
default:
|
|
OSL_FAIL("OPropertyBrowserController::Clicked: unknown result value!");
|
|
break;
|
|
}
|
|
}
|
|
catch (const Exception&)
|
|
{
|
|
DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
|
|
}
|
|
m_xInteractiveHandler = nullptr;
|
|
}
|
|
|
|
bool OPropertyBrowserController::hasPropertyByName(const OUString& _rName)
|
|
{
|
|
for (auto const& property : m_aProperties)
|
|
if (property.second.Name == _rName)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void OPropertyBrowserController::Commit(const OUString& rName, const Any& _rValue)
|
|
{
|
|
try
|
|
{
|
|
OUString sPlcHolder = PcrRes(RID_EMBED_IMAGE_PLACEHOLDER);
|
|
bool bIsPlaceHolderValue = false;
|
|
|
|
if (rName == PROPERTY_IMAGE_URL)
|
|
{
|
|
// if the prop value is the PlaceHolder
|
|
// can ignore it
|
|
OUString sVal;
|
|
_rValue >>= sVal;
|
|
if (sVal == sPlcHolder)
|
|
bIsPlaceHolderValue = true;
|
|
}
|
|
m_sCommittingProperty = rName;
|
|
|
|
bool bIsActuatingProperty = impl_isActuatingProperty_nothrow(rName);
|
|
|
|
Any aOldValue;
|
|
if (bIsActuatingProperty)
|
|
aOldValue = impl_getPropertyValue_throw(rName);
|
|
|
|
// do we have a dedicated handler for this property, which we can delegate some tasks to?
|
|
PropertyHandlerRef handler = impl_getHandlerForProperty_throw(rName);
|
|
|
|
// set the value ( only if it's not a placeholder )
|
|
if (!bIsPlaceHolderValue)
|
|
handler->setPropertyValue(rName, _rValue);
|
|
|
|
// re-retrieve the value
|
|
Any aNormalizedValue = handler->getPropertyValue(rName);
|
|
|
|
// care for any inter-property dependencies
|
|
if (bIsActuatingProperty)
|
|
impl_broadcastPropertyChange_nothrow(rName, aNormalizedValue, aOldValue, false);
|
|
|
|
// and display it again. This ensures proper formatting
|
|
getPropertyBox().SetPropertyValue(rName, aNormalizedValue, false);
|
|
}
|
|
catch (const PropertyVetoException& eVetoException)
|
|
{
|
|
std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(
|
|
m_xPropView->getPropertyBox().getWidget(), VclMessageType::Info, VclButtonsType::Ok,
|
|
eVetoException.Message));
|
|
xInfoBox->run();
|
|
PropertyHandlerRef handler = impl_getHandlerForProperty_throw(rName);
|
|
Any aNormalizedValue = handler->getPropertyValue(rName);
|
|
getPropertyBox().SetPropertyValue(rName, aNormalizedValue, false);
|
|
}
|
|
catch (const Exception&)
|
|
{
|
|
TOOLS_WARN_EXCEPTION("extensions.propctrlr", "");
|
|
}
|
|
|
|
m_sCommittingProperty.clear();
|
|
}
|
|
|
|
void OPropertyBrowserController::focusGained(const Reference<XPropertyControl>& Control)
|
|
{
|
|
m_aControlObservers.notifyEach(&XPropertyControlObserver::focusGained, Control);
|
|
}
|
|
|
|
void OPropertyBrowserController::valueChanged(const Reference<XPropertyControl>& Control)
|
|
{
|
|
m_aControlObservers.notifyEach(&XPropertyControlObserver::valueChanged, Control);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
Reference<XPropertyHandler> lcl_createHandler(const Reference<XComponentContext>& _rContext,
|
|
const Any& _rFactoryDescriptor)
|
|
{
|
|
Reference<XPropertyHandler> xHandler;
|
|
|
|
OUString sServiceName;
|
|
Reference<XSingleServiceFactory> xServiceFac;
|
|
Reference<XSingleComponentFactory> xComponentFac;
|
|
|
|
if (_rFactoryDescriptor >>= sServiceName)
|
|
xHandler.set(
|
|
_rContext->getServiceManager()->createInstanceWithContext(sServiceName, _rContext),
|
|
UNO_QUERY);
|
|
else if (_rFactoryDescriptor >>= xServiceFac)
|
|
xHandler.set(xServiceFac->createInstance(), css::uno::UNO_QUERY);
|
|
else if (_rFactoryDescriptor >>= xComponentFac)
|
|
xHandler.set(xComponentFac->createInstanceWithContext(_rContext), css::uno::UNO_QUERY);
|
|
OSL_ENSURE(xHandler.is(), "lcl_createHandler: Can not create handler");
|
|
return xHandler;
|
|
}
|
|
}
|
|
|
|
OPropertyBrowserController::PropertyHandlerArray
|
|
OPropertyBrowserController::getPropertyHandlers(const InterfaceArray& _rObjects)
|
|
{
|
|
PropertyHandlerArray aHandlers;
|
|
if (_rObjects.empty())
|
|
return aHandlers;
|
|
|
|
Sequence<Any> aHandlerFactories;
|
|
if (m_xModel.is())
|
|
aHandlerFactories = m_xModel->getHandlerFactories();
|
|
|
|
for (auto const& handlerFactory : aHandlerFactories)
|
|
{
|
|
if (_rObjects.size() == 1)
|
|
{ // we're inspecting only one object -> one handler
|
|
Reference<XPropertyHandler> xHandler(lcl_createHandler(m_xContext, handlerFactory));
|
|
if (xHandler.is())
|
|
{
|
|
xHandler->inspect(_rObjects[0]);
|
|
aHandlers.push_back(xHandler);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// create a single handler for every single object
|
|
std::vector<Reference<XPropertyHandler>> aSingleHandlers(_rObjects.size());
|
|
std::vector<Reference<XPropertyHandler>>::iterator pHandler = aSingleHandlers.begin();
|
|
|
|
for (auto const& elem : _rObjects)
|
|
{
|
|
*pHandler = lcl_createHandler(m_xContext, handlerFactory);
|
|
if (pHandler->is())
|
|
{
|
|
(*pHandler)->inspect(elem);
|
|
++pHandler;
|
|
}
|
|
}
|
|
aSingleHandlers.resize(pHandler - aSingleHandlers.begin());
|
|
|
|
// then create a handler which composes information out of those single handlers
|
|
if (!aSingleHandlers.empty())
|
|
aHandlers.push_back(new PropertyComposer(std::move(aSingleHandlers)));
|
|
}
|
|
}
|
|
|
|
// note that the handlers will not be used by our caller, if they indicate that there are no
|
|
// properties they feel responsible for
|
|
|
|
return aHandlers;
|
|
}
|
|
|
|
bool OPropertyBrowserController::impl_findObjectProperty_nothrow(
|
|
const OUString& _rName, OrderedPropertyMap::const_iterator* _pProperty)
|
|
{
|
|
OrderedPropertyMap::const_iterator search
|
|
= std::find_if(m_aProperties.begin(), m_aProperties.end(),
|
|
[&_rName](const OrderedPropertyMap::value_type& rEntry) {
|
|
return rEntry.second.Name == _rName;
|
|
});
|
|
if (_pProperty)
|
|
*_pProperty = search;
|
|
return (search != m_aProperties.end());
|
|
}
|
|
|
|
void OPropertyBrowserController::rebuildPropertyUI(const OUString& _rPropertyName)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
if (!haveView())
|
|
throw RuntimeException();
|
|
|
|
OrderedPropertyMap::const_iterator propertyPos;
|
|
if (!impl_findObjectProperty_nothrow(_rPropertyName, &propertyPos))
|
|
return;
|
|
|
|
OLineDescriptor aDescriptor;
|
|
try
|
|
{
|
|
describePropertyLine(propertyPos->second, aDescriptor);
|
|
}
|
|
catch (const Exception&)
|
|
{
|
|
TOOLS_WARN_EXCEPTION("extensions.propctrlr",
|
|
"OPropertyBrowserController::rebuildPropertyUI");
|
|
}
|
|
|
|
getPropertyBox().ChangeEntry(aDescriptor);
|
|
}
|
|
|
|
void OPropertyBrowserController::enablePropertyUI(const OUString& _rPropertyName, sal_Bool _bEnable)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
if (!haveView())
|
|
throw RuntimeException();
|
|
|
|
if (!impl_findObjectProperty_nothrow(_rPropertyName))
|
|
return;
|
|
|
|
getPropertyBox().EnablePropertyLine(_rPropertyName, _bEnable);
|
|
}
|
|
|
|
void OPropertyBrowserController::enablePropertyUIElements(const OUString& _rPropertyName,
|
|
sal_Int16 _nElements, sal_Bool _bEnable)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
if (!haveView())
|
|
throw RuntimeException();
|
|
|
|
if (!impl_findObjectProperty_nothrow(_rPropertyName))
|
|
return;
|
|
|
|
getPropertyBox().EnablePropertyControls(_rPropertyName, _nElements, _bEnable);
|
|
}
|
|
|
|
void OPropertyBrowserController::showPropertyUI(const OUString& _rPropertyName)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
if (!haveView())
|
|
throw RuntimeException();
|
|
|
|
// look up the property in our object properties
|
|
OrderedPropertyMap::const_iterator propertyPos;
|
|
if (!impl_findObjectProperty_nothrow(_rPropertyName, &propertyPos))
|
|
return;
|
|
|
|
if (getPropertyBox().GetPropertyPos(_rPropertyName) != EDITOR_LIST_ENTRY_NOTFOUND)
|
|
{
|
|
rebuildPropertyUI(_rPropertyName);
|
|
return;
|
|
}
|
|
|
|
OLineDescriptor aDescriptor;
|
|
describePropertyLine(propertyPos->second, aDescriptor);
|
|
|
|
// look for the position to insert the property
|
|
|
|
// side note: The methods GetPropertyPos and InsertEntry of the OPropertyEditor work
|
|
// only on the current page. This implies that it's impossible to use this method here
|
|
// to show property lines which are *not* on the current page.
|
|
// This is sufficient for now, but should be changed in the future.
|
|
|
|
// by definition, the properties in m_aProperties are in the order in which they appear in the UI
|
|
// So all we need is a predecessor of pProperty in m_aProperties
|
|
sal_uInt16 nUIPos = EDITOR_LIST_ENTRY_NOTFOUND;
|
|
do
|
|
{
|
|
if (propertyPos != m_aProperties.begin())
|
|
--propertyPos;
|
|
nUIPos = getPropertyBox().GetPropertyPos(propertyPos->second.Name);
|
|
} while ((nUIPos == EDITOR_LIST_ENTRY_NOTFOUND) && (propertyPos != m_aProperties.begin()));
|
|
|
|
if (nUIPos == EDITOR_LIST_ENTRY_NOTFOUND)
|
|
// insert at the very top
|
|
nUIPos = 0;
|
|
else
|
|
// insert right after the predecessor we found
|
|
++nUIPos;
|
|
|
|
getPropertyBox().InsertEntry(aDescriptor,
|
|
impl_getPageIdForCategory_nothrow(aDescriptor.Category), nUIPos);
|
|
}
|
|
|
|
void OPropertyBrowserController::hidePropertyUI(const OUString& _rPropertyName)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
if (!haveView())
|
|
throw RuntimeException();
|
|
|
|
if (!impl_findObjectProperty_nothrow(_rPropertyName))
|
|
return;
|
|
|
|
getPropertyBox().RemoveEntry(_rPropertyName);
|
|
}
|
|
|
|
void OPropertyBrowserController::showCategory(const OUString& rCategory, sal_Bool bShow)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
if (!haveView())
|
|
throw RuntimeException();
|
|
|
|
sal_uInt16 nPageId = impl_getPageIdForCategory_nothrow(rCategory);
|
|
OSL_ENSURE(nPageId != sal_uInt16(-1),
|
|
"OPropertyBrowserController::showCategory: invalid category!");
|
|
|
|
getPropertyBox().ShowPropertyPage(nPageId, bShow);
|
|
}
|
|
|
|
Reference<XPropertyControl>
|
|
SAL_CALL OPropertyBrowserController::getPropertyControl(const OUString& _rPropertyName)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
if (!haveView())
|
|
throw RuntimeException();
|
|
|
|
Reference<XPropertyControl> xControl(getPropertyBox().GetPropertyControl(_rPropertyName));
|
|
return xControl;
|
|
}
|
|
|
|
void SAL_CALL OPropertyBrowserController::registerControlObserver(
|
|
const Reference<XPropertyControlObserver>& Observer)
|
|
{
|
|
m_aControlObservers.addInterface(Observer);
|
|
}
|
|
|
|
void SAL_CALL OPropertyBrowserController::revokeControlObserver(
|
|
const Reference<XPropertyControlObserver>& Observer)
|
|
{
|
|
m_aControlObservers.removeInterface(Observer);
|
|
}
|
|
|
|
void SAL_CALL OPropertyBrowserController::setHelpSectionText(const OUString& _rHelpText)
|
|
{
|
|
SolarMutexGuard aSolarGuard;
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
if (!haveView())
|
|
throw DisposedException();
|
|
|
|
if (!getPropertyBox().HasHelpSection())
|
|
throw NoSupportException();
|
|
|
|
getPropertyBox().SetHelpText(_rHelpText);
|
|
}
|
|
|
|
void OPropertyBrowserController::impl_broadcastPropertyChange_nothrow(
|
|
const OUString& _rPropertyName, const Any& _rNewValue, const Any& _rOldValue,
|
|
bool _bFirstTimeInit) const
|
|
{
|
|
// are there one or more handlers which are interested in the actuation?
|
|
std::pair<PropertyHandlerMultiRepository::const_iterator,
|
|
PropertyHandlerMultiRepository::const_iterator>
|
|
aInterestedHandlers = m_aDependencyHandlers.equal_range(_rPropertyName);
|
|
if (aInterestedHandlers.first == aInterestedHandlers.second)
|
|
// none of our handlers is interested in this
|
|
return;
|
|
|
|
ComposedUIAutoFireGuard aAutoFireGuard(*m_pUIRequestComposer);
|
|
try
|
|
{
|
|
// collect the responses from all interested handlers
|
|
PropertyHandlerMultiRepository::const_iterator handler = aInterestedHandlers.first;
|
|
while (handler != aInterestedHandlers.second)
|
|
{
|
|
handler->second->actuatingPropertyChanged(
|
|
_rPropertyName, _rNewValue, _rOldValue,
|
|
m_pUIRequestComposer->getUIForPropertyHandler(handler->second), _bFirstTimeInit);
|
|
++handler;
|
|
}
|
|
}
|
|
catch (const Exception&)
|
|
{
|
|
DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
|
|
}
|
|
}
|
|
|
|
} // namespace pcr
|
|
|
|
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
|
|
extensions_propctrlr_OPropertyBrowserController_get_implementation(
|
|
css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
|
|
{
|
|
return cppu::acquire(new pcr::OPropertyBrowserController(context));
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|