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("dialog-error");
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 OSL_ENSURE( 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
, "dbaccess/ui/sqlexception.ui", "SQLExceptionDialog")
280 , m_xExceptionList(m_xBuilder
->weld_tree_view("list"))
281 , m_xExceptionText(m_xBuilder
->weld_text_view("description"))
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_changed(LINK(this, OExceptionChainDialog
, OnExceptionSelected
));
295 bool bHave22018
= false;
296 size_t elementPos
= 0;
298 for (auto const& elem
: m_aExceptions
)
300 lcl_insertExceptionEntry(*m_xExceptionList
, elementPos
, elem
);
301 bHave22018
= elem
.sSQLState
== "22018";
305 // if the error has the code 22018, then add an additional explanation
309 ProviderFactory aProviderFactory
;
311 ExceptionDisplayInfo aInfo22018
;
312 aInfo22018
.sMessage
= DBA_RES( STR_EXPLAN_STRINGCONVERSION_ERROR
);
313 aInfo22018
.pLabelProvider
= aProviderFactory
.getLabelProvider( SQLExceptionInfo::TYPE::SQLContext
, false );
314 aInfo22018
.pImageProvider
= aProviderFactory
.getImageProvider( SQLExceptionInfo::TYPE::SQLContext
);
315 m_aExceptions
.push_back( aInfo22018
);
317 lcl_insertExceptionEntry(*m_xExceptionList
, m_aExceptions
.size() - 1, aInfo22018
);
320 if (m_xExceptionList
->n_children())
322 m_xExceptionList
->select(0);
323 OnExceptionSelected(*m_xExceptionList
);
327 IMPL_LINK_NOARG(OExceptionChainDialog
, OnExceptionSelected
, weld::TreeView
&, void)
331 OUString
sId(m_xExceptionList
->get_selected_id());
334 const ExceptionDisplayInfo
& aExceptionInfo(m_aExceptions
[sId
.toUInt32()]);
336 if ( !aExceptionInfo
.sSQLState
.isEmpty() )
338 sText
+= m_sStatusLabel
+ ": " + aExceptionInfo
.sSQLState
+ "\n";
341 if ( !aExceptionInfo
.sErrorCode
.isEmpty() )
343 sText
+= m_sErrorCodeLabel
+ ": " + aExceptionInfo
.sErrorCode
+ "\n";
346 if ( !sText
.isEmpty() )
349 sText
+= aExceptionInfo
.sMessage
;
352 m_xExceptionText
->set_text(sText
);
355 // SQLMessageBox_Impl
356 struct SQLMessageBox_Impl
358 ExceptionDisplayChain aDisplayInfo
;
360 explicit SQLMessageBox_Impl( const SQLExceptionInfo
& _rExceptionInfo
)
362 // transform the exception chain to a form more suitable for displaying it here
363 ProviderFactory aProviderFactory
;
364 lcl_buildExceptionChain( _rExceptionInfo
, aProviderFactory
, aDisplayInfo
);
370 void lcl_addButton(weld::MessageDialog
* pDialog
, StandardButtonType eType
, bool bDefault
)
372 sal_uInt16 nButtonID
= 0;
375 case StandardButtonType::Yes
:
377 pDialog
->add_button(GetStandardText(StandardButtonType::Yes
), nButtonID
);
379 case StandardButtonType::No
:
381 pDialog
->add_button(GetStandardText(StandardButtonType::No
), nButtonID
);
383 case StandardButtonType::OK
:
385 pDialog
->add_button(GetStandardText(StandardButtonType::OK
), nButtonID
);
387 case StandardButtonType::Cancel
:
388 nButtonID
= RET_CANCEL
;
389 pDialog
->add_button(GetStandardText(StandardButtonType::Cancel
), nButtonID
);
391 case StandardButtonType::Retry
:
392 nButtonID
= RET_RETRY
;
393 pDialog
->add_button(GetStandardText(StandardButtonType::Retry
), nButtonID
);
395 case StandardButtonType::Help
:
396 nButtonID
= RET_HELP
;
397 pDialog
->add_button(GetStandardText(StandardButtonType::Help
), nButtonID
);
400 OSL_FAIL( "lcl_addButton: invalid button id!" );
404 pDialog
->set_default_response(nButtonID
);
408 void OSQLMessageBox::impl_fillMessages()
410 OSL_PRECOND( !m_pImpl
->aDisplayInfo
.empty(), "OSQLMessageBox::impl_fillMessages: nothing to display at all?" );
412 if ( m_pImpl
->aDisplayInfo
.empty() )
414 const ExceptionDisplayInfo
* pSecondInfo
= nullptr;
416 const ExceptionDisplayInfo
& rFirstInfo
= *m_pImpl
->aDisplayInfo
.begin();
417 if ( m_pImpl
->aDisplayInfo
.size() > 1 )
418 pSecondInfo
= &m_pImpl
->aDisplayInfo
[1];
419 OUString sPrimary
, sSecondary
;
420 sPrimary
= rFirstInfo
.sMessage
;
421 // one or two texts to display?
424 // we show two elements in the main dialog if and only if one of
425 // - the first element in the chain is an SQLContext, and the second
426 // element denotes its sub entry
427 // - the first and the second element are both independent (i.e. the second
428 // is no sub entry), and none of them is a context.
429 bool bFirstElementIsContext
= ( rFirstInfo
.eType
== SQLExceptionInfo::TYPE::SQLContext
);
430 bool bSecondElementIsContext
= ( pSecondInfo
->eType
== SQLExceptionInfo::TYPE::SQLContext
);
432 if ( bFirstElementIsContext
&& pSecondInfo
->bSubEntry
)
433 sSecondary
= pSecondInfo
->sMessage
;
434 if ( !bFirstElementIsContext
&& !bSecondElementIsContext
)
435 sSecondary
= pSecondInfo
->sMessage
;
439 m_xDialog
->set_primary_text(lcl_stripOOoBaseVendor(sPrimary
));
441 // secondary text (if applicable)
442 m_xDialog
->set_secondary_text(lcl_stripOOoBaseVendor(sSecondary
));
445 void OSQLMessageBox::impl_createStandardButtons( MessBoxStyle _nStyle
)
447 if ( _nStyle
& MessBoxStyle::YesNoCancel
)
449 lcl_addButton(m_xDialog
.get(), StandardButtonType::Yes
, bool(_nStyle
& MessBoxStyle::DefaultYes
));
450 lcl_addButton(m_xDialog
.get(), StandardButtonType::No
, bool(_nStyle
& MessBoxStyle::DefaultNo
));
451 lcl_addButton(m_xDialog
.get(), StandardButtonType::Cancel
, bool(_nStyle
& MessBoxStyle::DefaultCancel
));
453 else if ( _nStyle
& MessBoxStyle::OkCancel
)
455 lcl_addButton(m_xDialog
.get(), StandardButtonType::OK
, bool(_nStyle
& MessBoxStyle::DefaultOk
));
456 lcl_addButton(m_xDialog
.get(), StandardButtonType::Cancel
, bool(_nStyle
& MessBoxStyle::DefaultCancel
));
458 else if ( _nStyle
& MessBoxStyle::YesNo
)
460 lcl_addButton(m_xDialog
.get(), StandardButtonType::Yes
, bool(_nStyle
& MessBoxStyle::DefaultYes
));
461 lcl_addButton(m_xDialog
.get(), StandardButtonType::No
, bool(_nStyle
& MessBoxStyle::DefaultNo
));
463 else if ( _nStyle
& MessBoxStyle::RetryCancel
)
465 lcl_addButton(m_xDialog
.get(), StandardButtonType::Retry
, bool(_nStyle
& MessBoxStyle::DefaultRetry
));
466 lcl_addButton(m_xDialog
.get(), StandardButtonType::Cancel
, bool(_nStyle
& MessBoxStyle::DefaultCancel
));
468 else if ( _nStyle
& MessBoxStyle::Ok
)
470 lcl_addButton(m_xDialog
.get(), StandardButtonType::OK
, true);
473 if ( m_sHelpURL
.isEmpty() )
476 lcl_addButton(m_xDialog
.get(), StandardButtonType::Help
, false);
479 INetURLObject
aHID( m_sHelpURL
);
480 if ( aHID
.GetProtocol() == INetProtocol::Hid
)
481 aTmp
= aHID
.GetURLPath();
485 m_xDialog
->set_help_id(aTmp
);
488 void OSQLMessageBox::impl_addDetailsButton()
490 size_t nFirstPageVisible
= m_xDialog
->get_secondary_text().isEmpty() ? 1 : 2;
492 bool bMoreDetailsAvailable
= m_pImpl
->aDisplayInfo
.size() > nFirstPageVisible
;
493 if ( !bMoreDetailsAvailable
)
495 // even if the text fits into what we can display, we might need to details button
496 // if there is more non-trivial information in the errors than the mere messages
497 for (auto const& error
: m_pImpl
->aDisplayInfo
)
499 if ( lcl_hasDetails(error
) )
501 bMoreDetailsAvailable
= true;
507 if ( bMoreDetailsAvailable
)
509 m_xDialog
->add_button(GetStandardText(StandardButtonType::More
), RET_MORE
);
510 m_xMoreButton
.reset(m_xDialog
->weld_widget_for_response(RET_MORE
));
511 m_xMoreButton
->connect_clicked(LINK(this, OSQLMessageBox
, ButtonClickHdl
));
515 void OSQLMessageBox::Construct(weld::Window
* pParent
, MessBoxStyle _nStyle
, MessageType _eImage
)
518 MessageType
eType( _eImage
);
521 switch ( m_pImpl
->aDisplayInfo
[0].eType
)
523 case SQLExceptionInfo::TYPE::SQLException
: eType
= Error
; break;
524 case SQLExceptionInfo::TYPE::SQLWarning
: eType
= Warning
; break;
525 case SQLExceptionInfo::TYPE::SQLContext
: eType
= Info
; break;
526 default: OSL_FAIL( "OSQLMessageBox::Construct: invalid type!" );
529 VclMessageType eMessageType
;
533 OSL_FAIL( "OSQLMessageBox::impl_initImage: unsupported image type!" );
536 eMessageType
= VclMessageType::Info
;
539 eMessageType
= VclMessageType::Warning
;
542 eMessageType
= VclMessageType::Error
;
545 eMessageType
= VclMessageType::Question
;
549 m_xDialog
.reset(Application::CreateMessageDialog(pParent
, eMessageType
, VclButtonsType::NONE
, ""));
550 m_xDialog
->set_title(utl::ConfigManager::getProductName() + " Base");
555 impl_createStandardButtons( _nStyle
);
556 impl_addDetailsButton();
559 OSQLMessageBox::OSQLMessageBox(weld::Window
* pParent
, const SQLExceptionInfo
& rException
, MessBoxStyle nStyle
, OUString sHelpURL
)
560 : m_pImpl(new SQLMessageBox_Impl(rException
))
561 , m_sHelpURL(std::move(sHelpURL
))
563 Construct(pParent
, nStyle
, AUTO
);
566 OSQLMessageBox::OSQLMessageBox(weld::Window
* pParent
, const OUString
& rTitle
, const OUString
& rMessage
, MessBoxStyle nStyle
, MessageType eType
, const ::dbtools::SQLExceptionInfo
* pAdditionalErrorInfo
)
569 aError
.Message
= rTitle
;
570 aError
.Details
= rMessage
;
571 if (pAdditionalErrorInfo
)
572 aError
.NextException
= pAdditionalErrorInfo
->get();
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: */