diff options
Diffstat (limited to '')
-rw-r--r-- | dbaccess/source/ui/dlg/sqlmessage.cxx | 607 |
1 files changed, 607 insertions, 0 deletions
diff --git a/dbaccess/source/ui/dlg/sqlmessage.cxx b/dbaccess/source/ui/dlg/sqlmessage.cxx new file mode 100644 index 000000000..39ba6dc8e --- /dev/null +++ b/dbaccess/source/ui/dlg/sqlmessage.cxx @@ -0,0 +1,607 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <core_resource.hxx> +#include <sqlmessage.hxx> +#include <strings.hrc> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/sdb/SQLContext.hpp> +#include <vcl/stdtext.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <osl/diagnose.h> +#include <connectivity/dbexception.hxx> +#include <connectivity/sqlerror.hxx> +#include <unotools/configmgr.hxx> + +#include <tools/urlobj.hxx> + +#define RET_MORE RET_RETRY + 1 + +using namespace dbtools; +using namespace com::sun::star::uno; +using namespace com::sun::star::sdb; +using namespace com::sun::star::sdbc; + +namespace dbaui +{ + +namespace +{ + class ImageProvider + { + private: + OUString m_defaultImageID; + + public: + explicit ImageProvider(const OUString& defaultImageID) + : m_defaultImageID(defaultImageID) + { + } + + const OUString& getImage() const + { + return m_defaultImageID; + } + }; + + class LabelProvider + { + private: + OUString m_label; + public: + explicit LabelProvider(const char* labelResourceID) + : m_label(DBA_RES(labelResourceID)) + { + } + + const OUString& getLabel() const + { + return m_label; + } + }; + + class ProviderFactory + { + private: + mutable std::shared_ptr< ImageProvider > m_pErrorImage; + mutable std::shared_ptr< ImageProvider > m_pWarningsImage; + mutable std::shared_ptr< ImageProvider > m_pInfoImage; + mutable std::shared_ptr< LabelProvider > m_pErrorLabel; + mutable std::shared_ptr< LabelProvider > m_pWarningsLabel; + mutable std::shared_ptr< LabelProvider > m_pInfoLabel; + + public: + ProviderFactory() + { + } + + std::shared_ptr< ImageProvider > const & getImageProvider( SQLExceptionInfo::TYPE _eType ) const + { + std::shared_ptr< ImageProvider >* ppProvider( &m_pErrorImage ); + OUString sNormalImageID("dialog-error"); + + switch ( _eType ) + { + case SQLExceptionInfo::TYPE::SQLWarning: + ppProvider = &m_pWarningsImage; + sNormalImageID = "dialog-warning"; + break; + + case SQLExceptionInfo::TYPE::SQLContext: + ppProvider = &m_pInfoImage; + sNormalImageID = "dialog-information"; + break; + + default: + break; + } + + if ( !ppProvider->get() ) + (*ppProvider) = std::make_shared<ImageProvider>(sNormalImageID); + return *ppProvider; + } + + std::shared_ptr< LabelProvider > const & getLabelProvider( SQLExceptionInfo::TYPE _eType, bool _bSubLabel ) const + { + std::shared_ptr< LabelProvider >* ppProvider( &m_pErrorLabel ); + const char* pLabelID( STR_EXCEPTION_ERROR ); + + switch ( _eType ) + { + case SQLExceptionInfo::TYPE::SQLWarning: + ppProvider = &m_pWarningsLabel; + pLabelID = STR_EXCEPTION_WARNING; + break; + + case SQLExceptionInfo::TYPE::SQLContext: + ppProvider = &m_pInfoLabel; + pLabelID = _bSubLabel ? STR_EXCEPTION_DETAILS : STR_EXCEPTION_INFO; + break; + default: + break; + } + + if ( !ppProvider->get() ) + (*ppProvider) = std::make_shared<LabelProvider>( pLabelID ); + return *ppProvider; + } + + }; + + /// a stripped version of the SQLException, packed for displaying + struct ExceptionDisplayInfo + { + SQLExceptionInfo::TYPE eType; + + std::shared_ptr< ImageProvider > pImageProvider; + std::shared_ptr< LabelProvider > pLabelProvider; + + bool bSubEntry; + + OUString sMessage; + OUString sSQLState; + OUString sErrorCode; + + ExceptionDisplayInfo() : eType( SQLExceptionInfo::TYPE::Undefined ), bSubEntry( false ) { } + explicit ExceptionDisplayInfo( SQLExceptionInfo::TYPE _eType ) : eType( _eType ), bSubEntry( false ) { } + }; + + bool lcl_hasDetails( const ExceptionDisplayInfo& _displayInfo ) + { + return ( !_displayInfo.sErrorCode.isEmpty() ) + || ( !_displayInfo.sSQLState.isEmpty() + && _displayInfo.sSQLState != "S1000" + ); + } + + typedef std::vector< ExceptionDisplayInfo > ExceptionDisplayChain; + + /// strips the [OOoBase] vendor identifier from the given error message, if applicable + OUString lcl_stripOOoBaseVendor( const OUString& _rErrorMessage ) + { + OUString sErrorMessage( _rErrorMessage ); + + const OUString sVendorIdentifier( ::connectivity::SQLError::getMessagePrefix() ); + if ( sErrorMessage.startsWith( sVendorIdentifier ) ) + { + // characters to strip + sal_Int32 nStripLen( sVendorIdentifier.getLength() ); + // usually, there should be a whitespace between the vendor and the real message + while ( ( sErrorMessage.getLength() > nStripLen ) + && ( sErrorMessage[nStripLen] == ' ' ) + ) + ++nStripLen; + sErrorMessage = sErrorMessage.copy( nStripLen ); + } + + return sErrorMessage; + } + + void lcl_buildExceptionChain( const SQLExceptionInfo& _rErrorInfo, const ProviderFactory& _rFactory, ExceptionDisplayChain& _out_rChain ) + { + { + ExceptionDisplayChain empty; + _out_rChain.swap( empty ); + } + + SQLExceptionIteratorHelper iter( _rErrorInfo ); + while ( iter.hasMoreElements() ) + { + // current chain element + SQLExceptionInfo aCurrentElement; + iter.next( aCurrentElement ); + + const SQLException* pCurrentError = aCurrentElement; + OSL_ENSURE( pCurrentError, "lcl_buildExceptionChain: iterator failure!" ); + // hasMoreElements should not have returned <TRUE/> in this case + + ExceptionDisplayInfo aDisplayInfo( aCurrentElement.getType() ); + + aDisplayInfo.sMessage = pCurrentError->Message.trim(); + aDisplayInfo.sSQLState = pCurrentError->SQLState; + if ( pCurrentError->ErrorCode ) + aDisplayInfo.sErrorCode = OUString::number( pCurrentError->ErrorCode ); + + if ( aDisplayInfo.sMessage.isEmpty() + && !lcl_hasDetails( aDisplayInfo ) + ) + { + OSL_FAIL( "lcl_buildExceptionChain: useless exception: no state, no error code, no message!" ); + continue; + } + + aDisplayInfo.pImageProvider = _rFactory.getImageProvider( aCurrentElement.getType() ); + aDisplayInfo.pLabelProvider = _rFactory.getLabelProvider( aCurrentElement.getType(), false ); + + _out_rChain.push_back( aDisplayInfo ); + + if ( aCurrentElement.getType() == SQLExceptionInfo::TYPE::SQLContext ) + { + const SQLContext* pContext = aCurrentElement; + if ( !pContext->Details.isEmpty() ) + { + ExceptionDisplayInfo aSubInfo( aCurrentElement.getType() ); + + aSubInfo.sMessage = pContext->Details; + aSubInfo.pImageProvider = _rFactory.getImageProvider( aCurrentElement.getType() ); + aSubInfo.pLabelProvider = _rFactory.getLabelProvider( aCurrentElement.getType(), true ); + aSubInfo.bSubEntry = true; + + _out_rChain.push_back( aSubInfo ); + } + } + } + } + + void lcl_insertExceptionEntry(weld::TreeView& rList, size_t nElementPos, const ExceptionDisplayInfo& rEntry) + { + rList.append(OUString::number(nElementPos), rEntry.pLabelProvider->getLabel(), rEntry.pImageProvider->getImage()); + } +} + +namespace { + +class OExceptionChainDialog : public weld::GenericDialogController +{ + std::unique_ptr<weld::TreeView> m_xExceptionList; + std::unique_ptr<weld::TextView> m_xExceptionText; + + OUString m_sStatusLabel; + OUString m_sErrorCodeLabel; + + ExceptionDisplayChain m_aExceptions; + +public: + OExceptionChainDialog(weld::Window* pParent, const ExceptionDisplayChain& rExceptions); + +protected: + DECL_LINK(OnExceptionSelected, weld::TreeView&, void); +}; + +} + +OExceptionChainDialog::OExceptionChainDialog(weld::Window* pParent, const ExceptionDisplayChain& rExceptions) + : GenericDialogController(pParent, "dbaccess/ui/sqlexception.ui", "SQLExceptionDialog") + , m_xExceptionList(m_xBuilder->weld_tree_view("list")) + , m_xExceptionText(m_xBuilder->weld_text_view("description")) + , m_aExceptions(rExceptions) +{ + int nListWidth = m_xExceptionText->get_approximate_digit_width() * 28; + int nTextWidth = m_xExceptionText->get_approximate_digit_width() * 42; + int nHeight = m_xExceptionList->get_height_rows(6); + m_xExceptionList->set_size_request(nListWidth, nHeight); + m_xExceptionText->set_size_request(nTextWidth, nHeight); + + m_sStatusLabel = DBA_RES( STR_EXCEPTION_STATUS ); + m_sErrorCodeLabel = DBA_RES( STR_EXCEPTION_ERRORCODE ); + + m_xExceptionList->connect_changed(LINK(this, OExceptionChainDialog, OnExceptionSelected)); + + bool bHave22018 = false; + size_t elementPos = 0; + + for (auto const& elem : m_aExceptions) + { + lcl_insertExceptionEntry(*m_xExceptionList, elementPos, elem); + bHave22018 = elem.sSQLState == "22018"; + ++elementPos; + } + + // if the error has the code 22018, then add an additional explanation + // #i24021# + if ( bHave22018 ) + { + ProviderFactory aProviderFactory; + + ExceptionDisplayInfo aInfo22018; + aInfo22018.sMessage = DBA_RES( STR_EXPLAN_STRINGCONVERSION_ERROR ); + aInfo22018.pLabelProvider = aProviderFactory.getLabelProvider( SQLExceptionInfo::TYPE::SQLContext, false ); + aInfo22018.pImageProvider = aProviderFactory.getImageProvider( SQLExceptionInfo::TYPE::SQLContext ); + m_aExceptions.push_back( aInfo22018 ); + + lcl_insertExceptionEntry(*m_xExceptionList, m_aExceptions.size() - 1, aInfo22018); + } + + if (m_xExceptionList->n_children()) + { + m_xExceptionList->select(0); + OnExceptionSelected(*m_xExceptionList); + } +} + +IMPL_LINK_NOARG(OExceptionChainDialog, OnExceptionSelected, weld::TreeView&, void) +{ + OUString sText; + + OUString sId(m_xExceptionList->get_selected_id()); + if (!sId.isEmpty()) + { + const ExceptionDisplayInfo& aExceptionInfo(m_aExceptions[sId.toUInt32()]); + + if ( !aExceptionInfo.sSQLState.isEmpty() ) + { + sText += m_sStatusLabel + ": " + aExceptionInfo.sSQLState + "\n"; + } + + if ( !aExceptionInfo.sErrorCode.isEmpty() ) + { + sText += m_sErrorCodeLabel + ": " + aExceptionInfo.sErrorCode + "\n"; + } + + if ( !sText.isEmpty() ) + sText += "\n"; + + sText += aExceptionInfo.sMessage; + } + + m_xExceptionText->set_text(sText); +} + +// SQLMessageBox_Impl +struct SQLMessageBox_Impl +{ + ExceptionDisplayChain aDisplayInfo; + + explicit SQLMessageBox_Impl( const SQLExceptionInfo& _rExceptionInfo ) + { + // transform the exception chain to a form more suitable for displaying it here + ProviderFactory aProviderFactory; + lcl_buildExceptionChain( _rExceptionInfo, aProviderFactory, aDisplayInfo ); + } +}; + +namespace +{ + void lcl_addButton(weld::MessageDialog* pDialog, StandardButtonType eType, bool bDefault) + { + sal_uInt16 nButtonID = 0; + switch (eType) + { + case StandardButtonType::Yes: + nButtonID = RET_YES; + pDialog->add_button(GetStandardText(StandardButtonType::Yes), nButtonID); + break; + case StandardButtonType::No: + nButtonID = RET_NO; + pDialog->add_button(GetStandardText(StandardButtonType::No), nButtonID); + break; + case StandardButtonType::OK: + nButtonID = RET_OK; + pDialog->add_button(GetStandardText(StandardButtonType::OK), nButtonID); + break; + case StandardButtonType::Cancel: + nButtonID = RET_CANCEL; + pDialog->add_button(GetStandardText(StandardButtonType::Cancel), nButtonID); + break; + case StandardButtonType::Retry: + nButtonID = RET_RETRY; + pDialog->add_button(GetStandardText(StandardButtonType::Retry), nButtonID); + break; + case StandardButtonType::Help: + nButtonID = RET_HELP; + pDialog->add_button(GetStandardText(StandardButtonType::Help), nButtonID); + break; + default: + OSL_FAIL( "lcl_addButton: invalid button id!" ); + break; + } + if (bDefault) + pDialog->set_default_response(nButtonID); + } +} + +void OSQLMessageBox::impl_fillMessages() +{ + OSL_PRECOND( !m_pImpl->aDisplayInfo.empty(), "OSQLMessageBox::impl_fillMessages: nothing to display at all?" ); + + if ( m_pImpl->aDisplayInfo.empty() ) + return; + const ExceptionDisplayInfo* pSecondInfo = nullptr; + + const ExceptionDisplayInfo& rFirstInfo = *m_pImpl->aDisplayInfo.begin(); + if ( m_pImpl->aDisplayInfo.size() > 1 ) + pSecondInfo = &m_pImpl->aDisplayInfo[1]; + OUString sPrimary, sSecondary; + sPrimary = rFirstInfo.sMessage; + // one or two texts to display? + if ( pSecondInfo ) + { + // we show two elements in the main dialog if and only if one of + // - the first element in the chain is an SQLContext, and the second + // element denotes its sub entry + // - the first and the second element are both independent (i.e. the second + // is no sub entry), and none of them is a context. + bool bFirstElementIsContext = ( rFirstInfo.eType == SQLExceptionInfo::TYPE::SQLContext ); + bool bSecondElementIsContext = ( pSecondInfo->eType == SQLExceptionInfo::TYPE::SQLContext ); + + if ( bFirstElementIsContext && pSecondInfo->bSubEntry ) + sSecondary = pSecondInfo->sMessage; + if ( !bFirstElementIsContext && !bSecondElementIsContext ) + sSecondary = pSecondInfo->sMessage; + } + + // primary text + m_xDialog->set_primary_text(lcl_stripOOoBaseVendor(sPrimary)); + + // secondary text (if applicable) + m_xDialog->set_secondary_text(lcl_stripOOoBaseVendor(sSecondary)); +} + +void OSQLMessageBox::impl_createStandardButtons( MessBoxStyle _nStyle ) +{ + if ( _nStyle & MessBoxStyle::YesNoCancel ) + { + lcl_addButton(m_xDialog.get(), StandardButtonType::Yes, bool(_nStyle & MessBoxStyle::DefaultYes)); + lcl_addButton(m_xDialog.get(), StandardButtonType::No, bool(_nStyle & MessBoxStyle::DefaultNo)); + lcl_addButton(m_xDialog.get(), StandardButtonType::Cancel, bool(_nStyle & MessBoxStyle::DefaultCancel)); + } + else if ( _nStyle & MessBoxStyle::OkCancel ) + { + lcl_addButton(m_xDialog.get(), StandardButtonType::OK, bool(_nStyle & MessBoxStyle::DefaultOk)); + lcl_addButton(m_xDialog.get(), StandardButtonType::Cancel, bool(_nStyle & MessBoxStyle::DefaultCancel)); + } + else if ( _nStyle & MessBoxStyle::YesNo ) + { + lcl_addButton(m_xDialog.get(), StandardButtonType::Yes, bool(_nStyle & MessBoxStyle::DefaultYes)); + lcl_addButton(m_xDialog.get(), StandardButtonType::No, bool(_nStyle & MessBoxStyle::DefaultNo)); + } + else if ( _nStyle & MessBoxStyle::RetryCancel ) + { + lcl_addButton(m_xDialog.get(), StandardButtonType::Retry, bool(_nStyle & MessBoxStyle::DefaultRetry)); + lcl_addButton(m_xDialog.get(), StandardButtonType::Cancel, bool(_nStyle & MessBoxStyle::DefaultCancel)); + } + else if ( _nStyle & MessBoxStyle::Ok ) + { + lcl_addButton(m_xDialog.get(), StandardButtonType::OK, true); + } + + if ( m_sHelpURL.isEmpty() ) + return; + + lcl_addButton(m_xDialog.get(), StandardButtonType::Help, false); + + OUString aTmp; + INetURLObject aHID( m_sHelpURL ); + if ( aHID.GetProtocol() == INetProtocol::Hid ) + aTmp = aHID.GetURLPath(); + else + aTmp = m_sHelpURL; + + m_xDialog->set_help_id(OUStringToOString(aTmp, RTL_TEXTENCODING_UTF8)); +} + +void OSQLMessageBox::impl_addDetailsButton() +{ + size_t nFirstPageVisible = m_xDialog->get_secondary_text().isEmpty() ? 1 : 2; + + bool bMoreDetailsAvailable = m_pImpl->aDisplayInfo.size() > nFirstPageVisible; + if ( !bMoreDetailsAvailable ) + { + // even if the text fits into what we can display, we might need to details button + // if there is more non-trivial information in the errors than the mere messages + for (auto const& error : m_pImpl->aDisplayInfo) + { + if ( lcl_hasDetails(error) ) + { + bMoreDetailsAvailable = true; + break; + } + } + } + + if ( bMoreDetailsAvailable ) + { + m_xDialog->add_button(GetStandardText(StandardButtonType::More), RET_MORE); + m_xMoreButton.reset(m_xDialog->weld_widget_for_response(RET_MORE)); + m_xMoreButton->connect_clicked(LINK(this, OSQLMessageBox, ButtonClickHdl)); + } +} + +void OSQLMessageBox::Construct(weld::Window* pParent, MessBoxStyle _nStyle, MessageType _eImage) +{ + // init the image + MessageType eType( _eImage ); + if ( eType == AUTO ) + { + switch ( m_pImpl->aDisplayInfo[0].eType ) + { + case SQLExceptionInfo::TYPE::SQLException: eType = Error; break; + case SQLExceptionInfo::TYPE::SQLWarning: eType = Warning; break; + case SQLExceptionInfo::TYPE::SQLContext: eType = Info; break; + default: OSL_FAIL( "OSQLMessageBox::Construct: invalid type!" ); + } + } + VclMessageType eMessageType; + switch (eType) + { + default: + OSL_FAIL( "OSQLMessageBox::impl_initImage: unsupported image type!" ); + [[fallthrough]]; + case Info: + eMessageType = VclMessageType::Info; + break; + case Warning: + eMessageType = VclMessageType::Warning; + break; + case Error: + eMessageType = VclMessageType::Error; + break; + case Query: + eMessageType = VclMessageType::Question; + break; + } + + m_xDialog.reset(Application::CreateMessageDialog(pParent, eMessageType, VclButtonsType::NONE, "")); + m_xDialog->set_title(utl::ConfigManager::getProductName() + " Base"); + + impl_fillMessages(); + + // create buttons + impl_createStandardButtons( _nStyle ); + impl_addDetailsButton(); +} + +OSQLMessageBox::OSQLMessageBox(weld::Window* pParent, const SQLExceptionInfo& rException, MessBoxStyle nStyle, const OUString& rHelpURL) + : m_pImpl(new SQLMessageBox_Impl(rException)) + , m_sHelpURL(rHelpURL) +{ + Construct(pParent, nStyle, AUTO); +} + +OSQLMessageBox::OSQLMessageBox(weld::Window* pParent, const OUString& rTitle, const OUString& rMessage, MessBoxStyle nStyle, MessageType eType, const ::dbtools::SQLExceptionInfo* pAdditionalErrorInfo ) +{ + SQLContext aError; + aError.Message = rTitle; + aError.Details = rMessage; + if (pAdditionalErrorInfo) + aError.NextException = pAdditionalErrorInfo->get(); + + m_pImpl.reset(new SQLMessageBox_Impl(SQLExceptionInfo(aError))); + + Construct(pParent, nStyle, eType); +} + +OSQLMessageBox::~OSQLMessageBox() +{ +} + +IMPL_LINK_NOARG(OSQLMessageBox, ButtonClickHdl, weld::Button&, void) +{ + OExceptionChainDialog aDlg(m_xDialog.get(), m_pImpl->aDisplayInfo); + aDlg.run(); +} + +// OSQLWarningBox +OSQLWarningBox::OSQLWarningBox(weld::Window* pParent, const OUString& rMessage, MessBoxStyle nStyle, + const ::dbtools::SQLExceptionInfo* pAdditionalErrorInfo ) + : OSQLMessageBox(pParent, DBA_RES(STR_EXCEPTION_WARNING), rMessage, nStyle, MessageType::Warning, pAdditionalErrorInfo) +{ +} + +// OSQLErrorBox +OSQLErrorBox::OSQLErrorBox(weld::Window* pParent, const OUString& rMessage) + : OSQLMessageBox(pParent, DBA_RES(STR_EXCEPTION_ERROR), rMessage, MessBoxStyle::Ok | MessBoxStyle::DefaultOk, + MessageType::Error, nullptr) +{ +} + +} // namespace dbaui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |