Commit a2f0275b authored by Dmitry Kazakov's avatar Dmitry Kazakov

Implement progress reporting for asynchronous saving

This patch refactors KoProgresssUpdater a bit:

1) Multiple subtasks are handled correctly now
2) Subtasks names can be nested
3) KisViewManager creates not KoProgressUpdater, but KoUpdater class,
   which is a part of the global progress queue. Therefore, there
   in no concurrent access to the progress bar now.
parent 20e980c4
......@@ -44,7 +44,7 @@ void KisProjectionBenchmark::benchmarkProjection()
KisDocument *doc = KisPart::instance()->createDocument();
doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test.kra");
doc->image()->refreshGraph();
doc->exportDocument(QUrl::fromLocalFile(QString(FILES_OUTPUT_DIR) + QDir::separator() + "save_test.kra"), doc->mimeType());
doc->exportDocumentSync(QUrl::fromLocalFile(QString(FILES_OUTPUT_DIR) + QDir::separator() + "save_test.kra"), doc->mimeType());
delete doc;
}
}
......
......@@ -64,3 +64,10 @@ void KisCompositeProgressProxy::setFormat(const QString &format)
}
}
void KisCompositeProgressProxy::setAutoNestedName(const QString &name)
{
Q_FOREACH (KoProgressProxy *proxy, m_uniqueProxies) {
proxy->setAutoNestedName(name);
}
}
......@@ -30,10 +30,11 @@ public:
void addProxy(KoProgressProxy *proxy);
void removeProxy(KoProgressProxy *proxy);
int maximum() const;
void setValue(int value);
void setRange(int minimum, int maximum);
void setFormat(const QString &format);
int maximum() const override;
void setValue(int value) override;
void setRange(int minimum, int maximum) override;
void setFormat(const QString &format) override;
void setAutoNestedName(const QString &name) override;
private:
QList<KoProgressProxy*> m_proxies;
......
......@@ -42,10 +42,10 @@ class KRITAIMAGE_EXPORT KisNodeProgressProxy : public QObject, public KoProgress
~KisNodeProgressProxy();
public:
virtual int maximum() const ;
virtual void setValue(int value);
virtual void setRange(int minimum, int maximum);
virtual void setFormat(const QString & format);
int maximum() const override;
void setValue(int value) override;
void setRange(int minimum, int maximum) override;
void setFormat(const QString & format) override;
/**
* @return the current percentage (return -1 if no progress)
*/
......
......@@ -123,7 +123,13 @@ void KisQueuesProgressUpdater::timerTicked()
{
QMutexLocker locker(&m_d->mutex);
if (!m_d->initialQueueSizeMetric) {
m_d->progressProxy->setRange(0, 100);
m_d->progressProxy->setValue(100);
m_d->progressProxy->setFormat("%p%");
} else {
m_d->progressProxy->setRange(0, m_d->initialQueueSizeMetric);
m_d->progressProxy->setValue(m_d->initialQueueSizeMetric - m_d->queueSizeMetric);
m_d->progressProxy->setFormat(m_d->jobName);
}
}
......@@ -460,7 +460,7 @@ bool Node::save(const QString &filename, double xRes, double yRes)
dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0));
dst->initialRefreshGraph();
bool r = doc->exportDocument(QUrl::fromLocalFile(filename), mimeType.toLatin1());
bool r = doc->exportDocumentSync(QUrl::fromLocalFile(filename), mimeType.toLatin1());
if (!r) {
qWarning() << doc->errorMessage();
}
......
......@@ -467,7 +467,7 @@ bool KisApplication::start(const KisApplicationArguments &args)
qApp->processEvents(); // For vector layers to be updated
doc->setFileBatchMode(true);
if (!doc->exportDocument(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) {
if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) {
dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage();
}
nPrinted++;
......
......@@ -138,41 +138,6 @@ using namespace std;
*
**********************************************************/
namespace {
class DocumentProgressProxy : public KoProgressProxy {
public:
KisMainWindow *m_mainWindow;
DocumentProgressProxy(KisMainWindow *mainWindow)
: m_mainWindow(mainWindow)
{
}
~DocumentProgressProxy() override {
// signal that the job is done
setValue(-1);
}
int maximum() const override {
return 100;
}
void setValue(int value) override {
if (m_mainWindow) {
m_mainWindow->slotProgress(value);
}
}
void setRange(int /*minimum*/, int /*maximum*/) override {
}
void setFormat(const QString &/*format*/) override {
}
};
}
//static
QString KisDocument::newObjectName()
{
......@@ -301,9 +266,6 @@ public:
KoDocumentInfo *docInfo = 0;
KoProgressUpdater *progressUpdater = 0;
KoProgressProxy *progressProxy = 0;
KoUnit unit;
KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options]
......@@ -346,15 +308,13 @@ public:
KisIdleWatcher imageIdleWatcher;
QScopedPointer<KisSignalAutoConnection> imageIdleConnection;
bool suppressProgress = false;
KoProgressProxy* fileProgressProxy = 0;
QList<KisPaintingAssistantSP> assistants;
KisGridConfig gridConfig;
StdLockableWrapper<QMutex> savingLock;
QScopedPointer<KisDocument> backgroundSaveDocument;
QPointer<KoUpdater> savingUpdater;
QFuture<KisImportExportFilter::ConversionStatus> childSavingFuture;
KritaUtils::ExportFileJob backgroundSaveJob;
......@@ -572,7 +532,6 @@ bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bo
}
bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
......@@ -632,7 +591,6 @@ void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &jo
removeAutoSaveFiles();
}
emit clearStatusBarMessage();
emit completed();
emit sigSavingFinished();
}
......@@ -667,8 +625,27 @@ KisDocument* KisDocument::safeCreateClone()
return new KisDocument(*this);
}
bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
{
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return false;
}
d->savingImage = d->image;
const QString fileName = url.toLocalFile();
KisImportExportFilter::ConversionStatus status =
d->importExportManager->
exportDocument(fileName, fileName, mimeType, false, exportConfiguration);
d->savingImage = 0;
return status == KisImportExportFilter::OK;
}
bool KisDocument::initiateSavingInBackground(const QString statusMessage,
bool KisDocument::initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration)
......@@ -687,8 +664,6 @@ bool KisDocument::initiateSavingInBackground(const QString statusMessage,
d->backgroundSaveDocument.reset(clonedDocument.take());
d->backgroundSaveJob = job;
emit statusBarMessage(statusMessage);
connect(d->backgroundSaveDocument.data(),
SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus, const QString&)),
this,
......@@ -699,7 +674,8 @@ bool KisDocument::initiateSavingInBackground(const QString statusMessage,
receiverObject, receiverMethod, Qt::UniqueConnection);
bool started =
d->backgroundSaveDocument->startExportInBackground(job.filePath,
d->backgroundSaveDocument->startExportInBackground(actionName,
job.filePath,
job.filePath,
job.mimeType,
job.flags & KritaUtils::SaveShowWarnings,
......@@ -761,18 +737,22 @@ void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, K
if (!d->modifiedAfterAutosave) {
d->autoSaveTimer.stop(); // until the next change
}
emit clearStatusBarMessage();
}
}
bool KisDocument::startExportInBackground(const QString &location,
bool KisDocument::startExportInBackground(const QString &actionName,
const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
KisPropertiesConfigurationSP exportConfiguration)
{
d->savingImage = d->image;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
d->savingUpdater = window->viewManager()->createUpdater(actionName, false);
d->importExportManager->setUpdater(d->savingUpdater);
d->childSavingFuture =
d->importExportManager->exportDocumentAsyc(location,
realLocation,
......@@ -806,6 +786,11 @@ void KisDocument::finishExportInBackground()
d->savingImage.clear();
d->childSavingFuture = QFuture<KisImportExportFilter::ConversionStatus>();
d->lastErrorMessage.clear();
if (d->savingUpdater) {
d->savingUpdater->setProgress(100);
}
emit sigBackgroundSavingFinished(status, errorMessage);
}
......@@ -1046,7 +1031,9 @@ bool KisDocument::openFile()
}
dbgUI << localFilePath() << "type:" << typeName;
setFileProgressUpdater(i18n("Opening Document"));
KisMainWindow *window = KisPart::instance()->currentMainwindow();
KoUpdaterPtr updater = window->viewManager()->createUpdater(i18n("Opening document"), false);
d->importExportManager->setUpdater(updater);
KisImportExportFilter::ConversionStatus status;
......@@ -1059,7 +1046,6 @@ bool KisDocument::openFile()
errorMessage().split("\n") + warningMessage().split("\n"));
dlg.exec();
}
clearFileProgressUpdater();
return false;
}
else if (!warningMessage().isEmpty()) {
......@@ -1073,42 +1059,11 @@ bool KisDocument::openFile()
setMimeTypeAfterLoading(typeName);
emit sigLoadingFinished();
if (!d->suppressProgress && d->progressUpdater) {
QPointer<KoUpdater> updater = d->progressUpdater->startSubtask(1, "clear undo stack");
updater->setProgress(0);
undoStack()->clear();
updater->setProgress(100);
clearFileProgressUpdater();
} else {
undoStack()->clear();
}
return true;
}
KoProgressUpdater *KisDocument::progressUpdater() const
{
return d->progressUpdater;
}
void KisDocument::setProgressProxy(KoProgressProxy *progressProxy)
{
d->progressProxy = progressProxy;
}
KoProgressProxy* KisDocument::progressProxy() const
{
if (!d->progressProxy) {
KisMainWindow *mainWindow = 0;
if (KisPart::instance()->mainwindowCount() > 0) {
mainWindow = KisPart::instance()->mainWindows()[0];
}
d->progressProxy = new DocumentProgressProxy(mainWindow);
}
return d->progressProxy;
}
// shared between openFile and koMainWindow's "create new empty document" code
void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
{
......@@ -1461,10 +1416,9 @@ bool KisDocument::openUrlInternal(const QUrl &url)
d->mimeType = mime.toLocal8Bit();
d->m_bAutoDetectedMime = true;
}
setFileProgressProxy();
setUrl(d->m_url);
ret = openFile();
clearFileProgressProxy();
if (ret) {
emit completed();
......@@ -1610,52 +1564,6 @@ KisNodeSP KisDocument::preActivatedNode() const
return d->preActivatedNode;
}
void KisDocument::setFileProgressUpdater(const QString &text)
{
d->suppressProgress = d->importExportManager->batchMode();
if (!d->suppressProgress) {
d->progressUpdater = new KoProgressUpdater(d->progressProxy, KoProgressUpdater::Unthreaded);
d->progressUpdater->start(100, text);
d->importExportManager->setProgresUpdater(d->progressUpdater);
if (KisPart::instance()->currentMainwindow()) {
connect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
connect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled()));
}
}
}
void KisDocument::clearFileProgressUpdater()
{
if (!d->suppressProgress && d->progressUpdater) {
if (KisPart::instance()->currentMainwindow()) {
disconnect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled()));
disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
}
delete d->progressUpdater;
d->importExportManager->setProgresUpdater(0);
d->progressUpdater = 0;
}
}
void KisDocument::setFileProgressProxy()
{
if (!d->progressProxy && !d->importExportManager->batchMode()) {
d->fileProgressProxy = progressProxy();
} else {
d->fileProgressProxy = 0;
}
}
void KisDocument::clearFileProgressProxy()
{
if (d->fileProgressProxy) {
setProgressProxy(0);
delete d->fileProgressProxy;
d->fileProgressProxy = 0;
}
}
KisImageWSP KisDocument::image() const
{
return d->image;
......
......@@ -52,8 +52,6 @@ class KoShapeLayer;
class KoStore;
class KoOdfReadStore;
class KoDocumentInfo;
class KoProgressUpdater;
class KoProgressProxy;
class KoDocumentInfoDlg;
class KisImportExportManager;
class KisUndoStore;
......@@ -113,6 +111,13 @@ public:
*/
bool reload();
/**
* @brief try to clone the image. This method handles all the locking for you. If locking
* has failed, no cloning happens
* @return cloned document on success, null otherwise
*/
KisDocument *safeCreateClone();
/**
* @brief openUrl Open an URL
* @param url The URL to open
......@@ -141,6 +146,8 @@ public:
*/
bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0);
bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0);
bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration);
/**
......@@ -277,23 +284,6 @@ public:
*/
KoDocumentInfo *documentInfo() const;
/**
* @return the object to report progress to.
*
* This is only not zero if loading or saving is in progress.
*
* One can add more KoUpdaters to it to make the progress reporting more
* accurate. If no active progress reporter is present, 0 is returned.
**/
KoProgressUpdater *progressUpdater() const;
/**
* Set a custom progress proxy to use to report loading
* progress to.
*/
void setProgressProxy(KoProgressProxy *progressProxy);
KoProgressProxy* progressProxy() const;
/**
* Performs a cleanup of unneeded backup files
*/
......@@ -458,12 +448,12 @@ private:
friend class KisPart;
friend class SafeSavingLocker;
bool initiateSavingInBackground(const QString statusMessage,
bool initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration);
bool startExportInBackground(const QString &location,
bool startExportInBackground(const QString &actionName, const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
......@@ -533,26 +523,6 @@ public:
*/
KisImageSP savingImage() const;
/**
* Adds progressproxy for file operations
*/
void setFileProgressProxy();
/**
* Clears progressproxy for file operations
*/
void clearFileProgressProxy();
/**
* Adds progressupdater for file operations
*/
void setFileProgressUpdater(const QString &text);
/**
* Clears progressupdater for file operations
*/
void clearFileProgressUpdater();
/**
* Set the current image to the specified image and turn undo on.
*/
......@@ -606,8 +576,6 @@ private Q_SLOTS:
private:
KisDocument *safeCreateClone();
QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
QString prettyPathOrUrl() const;
......
......@@ -71,7 +71,7 @@ class Q_DECL_HIDDEN KisImportExportManager::Private
{
public:
bool batchMode {false};
QPointer<KoProgressUpdater> progressUpdater {0};
KoUpdaterPtr updater;
};
struct KisImportExportManager::ConversionResult {
......@@ -242,9 +242,9 @@ bool KisImportExportManager::batchMode(void) const
return d->batchMode;
}
void KisImportExportManager::setProgresUpdater(KoProgressUpdater *updater)
void KisImportExportManager::setUpdater(KoUpdaterPtr updater)
{
d->progressUpdater = updater;
d->updater = updater;
}
QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent)
......@@ -289,8 +289,11 @@ KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImpo
filter->setBatchMode(batchMode());
filter->setMimeType(typeName);
if (d->progressUpdater) {
filter->setUpdater(d->progressUpdater->startSubtask());
if (!d->updater.isNull()) {
// WARNING: The updater is not guaranteed to be persistent! If you ever want
// to add progress reporting to "Save also as .kra", make sure you create
// a separate KoProgressUpdater for that!
filter->setUpdater(d->updater);
}
QByteArray from, to;
......
......@@ -122,7 +122,7 @@ public:
*/
bool batchMode(void) const;
void setProgresUpdater(KoProgressUpdater *updater);
void setUpdater(KoUpdaterPtr updater);
static QString askForAudioFileName(const QString &defaultDir, QWidget *parent);
......
......@@ -94,6 +94,7 @@
#include <KoDockRegistry.h>
#include <KoPluginLoader.h>
#include <KoColorSpaceEngine.h>
#include <KoUpdater.h>
#include <KisMimeDatabase.h>
#include <brushengine/kis_paintop_settings.h>
......@@ -1659,12 +1660,10 @@ void KisMainWindow::importAnimation()
int firstFrame = dlg.firstFrame();
int step = dlg.step();
document->setFileProgressProxy();
document->setFileProgressUpdater(i18n("Import frames"));
KisAnimationImporter importer(document);
KoUpdaterPtr updater =
!document->fileBatchMode() ? viewManager()->createUpdater(i18n("Import frames"), false) : 0;
KisAnimationImporter importer(document->image(), updater);
KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step);
document->clearFileProgressUpdater();
document->clearFileProgressProxy();
if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) {
QString msg = KisImportExportFilter::conversionStatusString(status);
......
......@@ -373,24 +373,12 @@ KisAnimationCachePopulator* KisPart::cachePopulator() const
void KisPart::openExistingFile(const QUrl &url)
{
Q_ASSERT(url.isLocalFile());
qApp->setOverrideCursor(Qt::BusyCursor);
KisDocument *document = createDocument();
if (!document->openUrl(url)) {
delete document;
return;
}
if (!document->image()) {
delete document;
return;
}
document->setModified(false);
addDocument(document);
// TODO: refactor out this method!
KisMainWindow *mw = currentMainwindow();
mw->addViewAndNotifyLoadingCompleted(document);
KIS_SAFE_ASSERT_RECOVER_RETURN(mw);
qApp->restoreOverrideCursor();
mw->openDocument(url, KisMainWindow::None);
}
void KisPart::updateShortcuts()
......
......@@ -121,7 +121,7 @@ bool KisSaveGroupVisitor::visit(KisGroupLayer *layer)
QUrl url = QUrl::fromLocalFile(path);
exportDocument->setFileBatchMode(true);
exportDocument->exportDocument(url, m_mimeFilter.toLatin1());
exportDocument->exportDocumentSync(url, m_mimeFilter.toLatin1());
if (!m_saveTopLevelOnly) {
KisGroupLayerSP child = dynamic_cast<KisGroupLayer*>(layer->firstChild().data());
......
......@@ -207,7 +207,7 @@ void KisTemplateCreateDia::createTemplate(const QString &templatesResourcePath,
}
fileName = tempFile.fileName();
}
bool retval = document->exportDocument(QUrl::fromLocalFile(fileName), document->mimeType());
bool retval = document->exportDocumentSync(QUrl::fromLocalFile(fileName), document->mimeType());
if (!retval) {
qWarning("Could not save template");
return;
......
......@@ -81,10 +81,8 @@
#include "KisUndoStackAction.h"
#include "KisViewManager.h"
#include "kis_zoom_manager.h"
#include "kis_composite_progress_proxy.h"
#include "kis_statusbar.h"
#include "kis_painting_assistants_decoration.h"
#include "kis_progress_widget.h"
#include "kis_signal_compressor.h"
#include "kis_filter_manager.h"
#include "kis_file_layer.h"
......@@ -261,10 +259,6 @@ KisView::KisView(KisDocument *document, KoCanvasResourceManager *resourceManager
KisView::~KisView()
{
if (d->viewManager) {
KoProgressProxy *proxy = d->viewManager->statusBar()->progress()->progressProxy();
KIS_ASSERT_RECOVER_NOOP(proxy);
image()->compositeProgressProxy()->removeProxy(proxy);
if (d->viewManager->filterManager()->isStrokeRunning()) {
d->viewManager->filterManager()->cancel();
}
......@@ -358,15 +352,6 @@ void KisView::setViewManager(KisViewManager *view)
SLOT(slotContinueRemoveNode(KisNodeSP)),
Qt::AutoConnection);
/*
* WARNING: Currently we access the global progress bar in two ways:
* connecting to composite progress proxy (strokes) and creating
* progress updaters. The latter way should be deprecated in favour
* of displaying the status of the global strokes queue
*/
image()->compositeProgressProxy()->addProxy(d->viewManager->statusBar()->progress()->progressProxy());
connect(d->viewManager->statusBar()->progress(), SIGNAL(sigCancellationRequested()), image(), SLOT(requestStrokeCancellation()));
d->viewManager->updateGUI();
KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush");
......@@ -681,7 +666,7 @@ void KisView::closeEvent(QCloseEvent *event)
}
if (queryClose()) {
d->viewManager->removeStatusBarItem(zoomManager()->zoomActionWidget());
d->viewManager->statusBar()->setView(0);
event->accept();
return;
}
......
......@@ -83,6 +83,7 @@
#include "kis_canvas_controls_manager.h"
#include "kis_canvas_resource_provider.h"
#include "kis_composite_progress_proxy.h"
#include <KoProgressUpdater.h>
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_control_frame.h"
......@@ -107,7 +108,7 @@
#include <brushengine/kis_paintop_preset.h>
#include "KisPart.h"
#include "KisPrintJob.h"
#include "kis_progress_widget.h"
#include <KoUpdater.h>
#include "kis_resource_server_provider.h"
#include "kis_selection.h"
#include "kis_selection_manager.h"
......@@ -128,6 +129,7 @@
#include "kis_guides_manager.h"
#include "kis_derived_resources.h"
#include "dialogs/kis_delayed_save_dialog.h"
#include <kis_image.h>
class BlockingUserInputEventFilter : public QObject
......@@ -221,6 +223,7 @@ public:
KisSelectionManager selectionManager;
KisGuidesManager guidesManager;
KisStatusBar statusBar;
QPointer<KoUpdater> persistentImageProgressUpdater;
KisControlFrame controlFrame;
KisNodeManager nodeManager;
......@@ -264,6 +267,13 @@ KisViewManager::KisViewManager(QWidget *parent, KActionCollection *_actionCollec
// These initialization functions must wait until KisViewManager ctor is complete.
d->statusBar.setup();
d->persistentImageProgressUpdater =
d->statusBar.progressUpdater()->startSubtask(1, "", true);
// reset state to nil
d->persistentImageProgressUpdater->setRange(0,100);
d->persistentImageProgressUpdater->setValue(100);
d->controlFrame.setup(parent);
//Check to draw scrollbars after "Canvas only mode" toggle is created.
......@@ -347,6 +357,7 @@ void KisViewManager::setCurrentView(KisView *view)
first = false;
KisDocument* doc = d->currentImageView->document();
if (doc) {
doc->image()->compositeProgressProxy()->removeProxy(d->persistentImageProgressUpdater);
doc->disconnect(this);