weld UpdateDialog

Change-Id: Ieca75774925a8766162481713f6e8a6ba0e9feb0
Reviewed-on: https://gerrit.libreoffice.org/68396
Tested-by: Jenkins
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
Tested-by: Caolán McNamara <caolanm@redhat.com>
diff --git a/desktop/inc/bitmaps.hlst b/desktop/inc/bitmaps.hlst
index d9dbc0c..7a166d5 100644
--- a/desktop/inc/bitmaps.hlst
+++ b/desktop/inc/bitmaps.hlst
@@ -14,7 +14,6 @@
#define RID_BMP_LOCKED              "desktop/res/lock_16.png"
#define RID_BMP_SHARED              "desktop/res/shared_16.png"
#define RID_BMP_EXTENSION           "desktop/res/extension_32.png"
#define RID_DLG_UPDATE_NORMALALERT  "desktop/res/caution_12.png"

#endif

diff --git a/desktop/inc/strings.hrc b/desktop/inc/strings.hrc
index 6d70648..975a0221ea 100644
--- a/desktop/inc/strings.hrc
+++ b/desktop/inc/strings.hrc
@@ -150,9 +150,6 @@
#define RID_DLG_UPDATE_NODEPENDENCY_CUR_VER                 NC_("RID_DLG_UPDATE_NODEPENDENCY_CUR_VER", "You have %PRODUCTNAME %VERSION")
#define RID_DLG_UPDATE_BROWSERBASED                         NC_("RID_DLG_UPDATE_BROWSERBASED", "browser based update")
#define RID_DLG_UPDATE_VERSION                              NC_("RID_DLG_UPDATE_VERSION", "Version")
#define RID_DLG_UPDATE_IGNORE                               NC_("RID_DLG_UPDATE_IGNORE", "Ignore this Update")
#define RID_DLG_UPDATE_IGNORE_ALL                           NC_("RID_DLG_UPDATE_IGNORE_ALL", "Ignore all Updates")
#define RID_DLG_UPDATE_ENABLE                               NC_("RID_DLG_UPDATE_ENABLE", "Enable Updates")
#define RID_DLG_UPDATE_IGNORED_UPDATE                       NC_("RID_DLG_UPDATE_IGNORED_UPDATE", "This update will be ignored.\n")

#define STR_BOOTSTRAP_ERR_CANNOT_START                      NC_("STR_BOOTSTRAP_ERR_CANNOT_START", "The application cannot be started. ")
diff --git a/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx b/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx
index e0dc0b36..9d60a0a 100644
--- a/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx
+++ b/desktop/source/deployment/gui/dp_gui_extensioncmdqueue.cxx
@@ -896,11 +896,11 @@ void ExtensionCmdQueue::Thread::_checkForUpdates(
    const SolarMutexGuard guard;

    std::vector< UpdateData > vData;
    ScopedVclPtrInstance<UpdateDialog> pUpdateDialog( m_xContext, m_pDialogHelper? m_pDialogHelper->getWindow() : nullptr, vExtensionList, &vData );
    UpdateDialog aUpdateDialog(m_xContext, m_pDialogHelper? m_pDialogHelper->getFrameWeld() : nullptr, vExtensionList, &vData);

    pUpdateDialog->notifyMenubar( true, false ); // prepare the checking, if there updates to be notified via menu bar icon
    aUpdateDialog.notifyMenubar( true, false ); // prepare the checking, if there updates to be notified via menu bar icon

    if ( ( pUpdateDialog->Execute() == RET_OK ) && !vData.empty() )
    if (aUpdateDialog.run() == RET_OK && !vData.empty())
    {
        // If there is at least one directly downloadable extension then we
        // open the install dialog.
@@ -917,10 +917,10 @@ void ExtensionCmdQueue::Thread::_checkForUpdates(
        {
            UpdateInstallDialog aDlg(m_pDialogHelper? m_pDialogHelper->getFrameWeld() : nullptr, dataDownload, m_xContext);
            nDialogResult = aDlg.run();
            pUpdateDialog->notifyMenubar( false, true ); // Check, if there are still pending updates to be notified via menu bar icon
            aUpdateDialog.notifyMenubar( false, true ); // Check, if there are still pending updates to be notified via menu bar icon
        }
        else
            pUpdateDialog->notifyMenubar( false, false ); // Check, if there are pending updates to be notified via menu bar icon
            aUpdateDialog.notifyMenubar( false, false ); // Check, if there are pending updates to be notified via menu bar icon

        //Now start the webbrowser and navigate to the websites where we get the updates
        if ( RET_OK == nDialogResult )
@@ -933,9 +933,7 @@ void ExtensionCmdQueue::Thread::_checkForUpdates(
        }
    }
    else
        pUpdateDialog->notifyMenubar( false, false ); // check if there updates to be notified via menu bar icon

    pUpdateDialog.disposeAndClear();
        aUpdateDialog.notifyMenubar( false, false ); // check if there updates to be notified via menu bar icon
}


diff --git a/desktop/source/deployment/gui/dp_gui_updatedialog.cxx b/desktop/source/deployment/gui/dp_gui_updatedialog.cxx
index 243ed17..47ebe70 100644
--- a/desktop/source/deployment/gui/dp_gui_updatedialog.cxx
+++ b/desktop/source/deployment/gui/dp_gui_updatedialog.cxx
@@ -127,9 +127,6 @@ namespace {

static sal_Unicode const LF = 0x000A;
static sal_Unicode const CR = 0x000D;
static const sal_uInt16 CMD_ENABLE_UPDATE = 1;
static const sal_uInt16 CMD_IGNORE_UPDATE = 2;
static const sal_uInt16 CMD_IGNORE_ALL_UPDATES = 3;

#define IGNORED_UPDATES     OUString("/org.openoffice.Office.ExtensionManager/ExtensionUpdateData/IgnoredUpdates")
#define PROPERTY_VERSION    "Version"
@@ -244,7 +241,7 @@ UpdateDialog::Thread::Thread(
    if( m_context.is() )
    {
        m_xInteractionHdl.set(
            task::InteractionHandler::createWithParent(m_context, nullptr),
            task::InteractionHandler::createWithParent(m_context, dialog.getDialog()->GetXWindow()),
            uno::UNO_QUERY );
        m_updateInformation->setInteractionHandler( m_xInteractionHdl );
    }
@@ -463,57 +460,51 @@ bool UpdateDialog::Thread::update(
    return ret;
}


// UpdateDialog ----------------------------------------------------------
UpdateDialog::UpdateDialog(
    uno::Reference< uno::XComponentContext > const & context,
    vcl::Window * parent,
                           const std::vector<uno::Reference< deployment::XPackage > > &vExtensionList,
    std::vector< dp_gui::UpdateData > * updateData):
    ModalDialog(parent, "UpdateDialog", "desktop/ui/updatedialog.ui"),
    m_context(context),
    m_none(DpResId(RID_DLG_UPDATE_NONE)),
    m_noInstallable(DpResId(RID_DLG_UPDATE_NOINSTALLABLE)),
    m_failure(DpResId(RID_DLG_UPDATE_FAILURE)),
    m_unknownError(DpResId(RID_DLG_UPDATE_UNKNOWNERROR)),
    m_noDescription(DpResId(RID_DLG_UPDATE_NODESCRIPTION)),
    m_noInstall(DpResId(RID_DLG_UPDATE_NOINSTALL)),
    m_noDependency(DpResId(RID_DLG_UPDATE_NODEPENDENCY)),
    m_noDependencyCurVer(DpResId(RID_DLG_UPDATE_NODEPENDENCY_CUR_VER)),
    m_browserbased(DpResId(RID_DLG_UPDATE_BROWSERBASED)),
    m_version(DpResId(RID_DLG_UPDATE_VERSION)),
    m_ignoredUpdate(DpResId(RID_DLG_UPDATE_IGNORED_UPDATE)),
    m_updateData(*updateData),
    m_thread(
        new UpdateDialog::Thread(
            context, *this, vExtensionList)),
    m_bModified( false )
    // TODO: check!
//    ,
//    m_extensionManagerDialog(extensionManagerDialog)
    weld::Window * parent, const std::vector<uno::Reference< deployment::XPackage > > &vExtensionList,
    std::vector< dp_gui::UpdateData > * updateData)
    : GenericDialogController(parent, "desktop/ui/updatedialog.ui", "UpdateDialog")
    , m_context(context)
    , m_none(DpResId(RID_DLG_UPDATE_NONE))
    , m_noInstallable(DpResId(RID_DLG_UPDATE_NOINSTALLABLE))
    , m_failure(DpResId(RID_DLG_UPDATE_FAILURE))
    , m_unknownError(DpResId(RID_DLG_UPDATE_UNKNOWNERROR))
    , m_noDescription(DpResId(RID_DLG_UPDATE_NODESCRIPTION))
    , m_noInstall(DpResId(RID_DLG_UPDATE_NOINSTALL))
    , m_noDependency(DpResId(RID_DLG_UPDATE_NODEPENDENCY))
    , m_noDependencyCurVer(DpResId(RID_DLG_UPDATE_NODEPENDENCY_CUR_VER))
    , m_browserbased(DpResId(RID_DLG_UPDATE_BROWSERBASED))
    , m_version(DpResId(RID_DLG_UPDATE_VERSION))
    , m_ignoredUpdate(DpResId(RID_DLG_UPDATE_IGNORED_UPDATE))
    , m_updateData(*updateData)
    , m_thread(new UpdateDialog::Thread(context, *this, vExtensionList))
    , m_bModified( false )
    , m_xChecking(m_xBuilder->weld_label("UPDATE_CHECKING"))
    , m_xThrobber(m_xBuilder->weld_spinner("THROBBER"))
    , m_xUpdate(m_xBuilder->weld_label("UPDATE_LABEL"))
    , m_xUpdates(m_xBuilder->weld_tree_view("checklist"))
    , m_xAll(m_xBuilder->weld_check_button("UPDATE_ALL"))
    , m_xDescription(m_xBuilder->weld_label("DESCRIPTION_LABEL"))
    , m_xPublisherLabel(m_xBuilder->weld_label("PUBLISHER_LABEL"))
    , m_xPublisherLink(m_xBuilder->weld_link_button("PUBLISHER_LINK"))
    , m_xReleaseNotesLabel(m_xBuilder->weld_label("RELEASE_NOTES_LABEL"))
    , m_xReleaseNotesLink(m_xBuilder->weld_link_button("RELEASE_NOTES_LINK"))
    , m_xDescriptions(m_xBuilder->weld_text_view("DESCRIPTIONS"))
    , m_xOk(m_xBuilder->weld_button("ok"))
    , m_xClose(m_xBuilder->weld_button("close"))
    , m_xHelp(m_xBuilder->weld_button("help"))
{
    get(m_pchecking, "UPDATE_CHECKING");
    get(m_pthrobber, "THROBBER");
    get(m_pUpdate, "UPDATE_LABEL");
    get(m_pContainer, "UPDATES_CONTAINER");
    m_pUpdates = VclPtr<UpdateDialog::CheckListBox>::Create(m_pContainer, *this);
    Size aSize(LogicToPixel(Size(240, 51), MapMode(MapUnit::MapAppFont)));
    m_pUpdates->set_width_request(aSize.Width());
    m_pUpdates->set_height_request(aSize.Height());
    m_pUpdates->Show();
    get(m_pAll, "UPDATE_ALL");
    get(m_pDescription, "DESCRIPTION_LABEL");
    get(m_pPublisherLabel, "PUBLISHER_LABEL");
    get(m_pPublisherLink, "PUBLISHER_LINK");
    get(m_pReleaseNotesLabel, "RELEASE_NOTES_LABEL");
    get(m_pReleaseNotesLink, "RELEASE_NOTES_LINK");
    get(m_pDescriptions, "DESCRIPTIONS");
    aSize = LogicToPixel(Size(240, 59), MapMode(MapUnit::MapAppFont));
    m_pDescriptions->set_width_request(aSize.Width());
    m_pDescriptions->set_height_request(aSize.Height());
    get(m_pOk, "INSTALL");
    get(m_pClose, "close");
    get(m_pHelp, "help");
    auto nWidth = m_xDescriptions->get_approximate_digit_width() * 62;
    auto nHeight = m_xDescriptions->get_height_rows(8);
    m_xDescriptions->set_size_request(nWidth, nHeight);
    m_xUpdates->set_size_request(nWidth, nHeight);

    std::vector<int> aWidths;
    aWidths.push_back(m_xUpdates->get_checkbox_column_width());
    m_xUpdates->set_column_fixed_widths(aWidths);

    OSL_ASSERT(updateData != nullptr);

    m_xExtensionManager = deployment::ExtensionManager::get( context );
@@ -528,163 +519,55 @@ UpdateDialog::UpdateDialog(
        throw css::lang::WrappedTargetRuntimeException( e.Message,
                        e.Context, anyEx );
    }
    m_pUpdates->SetSelectHdl(LINK(this, UpdateDialog, selectionHandler));
    m_pAll->SetToggleHdl(LINK(this, UpdateDialog, allHandler));
    m_pOk->SetClickHdl(LINK(this, UpdateDialog, okHandler));
    m_pClose->SetClickHdl(LINK(this, UpdateDialog, closeHandler));
    if ( ! dp_misc::office_is_running())
        m_pHelp->Disable();
    m_xUpdates->connect_changed(LINK(this, UpdateDialog, selectionHandler));
    m_xUpdates->connect_toggled(LINK(this, UpdateDialog, entryToggled));
    m_xAll->connect_toggled(LINK(this, UpdateDialog, allHandler));
    m_xOk->connect_clicked(LINK(this, UpdateDialog, okHandler));
    m_xClose->connect_clicked(LINK(this, UpdateDialog, closeHandler));
    if (!dp_misc::office_is_running())
        m_xHelp->set_sensitive(false);

    initDescription();
    getIgnoredUpdates();
}


UpdateDialog::~UpdateDialog()
{
    disposeOnce();
}

void UpdateDialog::dispose()
{
    storeIgnoredUpdates();

    m_ListboxEntries.clear();
    m_ignoredUpdates.clear();
    m_pUpdates.disposeAndClear();
    m_pchecking.clear();
    m_pthrobber.clear();
    m_pUpdate.clear();
    m_pContainer.clear();
    m_pAll.clear();
    m_pDescription.clear();
    m_pPublisherLabel.clear();
    m_pPublisherLink.clear();
    m_pReleaseNotesLabel.clear();
    m_pReleaseNotesLink.clear();
    m_pDescriptions.clear();
    m_pHelp.clear();
    m_pOk.clear();
    m_pClose.clear();
    ModalDialog::dispose();
}


bool UpdateDialog::Close() {
    m_thread->stop();
    return ModalDialog::Close();
}

short UpdateDialog::Execute() {
    m_pthrobber->start();
short UpdateDialog::run() {
    m_xThrobber->start();
    m_thread->launch();
    return ModalDialog::Execute();
    short nRet = GenericDialogController::run();
    m_thread->stop();
    return nRet;
}

UpdateDialog::CheckListBox::CheckListBox( vcl::Window* pParent, UpdateDialog & dialog):
    SvxCheckListBox( pParent, WinBits(WB_BORDER) ),
    m_ignoreUpdate( DpResId( RID_DLG_UPDATE_IGNORE ) ),
    m_ignoreAllUpdates( DpResId( RID_DLG_UPDATE_IGNORE_ALL ) ),
    m_enableUpdate( DpResId( RID_DLG_UPDATE_ENABLE ) ),
    m_dialog(dialog)
IMPL_LINK(UpdateDialog, entryToggled, const row_col&, rRowCol, void)
{
    SetNormalStaticImage(Image(StockImage::Yes, RID_DLG_UPDATE_NORMALALERT));
    int nRow = rRowCol.first;

    // error's can't be enabled
    const UpdateDialog::Index* p = reinterpret_cast<UpdateDialog::Index const *>(m_xUpdates->get_id(rRowCol.first).toInt64());
    if (p->m_eKind == SPECIFIC_ERROR)
        m_xUpdates->set_toggle(nRow, false, 0);

    enableOk();
}

sal_uInt16 UpdateDialog::CheckListBox::getItemCount() const {
    sal_uLong i = GetEntryCount();
    OSL_ASSERT(i <= std::numeric_limits< sal_uInt16 >::max());
    return sal::static_int_cast< sal_uInt16 >(i);
}


void UpdateDialog::CheckListBox::MouseButtonDown( MouseEvent const & event )
sal_uInt16 UpdateDialog::insertItem(UpdateDialog::Index *pEntry, bool bEnabledCheckBox)
{
    // When clicking on a selected entry in an SvxCheckListBox, the entry's
    // checkbox is toggled on mouse button down:
    SvxCheckListBox::MouseButtonDown( event );
    int nEntry = m_xUpdates->n_children();
    m_xUpdates->append();
    m_xUpdates->set_toggle(nEntry, bEnabledCheckBox, 0);
    m_xUpdates->set_text(nEntry, pEntry->m_aName, 1);
    m_xUpdates->set_id(nEntry, OUString::number(reinterpret_cast<sal_Int64>(pEntry)));

    if ( event.IsRight() )
    {
        handlePopupMenu( event.GetPosPixel() );
    }

    m_dialog.enableOk();
}


void UpdateDialog::CheckListBox::MouseButtonUp(MouseEvent const & event) {
    // When clicking on an entry's checkbox in an SvxCheckListBox, the entry's
    // checkbox is toggled on mouse button up:
    SvxCheckListBox::MouseButtonUp(event);
    m_dialog.enableOk();
}

void UpdateDialog::CheckListBox::KeyInput(KeyEvent const & event) {
    SvxCheckListBox::KeyInput(event);
    m_dialog.enableOk();
}


void UpdateDialog::CheckListBox::handlePopupMenu( const Point &rPos )
{
    SvTreeListEntry *pData = GetEntry( rPos );

    if ( pData )
    {
        sal_uLong nEntryPos = GetSelectedEntryPos();
        UpdateDialog::Index * p = static_cast< UpdateDialog::Index * >( GetEntryData( nEntryPos ) );

        if ( ( p->m_eKind == ENABLED_UPDATE ) || ( p->m_eKind == DISABLED_UPDATE ) )
        {
            ScopedVclPtrInstance<PopupMenu> aPopup;

            if ( p->m_bIgnored )
                aPopup->InsertItem( CMD_ENABLE_UPDATE, m_enableUpdate );
            else
            {
                aPopup->InsertItem( CMD_IGNORE_UPDATE, m_ignoreUpdate );
                aPopup->InsertItem( CMD_IGNORE_ALL_UPDATES, m_ignoreAllUpdates );
            }

            sal_uInt16 aCmd = aPopup->Execute( this, rPos );
            if ( ( aCmd == CMD_IGNORE_UPDATE ) || ( aCmd == CMD_IGNORE_ALL_UPDATES ) )
            {
                p->m_bIgnored = true;
                if ( p->m_eKind == ENABLED_UPDATE )
                {
                    RemoveEntry( nEntryPos );
                    m_dialog.addAdditional( p, SvLBoxButtonKind::DisabledCheckbox );
                }
                if ( aCmd == CMD_IGNORE_UPDATE )
                    m_dialog.setIgnoredUpdate( p, true, false );
                else
                    m_dialog.setIgnoredUpdate( p, true, true );
                // TODO: reselect entry to display new description!
            }
            else if ( aCmd == CMD_ENABLE_UPDATE )
            {
                p->m_bIgnored = false;
                if ( p->m_eKind == ENABLED_UPDATE )
                {
                    RemoveEntry( nEntryPos );
                    m_dialog.insertItem( p, SvLBoxButtonKind::EnabledCheckbox );
                }
                m_dialog.setIgnoredUpdate( p, false, false );
            }
        }
    }
}


sal_uInt16 UpdateDialog::insertItem( UpdateDialog::Index *pEntry, SvLBoxButtonKind kind )
{
    m_pUpdates->InsertEntry( pEntry->m_aName, TREELIST_APPEND, static_cast< void * >( pEntry ), kind );

    for ( sal_uInt16 i = m_pUpdates->getItemCount(); i != 0 ; )
    for (sal_uInt16 i = nEntry; i != 0 ;)
    {
        i -= 1;
        UpdateDialog::Index const * p = static_cast< UpdateDialog::Index const * >( m_pUpdates->GetEntryData( i ) );
        UpdateDialog::Index const * p = reinterpret_cast< UpdateDialog::Index const * >(m_xUpdates->get_id(i).toInt64());
        if ( p == pEntry )
            return i;
    }
@@ -692,21 +575,19 @@ sal_uInt16 UpdateDialog::insertItem( UpdateDialog::Index *pEntry, SvLBoxButtonKi
    return 0;
}


void UpdateDialog::addAdditional( UpdateDialog::Index * index, SvLBoxButtonKind kind )
void UpdateDialog::addAdditional(UpdateDialog::Index * index, bool bEnabledCheckBox)
{
    m_pAll->Enable();
    if (m_pAll->IsChecked())
    m_xAll->set_sensitive(true);
    if (m_xAll->get_active())
    {
        insertItem( index, kind );
        m_pUpdate->Enable();
        m_pUpdates->Enable();
        m_pDescription->Enable();
        m_pDescriptions->Enable();
        insertItem(index, bEnabledCheckBox);
        m_xUpdate->set_sensitive(true);
        m_xUpdates->set_sensitive(true);
        m_xDescription->set_sensitive(true);
        m_xDescriptions->set_sensitive(true);
    }
}


void UpdateDialog::addEnabledUpdate( OUString const & name,
                                     dp_gui::UpdateData const & data )
{
@@ -716,21 +597,19 @@ void UpdateDialog::addEnabledUpdate( OUString const & name,
    m_enabledUpdates.push_back( data );
    m_ListboxEntries.emplace_back( pEntry );

    if ( ! isIgnoredUpdate( pEntry ) )
    if (!isIgnoredUpdate(pEntry))
    {
        sal_uInt16 nPos = insertItem( pEntry, SvLBoxButtonKind::EnabledCheckbox );
        m_pUpdates->CheckEntryPos( nPos );
        insertItem(pEntry, true);
    }
    else
        addAdditional( pEntry, SvLBoxButtonKind::DisabledCheckbox );
        addAdditional(pEntry, false);

    m_pUpdate->Enable();
    m_pUpdates->Enable();
    m_pDescription->Enable();
    m_pDescriptions->Enable();
    m_xUpdate->set_sensitive(true);
    m_xUpdates->set_sensitive(true);
    m_xDescription->set_sensitive(true);
    m_xDescriptions->set_sensitive(true);
}


void UpdateDialog::addDisabledUpdate( UpdateDialog::DisabledUpdate const & data )
{
    sal_uInt16 nIndex = sal::static_int_cast< sal_uInt16 >( m_disabledUpdates.size() );
@@ -740,10 +619,9 @@ void UpdateDialog::addDisabledUpdate( UpdateDialog::DisabledUpdate const & data 
    m_ListboxEntries.emplace_back( pEntry );

    isIgnoredUpdate( pEntry );
    addAdditional( pEntry, SvLBoxButtonKind::DisabledCheckbox );
    addAdditional(pEntry, false);
}


void UpdateDialog::addSpecificError( UpdateDialog::SpecificError const & data )
{
    sal_uInt16 nIndex = sal::static_int_cast< sal_uInt16 >( m_specificErrors.size() );
@@ -752,18 +630,18 @@ void UpdateDialog::addSpecificError( UpdateDialog::SpecificError const & data )
    m_specificErrors.push_back( data );
    m_ListboxEntries.emplace_back( pEntry );

    addAdditional( pEntry, SvLBoxButtonKind::StaticImage);
    addAdditional(pEntry, false);
}

void UpdateDialog::checkingDone() {
    m_pchecking->Hide();
    m_pthrobber->stop();
    m_pthrobber->Hide();
    if (m_pUpdates->getItemCount() == 0)
    m_xChecking->hide();
    m_xThrobber->stop();
    m_xThrobber->hide();
    if (m_xUpdates->n_children() == 0)
    {
        clearDescription();
        m_pDescription->Enable();
        m_pDescriptions->Enable();
        m_xDescription->set_sensitive(true);
        m_xDescriptions->set_sensitive(true);

        if ( m_disabledUpdates.empty() && m_specificErrors.empty() && m_ignoredUpdates.empty() )
            showDescription( m_none );
@@ -775,8 +653,13 @@ void UpdateDialog::checkingDone() {
}

void UpdateDialog::enableOk() {
    if (!m_pchecking->IsVisible()) {
        m_pOk->Enable(m_pUpdates->GetCheckedEntryCount() != 0);
    if (!m_xChecking->get_visible()) {
        int nChecked = 0;
        for (int i = 0, nCount = m_xUpdates->n_children(); i < nCount; ++i) {
            if (m_xUpdates->get_toggle(i, 0))
                ++nChecked;
        }
        m_xOk->set_sensitive(nChecked != 0);
    }
}

@@ -850,11 +733,11 @@ void UpdateDialog::notifyMenubar( bool bPrepareOnly, bool bRecheckOnly )
    if ( ! bRecheckOnly )
    {
        sal_Int32 nCount = 0;
        for ( sal_uInt16 i = 0; i < m_pUpdates->getItemCount(); ++i )
        for (sal_uInt16 i = 0, nItemCount = m_xUpdates->n_children(); i < nItemCount; ++i)
        {
            uno::Sequence< OUString > aItem(2);

            UpdateDialog::Index const * p = static_cast< UpdateDialog::Index const * >(m_pUpdates->GetEntryData(i));
            UpdateDialog::Index const * p = reinterpret_cast< UpdateDialog::Index const * >(m_xUpdates->get_id(i).toInt64());

            if ( p->m_eKind == ENABLED_UPDATE )
            {
@@ -881,22 +764,22 @@ void UpdateDialog::notifyMenubar( bool bPrepareOnly, bool bRecheckOnly )

void UpdateDialog::initDescription()
{
    m_pPublisherLabel->Hide();
    m_pPublisherLink->Hide();
    m_pReleaseNotesLabel->Hide();
    m_pReleaseNotesLink->Hide();
    m_xPublisherLabel->hide();
    m_xPublisherLink->hide();
    m_xReleaseNotesLabel->hide();
    m_xReleaseNotesLink->hide();
}

void UpdateDialog::clearDescription()
{
    m_pPublisherLabel->Hide();
    m_pPublisherLink->Hide();
    m_pPublisherLink->SetText( "" );
    m_pPublisherLink->SetURL( "" );
    m_pReleaseNotesLabel->Hide();
    m_pReleaseNotesLink->Hide();
    m_pReleaseNotesLink->SetURL( "" );
    m_pDescriptions->SetText("");
    m_xPublisherLabel->hide();
    m_xPublisherLink->hide();
    m_xPublisherLink->set_label("");
    m_xPublisherLink->set_uri("");
    m_xReleaseNotesLabel->hide();
    m_xReleaseNotesLink->hide();
    m_xReleaseNotesLink->set_uri( "" );
    m_xDescriptions->set_text("");
}

bool UpdateDialog::showDescription(uno::Reference< xml::dom::XNode > const & aUpdateInfo)
@@ -926,17 +809,17 @@ bool UpdateDialog::showDescription(std::pair< OUString, OUString > const & pairP

    if ( !sPub.isEmpty() )
    {
        m_pPublisherLabel->Show();
        m_pPublisherLink->Show();
        m_pPublisherLink->SetText( sPub );
        m_pPublisherLink->SetURL( sURL );
        m_xPublisherLabel->show();
        m_xPublisherLink->show();
        m_xPublisherLink->set_label(sPub);
        m_xPublisherLink->set_uri(sURL);
    }

    if ( !sReleaseNotes.isEmpty() )
    {
        m_pReleaseNotesLabel->Show();
        m_pReleaseNotesLink->Show();
        m_pReleaseNotesLink->SetURL( sReleaseNotes );
        m_xReleaseNotesLabel->show();
        m_xReleaseNotesLink->show();
        m_xReleaseNotesLink->set_uri( sReleaseNotes );
    }
    return true;
}
@@ -947,11 +830,10 @@ bool UpdateDialog::showDescription( const OUString& rDescription)
        // nothing to show
        return false;

    m_pDescriptions->SetText( rDescription );
    m_xDescriptions->set_text(rDescription);
    return true;
}


void UpdateDialog::getIgnoredUpdates()
{
    uno::Reference< lang::XMultiServiceFactory > xConfig(
@@ -1107,14 +989,16 @@ void UpdateDialog::setIgnoredUpdate( UpdateDialog::Index const *pIndex, bool bIg
}


IMPL_LINK_NOARG(UpdateDialog, selectionHandler, SvTreeListBox*, void)
IMPL_LINK_NOARG(UpdateDialog, selectionHandler, weld::TreeView&, void)
{
    OUStringBuffer b;
    UpdateDialog::Index const * p = static_cast< UpdateDialog::Index const * >(
        m_pUpdates->GetSelectedEntryData());
    int nSelectedPos = m_xUpdates->get_selected_index();
    clearDescription();

    if ( p != nullptr )
    const UpdateDialog::Index* p = nullptr;
    if (nSelectedPos != -1)
        p = reinterpret_cast<UpdateDialog::Index const *>(m_xUpdates->get_id(nSelectedPos).toInt64());
    if (p != nullptr)
    {
        sal_uInt16 pos = p->m_nIndex;

@@ -1205,48 +1089,47 @@ IMPL_LINK_NOARG(UpdateDialog, selectionHandler, SvTreeListBox*, void)
    showDescription( b.makeStringAndClear() );
}

IMPL_LINK_NOARG(UpdateDialog, allHandler, CheckBox&, void)
IMPL_LINK_NOARG(UpdateDialog, allHandler, weld::ToggleButton&, void)
{
    if (m_pAll->IsChecked())
    if (m_xAll->get_active())
    {
        m_pUpdate->Enable();
        m_pUpdates->Enable();
        m_pDescription->Enable();
        m_pDescriptions->Enable();
        m_xUpdate->set_sensitive(true);
        m_xUpdates->set_sensitive(true);
        m_xDescription->set_sensitive(true);
        m_xDescriptions->set_sensitive(true);

        for (auto const& listboxEntry : m_ListboxEntries)
        {
            if ( listboxEntry->m_bIgnored || ( listboxEntry->m_eKind != ENABLED_UPDATE ) )
                insertItem( listboxEntry.get(), SvLBoxButtonKind::DisabledCheckbox );
                insertItem(listboxEntry.get(), false);
        }
    }
    else
    {
        for ( sal_uInt16 i = 0; i < m_pUpdates->getItemCount(); )
        for (sal_uInt16 i = m_xUpdates->n_children(); i != 0 ;)
        {
            UpdateDialog::Index const * p = static_cast< UpdateDialog::Index const * >( m_pUpdates->GetEntryData(i) );
            i -= 1;
            UpdateDialog::Index const * p = reinterpret_cast< UpdateDialog::Index const * >( m_xUpdates->get_id(i).toInt64() );
            if ( p->m_bIgnored || ( p->m_eKind != ENABLED_UPDATE ) )
            {
                m_pUpdates->RemoveEntry(i);
            } else {
                ++i;
                m_xUpdates->remove(i);
            }
        }

        if (m_pUpdates->getItemCount() == 0)
        if (m_xUpdates->n_children() == 0)
        {
            clearDescription();
            m_pUpdate->Disable();
            m_pUpdates->Disable();
            if (m_pchecking->IsVisible())
                m_pDescription->Disable();
            m_xUpdate->set_sensitive(false);
            m_xUpdates->set_sensitive(false);
            if (m_xChecking->get_visible())
                m_xDescription->set_sensitive(false);
            else
                showDescription(m_noInstallable);
        }
    }
}

IMPL_LINK_NOARG(UpdateDialog, okHandler, Button*, void)
IMPL_LINK_NOARG(UpdateDialog, okHandler, weld::Button&, void)
{
    //If users are going to update a shared extension then we need
    //to warn them
@@ -1258,22 +1141,23 @@ IMPL_LINK_NOARG(UpdateDialog, okHandler, Button*, void)
    }


    for (sal_uInt16 i = 0; i < m_pUpdates->getItemCount(); ++i) {
    for (sal_uInt16 i = 0, nCount = m_xUpdates->n_children(); i < nCount; ++i)
    {
        UpdateDialog::Index const * p =
            static_cast< UpdateDialog::Index const * >(
                m_pUpdates->GetEntryData(i));
        if (p->m_eKind == ENABLED_UPDATE && m_pUpdates->IsChecked(i)) {
            reinterpret_cast< UpdateDialog::Index const * >(
                m_xUpdates->get_id(i).toInt64());
        if (p->m_eKind == ENABLED_UPDATE && m_xUpdates->get_toggle(i, 0)) {
            m_updateData.push_back( m_enabledUpdates[ p->m_nIndex ] );
        }
    }

    EndDialog(RET_OK);
    m_xDialog->response(RET_OK);
}

IMPL_LINK_NOARG(UpdateDialog, closeHandler, Button*, void)
IMPL_LINK_NOARG(UpdateDialog, closeHandler, weld::Button&, void)
{
    m_thread->stop();
    EndDialog();
    m_xDialog->response(RET_CANCEL);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/desktop/source/deployment/gui/dp_gui_updatedialog.hxx b/desktop/source/deployment/gui/dp_gui_updatedialog.hxx
index 1b0812a..b32797e 100644
--- a/desktop/source/deployment/gui/dp_gui_updatedialog.hxx
+++ b/desktop/source/deployment/gui/dp_gui_updatedialog.hxx
@@ -27,15 +27,8 @@
#include <com/sun/star/uno/Sequence.hxx>
#include <rtl/ref.hxx>
#include <rtl/ustring.hxx>
#include <vcl/svlbitm.hxx>
#include <svx/checklbx.hxx>
#include <tools/link.hxx>
#include <vcl/layout.hxx>
#include <vcl/button.hxx>
#include <vcl/dialog.hxx>
#include <vcl/fixed.hxx>
#include <vcl/fixedhyper.hxx>
#include <vcl/throbber.hxx>
#include <vcl/weld.hxx>

#include "dp_gui_updatedata.hxx"

@@ -57,7 +50,7 @@ namespace dp_gui {
/**
   The modal &ldquo;Check for Updates&rdquo; dialog.
*/
class UpdateDialog: public ModalDialog {
class UpdateDialog: public weld::GenericDialogController {
public:
    /**
       Create an instance.
@@ -79,17 +72,14 @@ public:
    */
    UpdateDialog(
        css::uno::Reference< css::uno::XComponentContext > const & context,
        vcl::Window * parent,
        weld::Window * parent,
        const std::vector< css::uno::Reference<
        css::deployment::XPackage > > & vExtensionList,
        std::vector< dp_gui::UpdateData > * updateData);

    virtual ~UpdateDialog() override;
    virtual void dispose() override;

    virtual bool Close() override;

    virtual short Execute() override;
    virtual short run() override;

    void notifyMenubar( bool bPrepareOnly, bool bRecheckOnly );
    static void createNotifyJob( bool bPrepareOnly,
@@ -107,34 +97,10 @@ private:
    class Thread;
    friend class Thread;

    class CheckListBox: public SvxCheckListBox {
    public:
        CheckListBox(
            vcl::Window* pParent, UpdateDialog & dialog);

        sal_uInt16 getItemCount() const;

    private:
        explicit CheckListBox(UpdateDialog::CheckListBox const &) = delete;
        void operator =(UpdateDialog::CheckListBox const &) = delete;

        virtual void MouseButtonDown(MouseEvent const & event) override;
        virtual void MouseButtonUp(MouseEvent const & event) override;
        virtual void KeyInput(KeyEvent const & event) override;

        void handlePopupMenu( const Point &rPos );

        OUString m_ignoreUpdate;
        OUString m_ignoreAllUpdates;
        OUString m_enableUpdate;
        UpdateDialog & m_dialog;
    };


    friend class CheckListBox;

    sal_uInt16 insertItem( UpdateDialog::Index *pIndex, SvLBoxButtonKind kind );
    void addAdditional( UpdateDialog::Index *pIndex, SvLBoxButtonKind kind );
    sal_uInt16 insertItem(UpdateDialog::Index *pIndex, bool bEnableCheckBox);
    void addAdditional(UpdateDialog::Index *pIndex, bool bEnableCheckBox);
    bool isIgnoredUpdate( UpdateDialog::Index *pIndex );
    void setIgnoredUpdate( UpdateDialog::Index const *pIndex, bool bIgnore, bool bIgnoreAll );

@@ -159,27 +125,14 @@ private:
        css::xml::dom::XNode > const & aUpdateInfo);
    bool showDescription( const OUString& rDescription);

    DECL_LINK(selectionHandler, SvTreeListBox*, void);
    DECL_LINK(allHandler, CheckBox&, void);
    DECL_LINK(okHandler, Button*, void);
    DECL_LINK(closeHandler, Button*, void);
    DECL_LINK(selectionHandler, weld::TreeView&, void);
    DECL_LINK(allHandler, weld::ToggleButton&, void);
    DECL_LINK(okHandler, weld::Button&, void);
    DECL_LINK(closeHandler, weld::Button&, void);
    typedef std::pair<int, int> row_col;
    DECL_LINK(entryToggled, const row_col&, void);

    css::uno::Reference< css::uno::XComponentContext >  m_context;
    VclPtr<FixedText> m_pchecking;
    VclPtr<Throbber> m_pthrobber;
    VclPtr<FixedText> m_pUpdate;
    VclPtr<VclViewport> m_pContainer;
    VclPtr<UpdateDialog::CheckListBox> m_pUpdates;
    VclPtr<CheckBox> m_pAll;
    VclPtr<FixedText> m_pDescription;
    VclPtr<FixedText> m_pPublisherLabel;
    VclPtr<FixedHyperlink> m_pPublisherLink;
    VclPtr<FixedText> m_pReleaseNotesLabel;
    VclPtr<FixedHyperlink> m_pReleaseNotesLink;
    VclPtr<VclMultiLineEdit> m_pDescriptions;
    VclPtr<HelpButton> m_pHelp;
    VclPtr<PushButton> m_pOk;
    VclPtr<PushButton> m_pClose;
    OUString m_none;
    OUString m_noInstallable;
    OUString m_failure;
@@ -201,6 +154,21 @@ private:
    css::uno::Reference< css::deployment::XExtensionManager > m_xExtensionManager;

    bool    m_bModified;

    std::unique_ptr<weld::Label> m_xChecking;
    std::unique_ptr<weld::Spinner> m_xThrobber;
    std::unique_ptr<weld::Label> m_xUpdate;
    std::unique_ptr<weld::TreeView> m_xUpdates;
    std::unique_ptr<weld::CheckButton> m_xAll;
    std::unique_ptr<weld::Label> m_xDescription;
    std::unique_ptr<weld::Label> m_xPublisherLabel;
    std::unique_ptr<weld::LinkButton> m_xPublisherLink;
    std::unique_ptr<weld::Label> m_xReleaseNotesLabel;
    std::unique_ptr<weld::LinkButton> m_xReleaseNotesLink;
    std::unique_ptr<weld::TextView> m_xDescriptions;
    std::unique_ptr<weld::Button> m_xOk;
    std::unique_ptr<weld::Button> m_xClose;
    std::unique_ptr<weld::Button> m_xHelp;
};

}
diff --git a/desktop/uiconfig/ui/updatedialog.ui b/desktop/uiconfig/ui/updatedialog.ui
index 9efe2d6..683f052 100644
--- a/desktop/uiconfig/ui/updatedialog.ui
+++ b/desktop/uiconfig/ui/updatedialog.ui
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<!-- Generated with glade 3.22.1 -->
<interface domain="dkt">
  <requires lib="gtk+" version="3.18"/>
  <object class="GtkDialog" id="UpdateDialog">
@@ -8,7 +8,13 @@
    <property name="vexpand">True</property>
    <property name="border_width">6</property>
    <property name="title" translatable="yes" context="updatedialog|UpdateDialog">Extension Update</property>
    <property name="modal">True</property>
    <property name="default_width">0</property>
    <property name="default_height">0</property>
    <property name="type_hint">dialog</property>
    <child>
      <placeholder/>
    </child>
    <child internal-child="vbox">
      <object class="GtkBox" id="dialog-vbox1">
        <property name="can_focus">False</property>
@@ -34,7 +40,7 @@
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="INSTALL">
              <object class="GtkButton" id="ok">
                <property name="label" translatable="yes" context="updatedialog|INSTALL">_Install</property>
                <property name="visible">True</property>
                <property name="sensitive">False</property>
@@ -73,13 +79,12 @@
          </packing>
        </child>
        <child>
          <object class="GtkBox" id="box1">
          <object class="GtkGrid">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="hexpand">True</property>
            <property name="vexpand">True</property>
            <property name="orientation">vertical</property>
            <property name="spacing">6</property>
            <property name="row_spacing">12</property>
            <child>
              <object class="GtkBox" id="box2">
                <property name="visible">True</property>
@@ -139,13 +144,54 @@
                  </packing>
                </child>
                <child>
                  <object class="GtkScrolledWindow" id="scrolledwindow1">
                  <object class="GtkScrolledWindow" id="checklistwin">
                    <property name="visible">True</property>
                    <property name="can_focus">True</property>
                    <property name="hexpand">True</property>
                    <property name="vexpand">True</property>
                    <property name="hscrollbar_policy">never</property>
                    <property name="vscrollbar_policy">never</property>
                    <property name="shadow_type">in</property>
                    <child>
                      <object class="GtkViewport" id="UPDATES_CONTAINER">
                      <object class="GtkTreeView" id="checklist">
                        <property name="visible">True</property>
                        <property name="can_focus">True</property>
                        <property name="receives_default">True</property>
                        <property name="hexpand">True</property>
                        <property name="vexpand">True</property>
                        <property name="model">liststore1</property>
                        <property name="headers_visible">False</property>
                        <property name="search_column">0</property>
                        <property name="show_expanders">False</property>
                        <child internal-child="selection">
                          <object class="GtkTreeSelection"/>
                        </child>
                        <child>
                          <object class="GtkTreeViewColumn" id="treeviewcolumn4">
                            <property name="resizable">True</property>
                            <property name="spacing">6</property>
                            <property name="alignment">0.5</property>
                            <child>
                              <object class="GtkCellRendererToggle" id="cellrenderer5"/>
                              <attributes>
                                <attribute name="visible">3</attribute>
                                <attribute name="active">0</attribute>
                              </attributes>
                            </child>
                          </object>
                        </child>
                        <child>
                          <object class="GtkTreeViewColumn" id="treeviewcolumn5">
                            <property name="resizable">True</property>
                            <property name="spacing">6</property>
                            <child>
                              <object class="GtkCellRendererText" id="cellrenderer4"/>
                              <attributes>
                                <attribute name="text">1</attribute>
                              </attributes>
                            </child>
                          </object>
                        </child>
                      </object>
                    </child>
                  </object>
@@ -174,9 +220,8 @@
                </child>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">0</property>
                <property name="left_attach">0</property>
                <property name="top_attach">0</property>
              </packing>
            </child>
            <child>
@@ -269,15 +314,25 @@
                  </packing>
                </child>
                <child>
                  <object class="GtkTextView" id="DESCRIPTIONS:border">
                  <object class="GtkScrolledWindow" id="DESCRIPTIONSWIN">
                    <property name="visible">True</property>
                    <property name="sensitive">False</property>
                    <property name="can_focus">True</property>
                    <property name="hexpand">True</property>
                    <property name="vexpand">True</property>
                    <property name="editable">False</property>
                    <property name="cursor_visible">False</property>
                    <property name="accepts_tab">False</property>
                    <property name="shadow_type">in</property>
                    <child>
                      <object class="GtkTextView" id="DESCRIPTIONS">
                        <property name="visible">True</property>
                        <property name="sensitive">False</property>
                        <property name="can_focus">True</property>
                        <property name="hexpand">True</property>
                        <property name="vexpand">True</property>
                        <property name="editable">False</property>
                        <property name="wrap_mode">word</property>
                        <property name="cursor_visible">False</property>
                        <property name="accepts_tab">False</property>
                      </object>
                    </child>
                  </object>
                  <packing>
                    <property name="expand">False</property>
@@ -287,14 +342,13 @@
                </child>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">1</property>
                <property name="left_attach">0</property>
                <property name="top_attach">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
@@ -303,8 +357,20 @@
    </child>
    <action-widgets>
      <action-widget response="-11">help</action-widget>
      <action-widget response="0">INSTALL</action-widget>
      <action-widget response="-5">ok</action-widget>
      <action-widget response="-7">close</action-widget>
    </action-widgets>
  </object>
  <object class="GtkTreeStore" id="liststore1">
    <columns>
      <!-- column-name check1 -->
      <column type="gboolean"/>
      <!-- column-name text -->
      <column type="gchararray"/>
      <!-- column-name id -->
      <column type="gchararray"/>
      <!-- column-name checkvis1 -->
      <column type="gboolean"/>
    </columns>
  </object>
</interface>
diff --git a/include/vcl/weld.hxx b/include/vcl/weld.hxx
index 4673da0..4802713 100644
--- a/include/vcl/weld.hxx
+++ b/include/vcl/weld.hxx
@@ -832,6 +832,13 @@ public:
    void connect_value_changed(const Link<Scale&, void>& rLink) { m_aValueChangedHdl = rLink; }
};

class VCL_DLLPUBLIC Spinner : virtual public Widget
{
public:
    virtual void start() = 0;
    virtual void stop() = 0;
};

class VCL_DLLPUBLIC ProgressBar : virtual public Widget
{
public:
@@ -1533,6 +1540,8 @@ public:
    virtual std::unique_ptr<ProgressBar> weld_progress_bar(const OString& id,
                                                           bool bTakeOwnership = false)
        = 0;
    virtual std::unique_ptr<Spinner> weld_spinner(const OString& id, bool bTakeOwnership = false)
        = 0;
    virtual std::unique_ptr<Image> weld_image(const OString& id, bool bTakeOwnership = false) = 0;
    virtual std::unique_ptr<Calendar> weld_calendar(const OString& id, bool bTakeOwnership = false)
        = 0;
diff --git a/vcl/source/app/salvtables.cxx b/vcl/source/app/salvtables.cxx
index ce61bf6..325d320 100644
--- a/vcl/source/app/salvtables.cxx
+++ b/vcl/source/app/salvtables.cxx
@@ -53,6 +53,7 @@
#include <vcl/svtabbx.hxx>
#include <vcl/tabctrl.hxx>
#include <vcl/tabpage.hxx>
#include <vcl/throbber.hxx>
#include <vcl/treelistentry.hxx>
#include <vcl/toolkit/unowrap.hxx>
#include <vcl/weld.hxx>
@@ -1867,6 +1868,29 @@ IMPL_LINK_NOARG(SalInstanceScale, SlideHdl, Slider*, void)
    signal_value_changed();
}

class SalInstanceSpinner : public SalInstanceWidget, public virtual weld::Spinner
{
private:
    VclPtr<Throbber> m_xThrobber;

public:
    SalInstanceSpinner(Throbber* pThrobber, SalInstanceBuilder* pBuilder, bool bTakeOwnership)
        : SalInstanceWidget(pThrobber, pBuilder, bTakeOwnership)
        , m_xThrobber(pThrobber)
    {
    }

    virtual void start() override
    {
        m_xThrobber->start();
    }

    virtual void stop() override
    {
        m_xThrobber->stop();
    }
};

class SalInstanceProgressBar : public SalInstanceWidget, public virtual weld::ProgressBar
{
private:
@@ -4209,6 +4233,12 @@ public:
        return pProgress ? std::make_unique<SalInstanceProgressBar>(pProgress, this, bTakeOwnership) : nullptr;
    }

    virtual std::unique_ptr<weld::Spinner> weld_spinner(const OString &id, bool bTakeOwnership) override
    {
        Throbber* pThrobber = m_xBuilder->get<Throbber>(id);
        return pThrobber ? std::make_unique<SalInstanceSpinner>(pThrobber, this, bTakeOwnership) : nullptr;
    }

    virtual std::unique_ptr<weld::Image> weld_image(const OString &id, bool bTakeOwnership) override
    {
        FixedImage* pImage = m_xBuilder->get<FixedImage>(id);
diff --git a/vcl/unx/gtk3/gtk3gtkinst.cxx b/vcl/unx/gtk3/gtk3gtkinst.cxx
index d33c59b..8d9ac7f 100644
--- a/vcl/unx/gtk3/gtk3gtkinst.cxx
+++ b/vcl/unx/gtk3/gtk3gtkinst.cxx
@@ -4818,6 +4818,29 @@ public:
    }
};

class GtkInstanceSpinner : public GtkInstanceWidget, public virtual weld::Spinner
{
private:
    GtkSpinner* m_pSpinner;

public:
    GtkInstanceSpinner(GtkSpinner* pSpinner, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
        : GtkInstanceWidget(GTK_WIDGET(pSpinner), pBuilder, bTakeOwnership)
        , m_pSpinner(pSpinner)
    {
    }

    virtual void start() override
    {
        gtk_spinner_start(m_pSpinner);
    }

    virtual void stop() override
    {
        gtk_spinner_stop(m_pSpinner);
    }
};

class GtkInstanceImage : public GtkInstanceWidget, public virtual weld::Image
{
private:
@@ -8613,6 +8636,15 @@ public:
        return std::make_unique<GtkInstanceProgressBar>(pProgressBar, this, bTakeOwnership);
    }

    virtual std::unique_ptr<weld::Spinner> weld_spinner(const OString &id, bool bTakeOwnership) override
    {
        GtkSpinner* pSpinner = GTK_SPINNER(gtk_builder_get_object(m_pBuilder, id.getStr()));
        if (!pSpinner)
            return nullptr;
        auto_add_parentless_widgets_to_container(GTK_WIDGET(pSpinner));
        return std::make_unique<GtkInstanceSpinner>(pSpinner, this, bTakeOwnership);
    }

    virtual std::unique_ptr<weld::Image> weld_image(const OString &id, bool bTakeOwnership) override
    {
        GtkImage* pImage = GTK_IMAGE(gtk_builder_get_object(m_pBuilder, id.getStr()));