1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <core_resource.hxx>
21 #include <sqlmessage.hxx>
22 #include <strings.hrc>
23 #include <com/sun/star/sdbc/SQLException.hpp>
24 #include <com/sun/star/sdb/SQLContext.hpp>
26 #include <vcl/stdtext.hxx>
27 #include <vcl/svapp.hxx>
28 #include <vcl/weld.hxx>
29 #include <osl/diagnose.h>
30 #include <connectivity/dbexception.hxx>
31 #include <connectivity/sqlerror.hxx>
32 #include <unotools/configmgr.hxx>
34 #include <tools/urlobj.hxx>
36 #define RET_MORE RET_RETRY + 1
38 using namespace dbtools
;
39 using namespace com::sun::star::uno
;
40 using namespace com::sun::star::sdb
;
41 using namespace com::sun::star::sdbc
;
51 OUString m_defaultImageID
;
54 explicit ImageProvider(OUString defaultImageID
)
55 : m_defaultImageID(std::move(defaultImageID
))
59 const OUString
& getImage() const
61 return m_defaultImageID
;
70 explicit LabelProvider(TranslateId labelResourceID
)
71 : m_label(DBA_RES(labelResourceID
))
75 const OUString
& getLabel() const
84 mutable std::shared_ptr
< ImageProvider
> m_pErrorImage
;
85 mutable std::shared_ptr
< ImageProvider
> m_pWarningsImage
;
86 mutable std::shared_ptr
< ImageProvider
> m_pInfoImage
;
87 mutable std::shared_ptr
< LabelProvider
> m_pErrorLabel
;
88 mutable std::shared_ptr
< LabelProvider
> m_pWarningsLabel
;
89 mutable std::shared_ptr
< LabelProvider
> m_pInfoLabel
;
96 std::shared_ptr
< ImageProvider
> const & getImageProvider( SQLExceptionInfo::TYPE _eType
) const
98 std::shared_ptr
< ImageProvider
>* ppProvider( &m_pErrorImage
);
99 OUString
sNormalImageID(u
"dialog-error"_ustr
);
103 case SQLExceptionInfo::TYPE::SQLWarning
:
104 ppProvider
= &m_pWarningsImage
;
105 sNormalImageID
= "dialog-warning";
108 case SQLExceptionInfo::TYPE::SQLContext
:
109 ppProvider
= &m_pInfoImage
;
110 sNormalImageID
= "dialog-information";
117 if ( !ppProvider
->get() )
118 (*ppProvider
) = std::make_shared
<ImageProvider
>(sNormalImageID
);
122 std::shared_ptr
< LabelProvider
> const & getLabelProvider( SQLExceptionInfo::TYPE _eType
, bool _bSubLabel
) const
124 std::shared_ptr
< LabelProvider
>* ppProvider( &m_pErrorLabel
);
125 TranslateId
pLabelID( STR_EXCEPTION_ERROR
);
129 case SQLExceptionInfo::TYPE::SQLWarning
:
130 ppProvider
= &m_pWarningsLabel
;
131 pLabelID
= STR_EXCEPTION_WARNING
;
134 case SQLExceptionInfo::TYPE::SQLContext
:
135 ppProvider
= &m_pInfoLabel
;
136 pLabelID
= _bSubLabel
? STR_EXCEPTION_DETAILS
: STR_EXCEPTION_INFO
;
142 if ( !ppProvider
->get() )
143 (*ppProvider
) = std::make_shared
<LabelProvider
>( pLabelID
);
149 /// a stripped version of the SQLException, packed for displaying
150 struct ExceptionDisplayInfo
152 SQLExceptionInfo::TYPE eType
;
154 std::shared_ptr
< ImageProvider
> pImageProvider
;
155 std::shared_ptr
< LabelProvider
> pLabelProvider
;
163 ExceptionDisplayInfo() : eType( SQLExceptionInfo::TYPE::Undefined
), bSubEntry( false ) { }
164 explicit ExceptionDisplayInfo( SQLExceptionInfo::TYPE _eType
) : eType( _eType
), bSubEntry( false ) { }
167 bool lcl_hasDetails( const ExceptionDisplayInfo
& _displayInfo
)
169 return ( !_displayInfo
.sErrorCode
.isEmpty() )
170 || ( !_displayInfo
.sSQLState
.isEmpty()
171 && _displayInfo
.sSQLState
!= "S1000"
175 typedef std::vector
< ExceptionDisplayInfo
> ExceptionDisplayChain
;
177 /// strips the [OOoBase] vendor identifier from the given error message, if applicable
178 OUString
lcl_stripOOoBaseVendor( const OUString
& _rErrorMessage
)
180 OUString
sErrorMessage( _rErrorMessage
);
182 const OUString
sVendorIdentifier( ::connectivity::SQLError::getMessagePrefix() );
183 if ( sErrorMessage
.startsWith( sVendorIdentifier
) )
185 // characters to strip
186 sal_Int32
nStripLen( sVendorIdentifier
.getLength() );
187 // usually, there should be a whitespace between the vendor and the real message
188 while ( ( sErrorMessage
.getLength() > nStripLen
)
189 && ( sErrorMessage
[nStripLen
] == ' ' )
192 sErrorMessage
= sErrorMessage
.copy( nStripLen
);
195 return sErrorMessage
;
198 void lcl_buildExceptionChain( const SQLExceptionInfo
& _rErrorInfo
, const ProviderFactory
& _rFactory
, ExceptionDisplayChain
& _out_rChain
)
200 ExceptionDisplayChain().swap(_out_rChain
);
202 SQLExceptionIteratorHelper
iter( _rErrorInfo
);
203 while ( iter
.hasMoreElements() )
205 // current chain element
206 SQLExceptionInfo aCurrentElement
;
207 iter
.next( aCurrentElement
);
209 const SQLException
* pCurrentError
= aCurrentElement
;
210 assert(pCurrentError
&& "lcl_buildExceptionChain: iterator failure!");
211 // hasMoreElements should not have returned <TRUE/> in this case
213 ExceptionDisplayInfo
aDisplayInfo( aCurrentElement
.getType() );
215 aDisplayInfo
.sMessage
= pCurrentError
->Message
.trim();
216 aDisplayInfo
.sSQLState
= pCurrentError
->SQLState
;
217 if ( pCurrentError
->ErrorCode
)
218 aDisplayInfo
.sErrorCode
= OUString::number( pCurrentError
->ErrorCode
);
220 if ( aDisplayInfo
.sMessage
.isEmpty()
221 && !lcl_hasDetails( aDisplayInfo
)
224 OSL_FAIL( "lcl_buildExceptionChain: useless exception: no state, no error code, no message!" );
228 aDisplayInfo
.pImageProvider
= _rFactory
.getImageProvider( aCurrentElement
.getType() );
229 aDisplayInfo
.pLabelProvider
= _rFactory
.getLabelProvider( aCurrentElement
.getType(), false );
231 _out_rChain
.push_back( aDisplayInfo
);
233 if ( aCurrentElement
.getType() == SQLExceptionInfo::TYPE::SQLContext
)
235 const SQLContext
* pContext
= aCurrentElement
;
236 if ( !pContext
->Details
.isEmpty() )
238 ExceptionDisplayInfo
aSubInfo( aCurrentElement
.getType() );
240 aSubInfo
.sMessage
= pContext
->Details
;
241 aSubInfo
.pImageProvider
= _rFactory
.getImageProvider( aCurrentElement
.getType() );
242 aSubInfo
.pLabelProvider
= _rFactory
.getLabelProvider( aCurrentElement
.getType(), true );
243 aSubInfo
.bSubEntry
= true;
245 _out_rChain
.push_back( aSubInfo
);
251 void lcl_insertExceptionEntry(weld::TreeView
& rList
, size_t nElementPos
, const ExceptionDisplayInfo
& rEntry
)
253 rList
.append(OUString::number(nElementPos
), rEntry
.pLabelProvider
->getLabel(), rEntry
.pImageProvider
->getImage());
259 class OExceptionChainDialog
: public weld::GenericDialogController
261 std::unique_ptr
<weld::TreeView
> m_xExceptionList
;
262 std::unique_ptr
<weld::TextView
> m_xExceptionText
;
264 OUString m_sStatusLabel
;
265 OUString m_sErrorCodeLabel
;
267 ExceptionDisplayChain m_aExceptions
;
270 OExceptionChainDialog(weld::Window
* pParent
, ExceptionDisplayChain
&& rExceptions
);
273 DECL_LINK(OnExceptionSelected
, weld::TreeView
&, void);
278 OExceptionChainDialog::OExceptionChainDialog(weld::Window
* pParent
, ExceptionDisplayChain
&& rExceptions
)
279 : GenericDialogController(pParent
, u
"dbaccess/ui/sqlexception.ui"_ustr
, u
"SQLExceptionDialog"_ustr
)
280 , m_xExceptionList(m_xBuilder
->weld_tree_view(u
"list"_ustr
))
281 , m_xExceptionText(m_xBuilder
->weld_text_view(u
"description"_ustr
))
282 , m_aExceptions(std::move(rExceptions
))
284 int nListWidth
= m_xExceptionText
->get_approximate_digit_width() * 28;
285 int nTextWidth
= m_xExceptionText
->get_approximate_digit_width() * 42;
286 int nHeight
= m_xExceptionList
->get_height_rows(6);
287 m_xExceptionList
->set_size_request(nListWidth
, nHeight
);
288 m_xExceptionText
->set_size_request(nTextWidth
, nHeight
);
290 m_sStatusLabel
= DBA_RES( STR_EXCEPTION_STATUS
);
291 m_sErrorCodeLabel
= DBA_RES( STR_EXCEPTION_ERRORCODE
);
293 m_xExceptionList
->connect_selection_changed(
294 LINK(this, OExceptionChainDialog
, OnExceptionSelected
));
296 bool bHave22018
= false;
297 size_t elementPos
= 0;
299 for (auto const& elem
: m_aExceptions
)
301 lcl_insertExceptionEntry(*m_xExceptionList
, elementPos
, elem
);
302 bHave22018
= elem
.sSQLState
== "22018";
306 // if the error has the code 22018, then add an additional explanation
310 ProviderFactory aProviderFactory
;
312 ExceptionDisplayInfo aInfo22018
;
313 aInfo22018
.sMessage
= DBA_RES( STR_EXPLAN_STRINGCONVERSION_ERROR
);
314 aInfo22018
.pLabelProvider
= aProviderFactory
.getLabelProvider( SQLExceptionInfo::TYPE::SQLContext
, false );
315 aInfo22018
.pImageProvider
= aProviderFactory
.getImageProvider( SQLExceptionInfo::TYPE::SQLContext
);
316 m_aExceptions
.push_back( aInfo22018
);
318 lcl_insertExceptionEntry(*m_xExceptionList
, m_aExceptions
.size() - 1, aInfo22018
);
321 if (m_xExceptionList
->n_children())
323 m_xExceptionList
->select(0);
324 OnExceptionSelected(*m_xExceptionList
);
328 IMPL_LINK_NOARG(OExceptionChainDialog
, OnExceptionSelected
, weld::TreeView
&, void)
332 OUString
sId(m_xExceptionList
->get_selected_id());
335 const ExceptionDisplayInfo
& aExceptionInfo(m_aExceptions
[sId
.toUInt32()]);
337 if ( !aExceptionInfo
.sSQLState
.isEmpty() )
339 sText
+= m_sStatusLabel
+ ": " + aExceptionInfo
.sSQLState
+ "\n";
342 if ( !aExceptionInfo
.sErrorCode
.isEmpty() )
344 sText
+= m_sErrorCodeLabel
+ ": " + aExceptionInfo
.sErrorCode
+ "\n";
347 if ( !sText
.isEmpty() )
350 sText
+= aExceptionInfo
.sMessage
;
353 m_xExceptionText
->set_text(sText
);
356 // SQLMessageBox_Impl
357 struct SQLMessageBox_Impl
359 ExceptionDisplayChain aDisplayInfo
;
361 explicit SQLMessageBox_Impl( const SQLExceptionInfo
& _rExceptionInfo
)
363 // transform the exception chain to a form more suitable for displaying it here
364 ProviderFactory aProviderFactory
;
365 lcl_buildExceptionChain( _rExceptionInfo
, aProviderFactory
, aDisplayInfo
);
371 void lcl_addButton(weld::MessageDialog
* pDialog
, StandardButtonType eType
, bool bDefault
)
373 sal_uInt16 nButtonID
= 0;
376 case StandardButtonType::Yes
:
378 pDialog
->add_button(GetStandardText(StandardButtonType::Yes
), nButtonID
);
380 case StandardButtonType::No
:
382 pDialog
->add_button(GetStandardText(StandardButtonType::No
), nButtonID
);
384 case StandardButtonType::OK
:
386 pDialog
->add_button(GetStandardText(StandardButtonType::OK
), nButtonID
);
388 case StandardButtonType::Cancel
:
389 nButtonID
= RET_CANCEL
;
390 pDialog
->add_button(GetStandardText(StandardButtonType::Cancel
), nButtonID
);
392 case StandardButtonType::Retry
:
393 nButtonID
= RET_RETRY
;
394 pDialog
->add_button(GetStandardText(StandardButtonType::Retry
), nButtonID
);
396 case StandardButtonType::Help
:
397 nButtonID
= RET_HELP
;
398 pDialog
->add_button(GetStandardText(StandardButtonType::Help
), nButtonID
);
401 OSL_FAIL( "lcl_addButton: invalid button id!" );
405 pDialog
->set_default_response(nButtonID
);
409 void OSQLMessageBox::impl_fillMessages()
411 OSL_PRECOND( !m_pImpl
->aDisplayInfo
.empty(), "OSQLMessageBox::impl_fillMessages: nothing to display at all?" );
413 if ( m_pImpl
->aDisplayInfo
.empty() )
415 const ExceptionDisplayInfo
* pSecondInfo
= nullptr;
417 const ExceptionDisplayInfo
& rFirstInfo
= *m_pImpl
->aDisplayInfo
.begin();
418 if ( m_pImpl
->aDisplayInfo
.size() > 1 )
419 pSecondInfo
= &m_pImpl
->aDisplayInfo
[1];
420 OUString sPrimary
, sSecondary
;
421 sPrimary
= rFirstInfo
.sMessage
;
422 // one or two texts to display?
425 // we show two elements in the main dialog if and only if one of
426 // - the first element in the chain is an SQLContext, and the second
427 // element denotes its sub entry
428 // - the first and the second element are both independent (i.e. the second
429 // is no sub entry), and none of them is a context.
430 bool bFirstElementIsContext
= ( rFirstInfo
.eType
== SQLExceptionInfo::TYPE::SQLContext
);
431 bool bSecondElementIsContext
= ( pSecondInfo
->eType
== SQLExceptionInfo::TYPE::SQLContext
);
433 if ( bFirstElementIsContext
&& pSecondInfo
->bSubEntry
)
434 sSecondary
= pSecondInfo
->sMessage
;
435 if ( !bFirstElementIsContext
&& !bSecondElementIsContext
)
436 sSecondary
= pSecondInfo
->sMessage
;
440 m_xDialog
->set_primary_text(lcl_stripOOoBaseVendor(sPrimary
));
442 // secondary text (if applicable)
443 m_xDialog
->set_secondary_text(lcl_stripOOoBaseVendor(sSecondary
));
446 void OSQLMessageBox::impl_createStandardButtons( MessBoxStyle _nStyle
)
448 if ( _nStyle
& MessBoxStyle::YesNoCancel
)
450 lcl_addButton(m_xDialog
.get(), StandardButtonType::Yes
, bool(_nStyle
& MessBoxStyle::DefaultYes
));
451 lcl_addButton(m_xDialog
.get(), StandardButtonType::No
, bool(_nStyle
& MessBoxStyle::DefaultNo
));
452 lcl_addButton(m_xDialog
.get(), StandardButtonType::Cancel
, bool(_nStyle
& MessBoxStyle::DefaultCancel
));
454 else if ( _nStyle
& MessBoxStyle::OkCancel
)
456 lcl_addButton(m_xDialog
.get(), StandardButtonType::OK
, bool(_nStyle
& MessBoxStyle::DefaultOk
));
457 lcl_addButton(m_xDialog
.get(), StandardButtonType::Cancel
, bool(_nStyle
& MessBoxStyle::DefaultCancel
));
459 else if ( _nStyle
& MessBoxStyle::YesNo
)
461 lcl_addButton(m_xDialog
.get(), StandardButtonType::Yes
, bool(_nStyle
& MessBoxStyle::DefaultYes
));
462 lcl_addButton(m_xDialog
.get(), StandardButtonType::No
, bool(_nStyle
& MessBoxStyle::DefaultNo
));
464 else if ( _nStyle
& MessBoxStyle::RetryCancel
)
466 lcl_addButton(m_xDialog
.get(), StandardButtonType::Retry
, bool(_nStyle
& MessBoxStyle::DefaultRetry
));
467 lcl_addButton(m_xDialog
.get(), StandardButtonType::Cancel
, bool(_nStyle
& MessBoxStyle::DefaultCancel
));
469 else if ( _nStyle
& MessBoxStyle::Ok
)
471 lcl_addButton(m_xDialog
.get(), StandardButtonType::OK
, true);
474 if ( m_sHelpURL
.isEmpty() )
477 lcl_addButton(m_xDialog
.get(), StandardButtonType::Help
, false);
480 INetURLObject
aHID( m_sHelpURL
);
481 if ( aHID
.GetProtocol() == INetProtocol::Hid
)
482 aTmp
= aHID
.GetURLPath();
486 m_xDialog
->set_help_id(aTmp
);
489 void OSQLMessageBox::impl_addDetailsButton()
491 size_t nFirstPageVisible
= m_xDialog
->get_secondary_text().isEmpty() ? 1 : 2;
493 bool bMoreDetailsAvailable
= m_pImpl
->aDisplayInfo
.size() > nFirstPageVisible
;
494 if ( !bMoreDetailsAvailable
)
496 // even if the text fits into what we can display, we might need to details button
497 // if there is more non-trivial information in the errors than the mere messages
498 for (auto const& error
: m_pImpl
->aDisplayInfo
)
500 if ( lcl_hasDetails(error
) )
502 bMoreDetailsAvailable
= true;
508 if ( bMoreDetailsAvailable
)
510 m_xDialog
->add_button(GetStandardText(StandardButtonType::More
), RET_MORE
);
511 m_xMoreButton
= m_xDialog
->weld_button_for_response(RET_MORE
);
512 m_xMoreButton
->connect_clicked(LINK(this, OSQLMessageBox
, ButtonClickHdl
));
516 void OSQLMessageBox::Construct(weld::Window
* pParent
, MessBoxStyle _nStyle
, MessageType _eImage
)
519 MessageType
eType( _eImage
);
522 switch ( m_pImpl
->aDisplayInfo
[0].eType
)
524 case SQLExceptionInfo::TYPE::SQLException
: eType
= Error
; break;
525 case SQLExceptionInfo::TYPE::SQLWarning
: eType
= Warning
; break;
526 case SQLExceptionInfo::TYPE::SQLContext
: eType
= Info
; break;
527 default: OSL_FAIL( "OSQLMessageBox::Construct: invalid type!" );
530 VclMessageType eMessageType
;
534 OSL_FAIL( "OSQLMessageBox::impl_initImage: unsupported image type!" );
537 eMessageType
= VclMessageType::Info
;
540 eMessageType
= VclMessageType::Warning
;
543 eMessageType
= VclMessageType::Error
;
546 eMessageType
= VclMessageType::Question
;
550 m_xDialog
.reset(Application::CreateMessageDialog(pParent
, eMessageType
, VclButtonsType::NONE
, u
""_ustr
));
551 m_xDialog
->set_title(utl::ConfigManager::getProductName() + " Base");
556 impl_createStandardButtons( _nStyle
);
557 impl_addDetailsButton();
560 OSQLMessageBox::OSQLMessageBox(weld::Window
* pParent
, const SQLExceptionInfo
& rException
, MessBoxStyle nStyle
, OUString sHelpURL
)
561 : m_pImpl(new SQLMessageBox_Impl(rException
))
562 , m_sHelpURL(std::move(sHelpURL
))
564 Construct(pParent
, nStyle
, AUTO
);
567 OSQLMessageBox::OSQLMessageBox(weld::Window
* pParent
, const OUString
& rTitle
, const OUString
& rMessage
, MessBoxStyle nStyle
, MessageType eType
, const ::dbtools::SQLExceptionInfo
* pAdditionalErrorInfo
)
570 if (pAdditionalErrorInfo
)
571 next
= pAdditionalErrorInfo
->get();
572 SQLContext
aError(rTitle
, {}, {}, 0, next
, rMessage
);
574 m_pImpl
.reset(new SQLMessageBox_Impl(SQLExceptionInfo(aError
)));
576 Construct(pParent
, nStyle
, eType
);
579 OSQLMessageBox::~OSQLMessageBox()
583 IMPL_LINK_NOARG(OSQLMessageBox
, ButtonClickHdl
, weld::Button
&, void)
585 OExceptionChainDialog
aDlg(m_xDialog
.get(), std::vector(m_pImpl
->aDisplayInfo
));
590 OSQLWarningBox::OSQLWarningBox(weld::Window
* pParent
, const OUString
& rMessage
, MessBoxStyle nStyle
,
591 const ::dbtools::SQLExceptionInfo
* pAdditionalErrorInfo
)
592 : OSQLMessageBox(pParent
, DBA_RES(STR_EXCEPTION_WARNING
), rMessage
, nStyle
, MessageType::Warning
, pAdditionalErrorInfo
)
597 OSQLErrorBox::OSQLErrorBox(weld::Window
* pParent
, const OUString
& rMessage
)
598 : OSQLMessageBox(pParent
, DBA_RES(STR_EXCEPTION_ERROR
), rMessage
, MessBoxStyle::Ok
| MessBoxStyle::DefaultOk
,
599 MessageType::Error
, nullptr)
605 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */