Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / desktop / source / deployment / gui / dp_gui_updateinstalldialog.cxx
blob3fe66f665ea168da480fea25000e65bf6ca695cf
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 .
21 #include "dp_gui_updatedata.hxx"
23 #include <sal/config.h>
24 #include <osl/file.hxx>
25 #include <osl/conditn.hxx>
26 #include <cppuhelper/exc_hlp.hxx>
27 #include <vcl/svapp.hxx>
28 #include <cppuhelper/implbase.hxx>
30 #include <com/sun/star/beans/PropertyValue.hpp>
31 #include <com/sun/star/beans/NamedValue.hpp>
32 #include <com/sun/star/xml/dom/XElement.hpp>
33 #include <com/sun/star/xml/dom/XNode.hpp>
34 #include <com/sun/star/xml/dom/XNodeList.hpp>
35 #include <com/sun/star/ucb/NameClash.hpp>
36 #include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
37 #include <com/sun/star/ucb/XCommandEnvironment.hpp>
38 #include <com/sun/star/ucb/XProgressHandler.hpp>
39 #include <com/sun/star/deployment/DeploymentException.hpp>
40 #include <com/sun/star/deployment/XExtensionManager.hpp>
41 #include <com/sun/star/deployment/ExtensionManager.hpp>
42 #include <com/sun/star/deployment/XUpdateInformationProvider.hpp>
43 #include <com/sun/star/deployment/DependencyException.hpp>
44 #include <com/sun/star/deployment/LicenseException.hpp>
45 #include <com/sun/star/deployment/VersionException.hpp>
46 #include <com/sun/star/deployment/ui/LicenseDialog.hpp>
47 #include <com/sun/star/task/XInteractionHandler.hpp>
48 #include <com/sun/star/ui/dialogs/XExecutableDialog.hpp>
49 #include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
50 #include <com/sun/star/task/XInteractionAbort.hpp>
51 #include <com/sun/star/task/XInteractionApprove.hpp>
53 #include <dp_descriptioninfoset.hxx>
54 #include <strings.hrc>
55 #include "dp_gui_updateinstalldialog.hxx"
56 #include <dp_shared.hxx>
57 #include <dp_ucb.h>
58 #include <dp_misc.h>
59 #include <dp_version.hxx>
60 #include "dp_gui_extensioncmdqueue.hxx"
61 #include <ucbhelper/content.hxx>
62 #include <rtl/ref.hxx>
63 #include <salhelper/thread.hxx>
64 #include <com/sun/star/uno/Sequence.h>
65 #include <comphelper/anytostring.hxx>
67 #include <vector>
69 namespace cssu = ::com::sun::star::uno;
71 using dp_misc::StrTitle;
73 namespace dp_gui {
75 class UpdateInstallDialog::Thread: public salhelper::Thread {
76 friend class UpdateCommandEnv;
77 public:
78 Thread(cssu::Reference< cssu::XComponentContext > const & ctx,
79 UpdateInstallDialog & dialog, std::vector< dp_gui::UpdateData > & aVecUpdateData);
81 void stop();
83 private:
84 virtual ~Thread() override;
86 virtual void execute() override;
87 void downloadExtensions();
88 bool download(OUString const & aUrls, UpdateData & aUpdatData);
89 void installExtensions();
90 void removeTempDownloads();
92 UpdateInstallDialog & m_dialog;
94 // guarded by Application::GetSolarMutex():
95 cssu::Reference< css::task::XAbortChannel > m_abort;
96 cssu::Reference< cssu::XComponentContext > m_xComponentContext;
97 std::vector< dp_gui::UpdateData > & m_aVecUpdateData;
98 ::rtl::Reference<UpdateCommandEnv> m_updateCmdEnv;
100 //A folder which is created in the temp directory in which then the updates are downloaded
101 OUString m_sDownloadFolder;
103 bool m_stop;
107 class UpdateCommandEnv
108 : public ::cppu::WeakImplHelper< css::ucb::XCommandEnvironment,
109 css::task::XInteractionHandler,
110 css::ucb::XProgressHandler >
112 friend class UpdateInstallDialog::Thread;
114 ::rtl::Reference<UpdateInstallDialog::Thread> m_installThread;
115 cssu::Reference< cssu::XComponentContext > m_xContext;
117 public:
118 UpdateCommandEnv( cssu::Reference< cssu::XComponentContext > const & xCtx,
119 ::rtl::Reference<UpdateInstallDialog::Thread>const & thread);
121 // XCommandEnvironment
122 virtual cssu::Reference<css::task::XInteractionHandler > SAL_CALL
123 getInteractionHandler() override;
124 virtual cssu::Reference<css::ucb::XProgressHandler >
125 SAL_CALL getProgressHandler() override;
127 // XInteractionHandler
128 virtual void SAL_CALL handle(
129 cssu::Reference<css::task::XInteractionRequest > const & xRequest ) override;
131 // XProgressHandler
132 virtual void SAL_CALL push( cssu::Any const & Status ) override;
133 virtual void SAL_CALL update( cssu::Any const & Status ) override;
134 virtual void SAL_CALL pop() override;
138 UpdateInstallDialog::Thread::Thread(
139 cssu::Reference< cssu::XComponentContext> const & xCtx,
140 UpdateInstallDialog & dialog,
141 std::vector< dp_gui::UpdateData > & aVecUpdateData):
142 salhelper::Thread("dp_gui_updateinstalldialog"),
143 m_dialog(dialog),
144 m_xComponentContext(xCtx),
145 m_aVecUpdateData(aVecUpdateData),
146 m_updateCmdEnv(new UpdateCommandEnv(xCtx, this)),
147 m_stop(false)
150 void UpdateInstallDialog::Thread::stop() {
151 cssu::Reference< css::task::XAbortChannel > abort;
153 SolarMutexGuard g;
154 abort = m_abort;
155 m_stop = true;
157 if (abort.is()) {
158 abort->sendAbort();
162 UpdateInstallDialog::Thread::~Thread() {}
164 void UpdateInstallDialog::Thread::execute()
166 try {
167 downloadExtensions();
168 installExtensions();
170 catch (...)
174 //clean up the temp directories
175 try {
176 removeTempDownloads();
177 } catch( ... ) {
181 //make sure m_dialog is still alive
182 SolarMutexGuard g;
183 if (! m_stop)
184 m_dialog.updateDone();
186 //UpdateCommandEnv keeps a reference to Thread and prevents destruction. Therefore remove it.
187 m_updateCmdEnv->m_installThread.clear();
190 UpdateInstallDialog::UpdateInstallDialog(
191 weld::Window* pParent,
192 std::vector<dp_gui::UpdateData> & aVecUpdateData,
193 cssu::Reference< cssu::XComponentContext > const & xCtx)
194 : GenericDialogController(pParent, "desktop/ui/updateinstalldialog.ui",
195 "UpdateInstallDialog")
196 , m_thread(new Thread(xCtx, *this, aVecUpdateData))
197 , m_bError(false)
198 , m_bNoEntry(true)
199 , m_sInstalling(DpResId(RID_DLG_UPDATE_INSTALL_INSTALLING))
200 , m_sFinished(DpResId(RID_DLG_UPDATE_INSTALL_FINISHED))
201 , m_sNoErrors(DpResId(RID_DLG_UPDATE_INSTALL_NO_ERRORS))
202 , m_sErrorDownload(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_DOWNLOAD))
203 , m_sErrorInstallation(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_INSTALLATION))
204 , m_sErrorLicenseDeclined(DpResId(RID_DLG_UPDATE_INSTALL_ERROR_LIC_DECLINED))
205 , m_sNoInstall(DpResId(RID_DLG_UPDATE_INSTALL_EXTENSION_NOINSTALL))
206 , m_sThisErrorOccurred(DpResId(RID_DLG_UPDATE_INSTALL_THIS_ERROR_OCCURRED))
207 , m_xFt_action(m_xBuilder->weld_label("DOWNLOADING"))
208 , m_xStatusbar(m_xBuilder->weld_progress_bar("STATUSBAR"))
209 , m_xFt_extension_name(m_xBuilder->weld_label("EXTENSION_NAME"))
210 , m_xMle_info(m_xBuilder->weld_text_view("INFO"))
211 , m_xHelp(m_xBuilder->weld_button("help"))
212 , m_xOk(m_xBuilder->weld_button("ok"))
213 , m_xCancel(m_xBuilder->weld_button("cancel"))
215 m_xMle_info->set_size_request(m_xMle_info->get_approximate_digit_width() * 52,
216 m_xMle_info->get_height_rows(5));
218 m_xExtensionManager = css::deployment::ExtensionManager::get( xCtx );
220 m_xCancel->connect_clicked(LINK(this, UpdateInstallDialog, cancelHandler));
221 if ( ! dp_misc::office_is_running())
222 m_xHelp->set_sensitive(false);
225 UpdateInstallDialog::~UpdateInstallDialog()
229 short UpdateInstallDialog::run()
231 m_thread->launch();
232 short nRet = GenericDialogController::run();
233 m_thread->stop();
234 return nRet;
237 // make sure the solar mutex is locked before calling
238 void UpdateInstallDialog::updateDone()
240 if (!m_bError)
241 m_xMle_info->set_text(m_xMle_info->get_text() + m_sNoErrors);
242 m_xOk->set_sensitive(true);
243 m_xOk->grab_focus();
244 m_xCancel->set_sensitive(false);
247 // make sure the solar mutex is locked before calling
248 //sets an error message in the text area
249 void UpdateInstallDialog::setError(INSTALL_ERROR err, OUString const & sExtension,
250 OUString const & exceptionMessage)
252 OUString sError;
253 m_bError = true;
255 switch (err)
257 case ERROR_DOWNLOAD:
258 sError = m_sErrorDownload;
259 break;
260 case ERROR_INSTALLATION:
261 sError = m_sErrorInstallation;
262 break;
263 case ERROR_LICENSE_DECLINED:
264 sError = m_sErrorLicenseDeclined;
265 break;
267 default:
268 OSL_ASSERT(false);
271 OUString sMsg(m_xMle_info->get_text());
272 sError = sError.replaceFirst("%NAME", sExtension);
273 //We want to have an empty line between the error messages. However,
274 //there shall be no empty line after the last entry.
275 if (m_bNoEntry)
276 m_bNoEntry = false;
277 else
278 sMsg += "\n";
279 sMsg += sError;
280 //Insert more information about the error
281 if (!exceptionMessage.isEmpty())
282 sMsg += m_sThisErrorOccurred + exceptionMessage + "\n";
284 sMsg += m_sNoInstall + "\n";
286 m_xMle_info->set_text(sMsg);
289 void UpdateInstallDialog::setError(OUString const & exceptionMessage)
291 m_bError = true;
292 m_xMle_info->set_text(m_xMle_info->get_text() + exceptionMessage + "\n");
295 IMPL_LINK_NOARG(UpdateInstallDialog, cancelHandler, weld::Button&, void)
297 m_xDialog->response(RET_CANCEL);
300 void UpdateInstallDialog::Thread::downloadExtensions()
304 //create the download directory in the temp folder
305 OUString sTempDir;
306 if (::osl::FileBase::getTempDirURL(sTempDir) != ::osl::FileBase::E_None)
307 throw cssu::Exception("Could not get URL for the temp directory. No extensions will be installed.", nullptr);
309 //create a unique name for the directory
310 OUString tempEntry, destFolder;
311 if (::osl::File::createTempFile(&sTempDir, nullptr, &tempEntry ) != ::osl::File::E_None)
312 throw cssu::Exception("Could not create a temporary file in " + sTempDir +
313 ". No extensions will be installed", nullptr );
315 tempEntry = tempEntry.copy( tempEntry.lastIndexOf( '/' ) + 1 );
317 destFolder = dp_misc::makeURL( sTempDir, tempEntry ) + "_";
318 m_sDownloadFolder = destFolder;
321 dp_misc::create_folder(nullptr, destFolder, m_updateCmdEnv.get() );
322 } catch (const cssu::Exception & e)
324 css::uno::Any anyEx = cppu::getCaughtException();
325 throw css::lang::WrappedTargetException( e.Message + " No extensions will be installed",
326 nullptr, anyEx );
330 sal_uInt16 count = 0;
331 for (auto & updateData : m_aVecUpdateData)
333 if (!updateData.aUpdateInfo.is() || updateData.aUpdateSource.is())
334 continue;
335 //We assume that m_aVecUpdateData contains only information about extensions which
336 //can be downloaded directly.
337 OSL_ASSERT(updateData.sWebsiteURL.isEmpty());
339 //update the name of the extension which is to be downloaded
341 SolarMutexGuard g;
342 if (m_stop) {
343 return;
345 m_dialog.m_xFt_extension_name->set_label(updateData.aInstalledPackage->getDisplayName());
346 sal_uInt16 prog = (sal::static_int_cast<sal_uInt16>(100) * ++count) /
347 sal::static_int_cast<sal_uInt16>(m_aVecUpdateData.size());
348 m_dialog.m_xStatusbar->set_percentage(prog);
350 dp_misc::DescriptionInfoset info(m_xComponentContext, updateData.aUpdateInfo);
351 //remember occurring exceptions in case we need to print out error information
352 std::vector< std::pair<OUString, cssu::Exception> > vecExceptions;
353 cssu::Sequence<OUString> seqDownloadURLs = info.getUpdateDownloadUrls();
354 OSL_ENSURE(seqDownloadURLs.hasElements(), "No download URL provided!");
355 for (sal_Int32 j = 0; j < seqDownloadURLs.getLength(); j++)
359 OSL_ENSURE(!seqDownloadURLs[j].isEmpty(), "Download URL is empty!");
360 bool bCancelled = download(seqDownloadURLs[j], updateData);
361 if (bCancelled || !updateData.sLocalURL.isEmpty())
362 break;
364 catch ( cssu::Exception & e )
366 vecExceptions.emplace_back(seqDownloadURLs[j], e);
367 //There can be several different errors, for example, the URL is wrong, webserver cannot be reached,
368 //name cannot be resolved. The UCB helper API does not specify different special exceptions for these
369 //cases. Therefore ignore and continue.
370 continue;
373 //update the progress and display download error
375 SolarMutexGuard g;
376 if (m_stop) {
377 return;
379 if (updateData.sLocalURL.isEmpty())
381 //Construct a string of all messages contained in the exceptions plus the respective download URLs
382 OUStringBuffer buf(256);
383 size_t nPos = 0;
384 for (auto const& elem : vecExceptions)
386 if (nPos)
387 buf.append("\n");
388 buf.append("Could not download ");
389 buf.append(elem.first);
390 buf.append(". ");
391 buf.append(elem.second.Message);
392 ++nPos;
394 m_dialog.setError(UpdateInstallDialog::ERROR_DOWNLOAD, updateData.aInstalledPackage->getDisplayName(),
395 buf.makeStringAndClear());
401 catch (const cssu::Exception & e)
403 SolarMutexGuard g;
404 if (m_stop) {
405 return;
407 m_dialog.setError(e.Message);
411 void UpdateInstallDialog::Thread::installExtensions()
413 //Update the fix text in the dialog to "Installing extensions..."
415 SolarMutexGuard g;
416 if (m_stop) {
417 return;
419 m_dialog.m_xFt_action->set_label(m_dialog.m_sInstalling);
420 m_dialog.m_xStatusbar->set_percentage(0);
423 sal_uInt16 count = 0;
424 for (auto const& updateData : m_aVecUpdateData)
426 //update the name of the extension which is to be installed
428 SolarMutexGuard g;
429 if (m_stop) {
430 return;
432 //we only show progress after an extension has been installed.
433 if (count > 0) {
434 m_dialog.m_xStatusbar->set_percentage(
435 (sal::static_int_cast<sal_uInt16>(100) * count) /
436 sal::static_int_cast<sal_uInt16>(m_aVecUpdateData.size()));
438 m_dialog.m_xFt_extension_name->set_label(updateData.aInstalledPackage->getDisplayName());
440 bool bError = false;
441 bool bLicenseDeclined = false;
442 cssu::Reference<css::deployment::XPackage> xExtension;
443 cssu::Exception exc;
446 cssu::Reference< css::task::XAbortChannel > xAbortChannel(
447 updateData.aInstalledPackage->createAbortChannel() );
449 SolarMutexGuard g;
450 if (m_stop) {
451 return;
453 m_abort = xAbortChannel;
455 if (!updateData.aUpdateSource.is() && !updateData.sLocalURL.isEmpty())
457 css::beans::NamedValue prop("EXTENSION_UPDATE", css::uno::makeAny(OUString("1")));
458 if (!updateData.bIsShared)
459 xExtension = m_dialog.getExtensionManager()->addExtension(
460 updateData.sLocalURL, css::uno::Sequence<css::beans::NamedValue>(&prop, 1),
461 "user", xAbortChannel, m_updateCmdEnv.get());
462 else
463 xExtension = m_dialog.getExtensionManager()->addExtension(
464 updateData.sLocalURL, css::uno::Sequence<css::beans::NamedValue>(&prop, 1),
465 "shared", xAbortChannel, m_updateCmdEnv.get());
467 else if (updateData.aUpdateSource.is())
469 OSL_ASSERT(updateData.aUpdateSource.is());
470 //I am not sure if we should obtain the install properties and pass them into
471 //add extension. Currently it contains only "SUPPRESS_LICENSE". So it could happen
472 //that a license is displayed when updating from the shared repository, although the
473 //shared extension was installed using "SUPPRESS_LICENSE".
474 css::beans::NamedValue prop("EXTENSION_UPDATE", css::uno::makeAny(OUString("1")));
475 if (!updateData.bIsShared)
476 xExtension = m_dialog.getExtensionManager()->addExtension(
477 updateData.aUpdateSource->getURL(), css::uno::Sequence<css::beans::NamedValue>(&prop, 1),
478 "user", xAbortChannel, m_updateCmdEnv.get());
479 else
480 xExtension = m_dialog.getExtensionManager()->addExtension(
481 updateData.aUpdateSource->getURL(), css::uno::Sequence<css::beans::NamedValue>(&prop, 1),
482 "shared", xAbortChannel, m_updateCmdEnv.get());
485 catch (css::deployment::DeploymentException & de)
487 if (de.Cause.has<css::deployment::LicenseException>())
489 bLicenseDeclined = true;
491 else
493 exc = de.Cause.get<cssu::Exception>();
494 bError = true;
497 catch (cssu::Exception& e)
499 exc = e;
500 bError = true;
503 if (bLicenseDeclined)
505 SolarMutexGuard g;
506 if (m_stop) {
507 return;
509 m_dialog.setError(UpdateInstallDialog::ERROR_LICENSE_DECLINED,
510 updateData.aInstalledPackage->getDisplayName(), OUString());
512 else if (!xExtension.is() || bError)
514 SolarMutexGuard g;
515 if (m_stop) {
516 return;
518 m_dialog.setError(UpdateInstallDialog::ERROR_INSTALLATION,
519 updateData.aInstalledPackage->getDisplayName(), exc.Message);
521 ++count;
524 SolarMutexGuard g;
525 if (m_stop) {
526 return;
528 m_dialog.m_xStatusbar->set_percentage(100);
529 m_dialog.m_xFt_extension_name->set_label(OUString());
530 m_dialog.m_xFt_action->set_label(m_dialog.m_sFinished);
534 void UpdateInstallDialog::Thread::removeTempDownloads()
536 if (!m_sDownloadFolder.isEmpty())
538 dp_misc::erase_path(m_sDownloadFolder,
539 cssu::Reference<css::ucb::XCommandEnvironment>(),false /* no throw: ignore errors */ );
540 //remove also the temp file which we have used to create the unique name
541 OUString tempFile = m_sDownloadFolder.copy(0, m_sDownloadFolder.getLength() - 1);
542 dp_misc::erase_path(tempFile, cssu::Reference<css::ucb::XCommandEnvironment>(),false);
543 m_sDownloadFolder.clear();
547 bool UpdateInstallDialog::Thread::download(OUString const & sDownloadURL, UpdateData & aUpdateData)
550 SolarMutexGuard g;
551 if (m_stop) {
552 return m_stop;
556 OSL_ASSERT(m_sDownloadFolder.getLength());
557 OUString destFolder, tempEntry;
558 if (::osl::File::createTempFile(
559 &m_sDownloadFolder,
560 nullptr, &tempEntry ) != ::osl::File::E_None)
562 //ToDo feedback in window that download of this component failed
563 throw cssu::Exception("Could not create temporary file in folder " + destFolder + ".", nullptr);
565 tempEntry = tempEntry.copy( tempEntry.lastIndexOf( '/' ) + 1 );
567 destFolder = dp_misc::makeURL( m_sDownloadFolder, tempEntry ) + "_";
569 ::ucbhelper::Content destFolderContent;
570 dp_misc::create_folder( &destFolderContent, destFolder, m_updateCmdEnv.get() );
572 ::ucbhelper::Content sourceContent;
573 (void)dp_misc::create_ucb_content(&sourceContent, sDownloadURL, m_updateCmdEnv.get());
575 const OUString sTitle( StrTitle::getTitle( sourceContent ) );
577 destFolderContent.transferContent(
578 sourceContent, ::ucbhelper::InsertOperation::Copy,
579 sTitle, css::ucb::NameClash::OVERWRITE );
582 //the user may have cancelled the dialog because downloading took too long
583 SolarMutexGuard g;
584 if (m_stop) {
585 return m_stop;
587 //all errors should be handled by the command environment.
588 aUpdateData.sLocalURL = destFolder + "/" + sTitle;
591 return m_stop;
594 UpdateCommandEnv::UpdateCommandEnv( cssu::Reference< cssu::XComponentContext > const & xCtx,
595 ::rtl::Reference<UpdateInstallDialog::Thread>const & thread)
596 : m_installThread(thread),
597 m_xContext(xCtx)
601 // XCommandEnvironment
602 cssu::Reference<css::task::XInteractionHandler> UpdateCommandEnv::getInteractionHandler()
604 return this;
607 cssu::Reference<css::ucb::XProgressHandler> UpdateCommandEnv::getProgressHandler()
609 return this;
612 // XInteractionHandler
613 void UpdateCommandEnv::handle(
614 cssu::Reference< css::task::XInteractionRequest> const & xRequest )
616 cssu::Any request( xRequest->getRequest() );
617 OSL_ASSERT( request.getValueTypeClass() == cssu::TypeClass_EXCEPTION );
618 dp_misc::TRACE("[dp_gui_cmdenv.cxx] incoming request:\n"
619 + ::comphelper::anyToString(request) + "\n\n");
621 css::deployment::VersionException verExc;
622 bool approve = false;
624 if (request >>= verExc)
625 { //We must catch the version exception during the update,
626 //because otherwise the user would be confronted with the dialogs, asking
627 //them if they want to replace an already installed version of the same extension.
628 //During an update we assume that we always want to replace the old version with the
629 //new version.
630 approve = true;
633 if (!approve)
635 //forward to interaction handler for main dialog.
636 handleInteractionRequest( m_xContext, xRequest );
638 else
640 // select:
641 cssu::Sequence< cssu::Reference< css::task::XInteractionContinuation > > conts(
642 xRequest->getContinuations() );
643 cssu::Reference< css::task::XInteractionContinuation > const * pConts =
644 conts.getConstArray();
645 sal_Int32 len = conts.getLength();
646 for ( sal_Int32 pos = 0; pos < len; ++pos )
648 if (approve) {
649 cssu::Reference< css::task::XInteractionApprove > xInteractionApprove(
650 pConts[ pos ], cssu::UNO_QUERY );
651 if (xInteractionApprove.is()) {
652 xInteractionApprove->select();
653 // don't query again for ongoing continuations:
654 approve = false;
661 // XProgressHandler
662 void UpdateCommandEnv::push( cssu::Any const & /*Status*/ )
666 void UpdateCommandEnv::update( cssu::Any const & /*Status */)
670 void UpdateCommandEnv::pop()
675 } //end namespace dp_gui
677 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */