Commit a49ec4b0 authored by Dmitry Kazakov's avatar Dmitry Kazakov
Browse files

Implement a feedback for barrier blocking when doing some actions

Summary:
Some actions should not be started before all the previous actions
are finished. That is especially true for the actions that work
with layers stack, like "Merge Down" and "Flatten".

Now KisDelayedSaveDialog is used not only for saving, but also for
waiting before doing usual actions. It also has a busy-loop timeout of
1 second before showing up, for not distracting painters from their
workflow when the action is postponed only a little bit.

BUG:372724
Fixes T4593

Test Plan:
Basically, the steps like in bug 372724.

1) Select multiple layers
2) Start a long stroke (1000px colorsmudge brush is a good example)
3) Press Ctrl+E ***multiple*** times quickly
4) After all the background work is finished, press Ctrl+Z

There should be no crash, and the result should be somewhat expected.

Repeat the same for:
1) Merge down
2) Flatten Image
3) Flatten Layer
4) Transform a layer using "Layers" menu(?)

Reviewers: #krita, timotheegiet, scottpetrovic

Maniphest Tasks: T4593

Differential Revision: https://phabricator.kde.org/D3449
parent 294c5c74
......@@ -220,8 +220,10 @@ KisBaseNode::PropertyList KisLayer::sectionModelProperties() const
KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties();
l << KisBaseNode::Property(KoID("opacity", i18n("Opacity")), i18n("%1%", percentOpacity()));
if (compositeOp()) {
l << KisBaseNode::Property(KoID("compositeop", i18n("Composite Mode")), compositeOp()->description());
const KoCompositeOp * compositeOp = this->compositeOp();
if (compositeOp) {
l << KisBaseNode::Property(KoID("compositeop", i18n("Composite Mode")), compositeOp->description());
}
if (m_d->layerStyle && !m_d->layerStyle->isEmpty()) {
......
......@@ -47,6 +47,7 @@ void KisLegacyUndoAdapter::addCommand(KUndo2Command *command)
undoStore()->addCommand(command);
}
else {
// TODO: add feedback
m_image->barrierLock();
undoStore()->addCommand(command);
m_image->unlock();
......@@ -56,6 +57,7 @@ void KisLegacyUndoAdapter::addCommand(KUndo2Command *command)
void KisLegacyUndoAdapter::beginMacro(const KUndo2MagicString& macroName)
{
if(!m_macroCounter) {
// TODO: add feedback
m_image->barrierLock();
}
......
......@@ -881,7 +881,8 @@ bool KisMainWindow::saveDocument(KisDocument *document, bool saveas)
std::unique_lock<StdLockableWrapper<QMutex>> l(wrapper, std::try_to_lock);
if (!l.owns_lock()) return false;
KisDelayedSaveDialog dlg(document->image(), this);
// no busy wait for saving because it is dangerous!
KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this);
dlg.blockIfImageIsBusy();
if (dlg.result() != QDialog::Accepted) {
......
......@@ -126,6 +126,7 @@
#include "kis_icon_utils.h"
#include "kis_guides_manager.h"
#include "kis_derived_resources.h"
#include "dialogs/kis_delayed_save_dialog.h"
class BlockingUserInputEventFilter : public QObject
......@@ -241,6 +242,8 @@ public:
KSelectAction *actionAuthor; // Select action for author profile.
QByteArray canvasState;
bool blockUntillOperationsFinishedImpl(KisImageSP image, bool force);
};
......@@ -743,6 +746,26 @@ int KisViewManager::viewCount() const
return 0;
}
bool KisViewManager::KisViewManagerPrivate::blockUntillOperationsFinishedImpl(KisImageSP image, bool force)
{
const int busyWaitDelay = 1000;
KisDelayedSaveDialog dialog(image, !force ? KisDelayedSaveDialog::GeneralDialog : KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, mainWindow);
dialog.blockIfImageIsBusy();
return dialog.result() == QDialog::Accepted;
}
bool KisViewManager::blockUntillOperationsFinished(KisImageSP image)
{
return d->blockUntillOperationsFinishedImpl(image, false);
}
void KisViewManager::blockUntillOperationsFinishedForced(KisImageSP image)
{
d->blockUntillOperationsFinishedImpl(image, true);
}
void KisViewManager::slotCreateTemplate()
{
if (!document()) return;
......@@ -1260,5 +1283,3 @@ void KisViewManager::slotUpdateAuthorProfileActions()
d->actionAuthor->setCurrentItem(0);
}
}
......@@ -165,6 +165,24 @@ public: // Krita specific interfaces
int viewCount() const;
/**
* @brief blockUntillOperationsFinished blocks the GUI of the application until execution
* of actions on \p image is finished
* @param image the image which we should wait for
* @return true if the image has finished execution of the actions, false if
* the user cancelled operation
*/
bool blockUntillOperationsFinished(KisImageSP image);
/**
* @brief blockUntillOperationsFinished blocks the GUI of the application until execution
* of actions on \p image is finished. Does *not* provide a "Cancel" button. So the
* user is forced to wait.
* @param image the image which we should wait for
*/
void blockUntillOperationsFinishedForced(KisImageSP image);
public:
KisGridManager * gridManager() const;
......
......@@ -369,6 +369,7 @@ void KisCopyMergedActionFactory::run(KisViewManager *view)
{
KisImageWSP image = view->image();
if (!image) return;
if (!view->blockUntillOperationsFinished(image)) return;
image->barrierLock();
KisPaintDeviceSP dev = image->root()->projection();
......
......@@ -288,15 +288,15 @@ bool KisCanvas2::yAxisMirrored() const
void KisCanvas2::channelSelectionChanged()
{
KisImageWSP image = this->image();
KisImageSP image = this->image();
m_d->channelFlags = image->rootLayer()->channelFlags();
m_d->view->viewManager()->blockUntillOperationsFinishedForced(image);
image->barrierLock();
m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags);
startUpdateInPatches(image->bounds());
image->unlock();
}
void KisCanvas2::addCommand(KUndo2Command *command)
......@@ -524,12 +524,12 @@ void KisCanvas2::startUpdateInPatches(const QRect &imageRect)
void KisCanvas2::setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter)
{
m_d->displayColorConverter.setDisplayFilter(displayFilter);
KisImageWSP image = this->image();
KisImageSP image = this->image();
image->barrierLock();
m_d->view->viewManager()->blockUntillOperationsFinishedForced(image);
image->barrierLock();
m_d->canvasWidget->setDisplayFilter(displayFilter);
image->unlock();
}
......
......@@ -20,6 +20,8 @@
#include "ui_kis_delayed_save_dialog.h"
#include <QTimer>
#include <QElapsedTimer>
#include <QThread>
#include "kis_debug.h"
#include "kis_image.h"
......@@ -28,24 +30,36 @@
struct KisDelayedSaveDialog::Private
{
Private(KisImageSP _image) : image(_image) {}
Private(KisImageSP _image, int _busyWait) : image(_image), busyWait(_busyWait) {}
KisImageSP image;
QTimer updateTimer;
int busyWait;
};
KisDelayedSaveDialog::KisDelayedSaveDialog(KisImageSP image, QWidget *parent)
KisDelayedSaveDialog::KisDelayedSaveDialog(KisImageSP image, Type type, int busyWait, QWidget *parent)
: QDialog(parent),
ui(new Ui::KisDelayedSaveDialog),
m_d(new Private(image))
m_d(new Private(image, busyWait))
{
KIS_ASSERT_RECOVER_NOOP(image);
ui->setupUi(this);
connect(ui->bnDontWait, SIGNAL(clicked()), SLOT(accept()));
if (type == SaveDialog) {
connect(ui->bnDontWait, SIGNAL(clicked()), SLOT(accept()));
connect(ui->bnCancel, SIGNAL(clicked()), SLOT(slotCancelRequested()));
} else {
ui->bnDontSave->setText(i18n("Cancel"));
ui->bnDontWait->setVisible(false);
ui->bnCancel->setVisible(false);
if (type == ForcedDialog) {
ui->bnDontSave->setVisible(false);
}
}
connect(ui->bnDontSave, SIGNAL(clicked()), SLOT(reject()));
connect(ui->bnCancel, SIGNAL(clicked()), SLOT(slotCancelRequested()));
connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(slotTimerTimeout()));
......@@ -67,6 +81,20 @@ void KisDelayedSaveDialog::blockIfImageIsBusy()
m_d->image->requestStrokeEnd();
QElapsedTimer t;
t.start();
while (t.elapsed() < m_d->busyWait) {
QApplication::processEvents();
if (m_d->image->isIdle()) {
setResult(Accepted);
return;
}
QThread::yieldCurrentThread();
}
m_d->updateTimer.start(200);
exec();
m_d->updateTimer.stop();
......
......@@ -32,7 +32,14 @@ class KisDelayedSaveDialog : public QDialog
Q_OBJECT
public:
explicit KisDelayedSaveDialog(KisImageSP image, QWidget *parent = 0);
enum Type {
SaveDialog,
GeneralDialog,
ForcedDialog
};
public:
explicit KisDelayedSaveDialog(KisImageSP image, Type type, int busyWait, QWidget *parent = 0);
~KisDelayedSaveDialog();
void blockIfImageIsBusy();
......
......@@ -196,7 +196,9 @@ void KisFilterManager::showFilterDialog(const QString &filterId)
* The UI should show only after every running stroke is finished,
* so a virtual barrier is added here.
*/
d->view->image()->waitForDone();
if (!d->view->blockUntillOperationsFinished(d->view->image())) {
return;
}
Q_ASSERT(d->view);
Q_ASSERT(d->view->activeNode());
......
......@@ -570,6 +570,8 @@ void KisLayerManager::rotateLayer(double radians)
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntillOperationsFinished(m_view->image())) return;
m_view->image()->rotateNode(layer, radians);
}
......@@ -580,12 +582,16 @@ void KisLayerManager::shearLayer(double angleX, double angleY)
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntillOperationsFinished(m_view->image())) return;
m_view->image()->shearNode(layer, angleX, angleY);
}
void KisLayerManager::flattenImage()
{
KisImageWSP image = m_view->image();
KisImageSP image = m_view->image();
if (!m_view->blockUntillOperationsFinished(image)) return;
if (image) {
bool doIt = true;
......@@ -634,12 +640,14 @@ bool tryMergeSelectionMasks(KisNodeSP currentNode, KisImageSP image)
void KisLayerManager::mergeLayer()
{
KisImageWSP image = m_view->image();
KisImageSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntillOperationsFinished(image)) return;
QList<KisNodeSP> selectedNodes = m_view->nodeManager()->selectedNodes();
if (selectedNodes.size() > 1) {
image->mergeMultipleLayers(selectedNodes, m_view->activeNode());
......@@ -665,24 +673,28 @@ void KisLayerManager::mergeLayer()
void KisLayerManager::flattenLayer()
{
KisImageWSP image = m_view->image();
KisImageSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntillOperationsFinished(image)) return;
image->flattenLayer(layer);
m_view->updateGUI();
}
void KisLayerManager::rasterizeLayer()
{
KisImageWSP image = m_view->image();
KisImageSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntillOperationsFinished(image)) return;
KisPaintLayerSP paintLayer = new KisPaintLayer(image, layer->name(), layer->opacity());
KisPainter gc(paintLayer->paintDevice());
QRect rc = layer->projection()->exactBounds();
......@@ -749,7 +761,7 @@ void KisLayerManager::saveGroupLayers()
QString extension = KisMimeDatabase::suffixesForMimeType(mimeType).first();
QString basename = f.baseName();
KisImageWSP image = m_view->image();
KisImageSP image = m_view->image();
if (!image) return;
KisSaveGroupVisitor v(image, chkInvisible->isChecked(), chkDepth->isChecked(), f.absolutePath(), basename, extension, mimeType);
......@@ -805,6 +817,8 @@ void KisLayerManager::layerStyle()
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntillOperationsFinished(image)) return;
KisPSDLayerStyleSP oldStyle;
if (layer->layerStyle()) {
oldStyle = layer->layerStyle()->clone();
......
......@@ -147,12 +147,13 @@ bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node)
} else {
KoShape * shape = view->document()->shapeForNode(node);
Q_ASSERT(shape);
KIS_ASSERT_RECOVER_RETURN_VALUE(shape, false);
selection->select(shape);
KoShapeLayer * shapeLayer = dynamic_cast<KoShapeLayer*>(shape);
Q_ASSERT(shapeLayer);
KIS_ASSERT_RECOVER_RETURN_VALUE(shapeLayer, false);
// shapeLayer->setGeometryProtected(node->userLocked());
// shapeLayer->setVisible(node->visible());
selection->setActiveLayer(shapeLayer);
......
......@@ -550,6 +550,10 @@ void KisTool::deleteSelection()
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager());
KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
KisViewManager* viewManager = kiscanvas->viewManager();
viewManager->blockUntillOperationsFinished(image());
if (!KisToolUtils::clearImage(image(), resources->currentNode(), resources->activeSelection())) {
KoToolBase::deleteSelection();
}
......
......@@ -202,9 +202,9 @@ void DlgClonesArray::reapplyClones()
{
cancelClicked();
KisImageWSP image = m_view->image();
image->barrierLock();
image->unlock();
KisImageSP image = m_view->image();
if (!m_view->blockUntillOperationsFinished(image)) return;
m_applicator =
new KisProcessingApplicator(image, 0,
......
......@@ -82,6 +82,8 @@ void WaveletDecompose::slotWaveletDecompose()
KisImageSP image = m_view->image();
if (!image) return;
if (!m_view->blockUntillOperationsFinished(image)) return;
image->barrierLock();
KisPaintDeviceSP projection = new KisPaintDevice(*(image->projection()), false, 0);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment