1 // Aseprite
2 // Copyright (C) 2001-2018  David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6 
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 
11 #include "app/ui/editor/moving_pixels_state.h"
12 
13 #include "app/app.h"
14 #include "app/color_utils.h"
15 #include "app/commands/cmd_flip.h"
16 #include "app/commands/cmd_move_mask.h"
17 #include "app/commands/cmd_rotate.h"
18 #include "app/commands/command.h"
19 #include "app/commands/commands.h"
20 #include "app/console.h"
21 #include "app/modules/gui.h"
22 #include "app/pref/preferences.h"
23 #include "app/tools/ink.h"
24 #include "app/tools/tool.h"
25 #include "app/ui/context_bar.h"
26 #include "app/ui/editor/editor.h"
27 #include "app/ui/editor/editor_customization_delegate.h"
28 #include "app/ui/editor/pixels_movement.h"
29 #include "app/ui/editor/standby_state.h"
30 #include "app/ui/editor/transform_handles.h"
31 #include "app/ui/keyboard_shortcuts.h"
32 #include "app/ui/main_window.h"
33 #include "app/ui/status_bar.h"
34 #include "app/ui_context.h"
35 #include "app/util/clipboard.h"
36 #include "base/bind.h"
37 #include "base/pi.h"
38 #include "base/unique_ptr.h"
39 #include "doc/algorithm/flip_image.h"
40 #include "doc/mask.h"
41 #include "doc/sprite.h"
42 #include "gfx/rect.h"
43 #include "ui/manager.h"
44 #include "ui/message.h"
45 #include "ui/system.h"
46 #include "ui/view.h"
47 
48 #include <cstring>
49 
50 namespace app {
51 
52 using namespace ui;
53 
MovingPixelsState(Editor * editor,MouseMessage * msg,PixelsMovementPtr pixelsMovement,HandleType handle)54 MovingPixelsState::MovingPixelsState(Editor* editor, MouseMessage* msg, PixelsMovementPtr pixelsMovement, HandleType handle)
55   : m_pixelsMovement(pixelsMovement)
56   , m_editor(editor)
57   , m_observingEditor(false)
58   , m_discarded(false)
59 {
60   // MovingPixelsState needs a selection tool to avoid problems
61   // sharing the extra cel between the drawing cursor preview and the
62   // pixels movement/transformation preview.
63   //ASSERT(!editor->getCurrentEditorInk()->isSelection());
64 
65   UIContext* context = UIContext::instance();
66 
67   if (handle != NoHandle) {
68     gfx::Point pt = editor->screenToEditor(msg->position());
69     m_pixelsMovement->catchImage(pt, handle);
70 
71     editor->captureMouse();
72   }
73 
74   // Setup transparent mode/mask color
75   if (Preferences::instance().selection.autoOpaque()) {
76     Preferences::instance().selection.opaque(
77       editor->layer()->isBackground());
78   }
79   onTransparentColorChange();
80 
81   // Hook BeforeCommandExecution signal so we know if the user wants
82   // to execute other command, so we can drop pixels.
83   m_ctxConn =
84     context->BeforeCommandExecution.connect(&MovingPixelsState::onBeforeCommandExecution, this);
85 
86   // Listen to any change to the transparent color from the ContextBar.
87   m_opaqueConn =
88     Preferences::instance().selection.opaque.AfterChange.connect(
89       base::Bind<void>(&MovingPixelsState::onTransparentColorChange, this));
90   m_transparentConn =
91     Preferences::instance().selection.transparentColor.AfterChange.connect(
92       base::Bind<void>(&MovingPixelsState::onTransparentColorChange, this));
93 
94   // Add the current editor as filter for key message of the manager
95   // so we can catch the Enter key, and avoid to execute the
96   // PlayAnimation command.
97   m_editor->manager()->addMessageFilter(kKeyDownMessage, m_editor);
98   m_editor->manager()->addMessageFilter(kKeyUpMessage, m_editor);
99   m_editor->add_observer(this);
100   m_observingEditor = true;
101 
102   ContextBar* contextBar = App::instance()->contextBar();
103   contextBar->updateForMovingPixels();
104   contextBar->add_observer(this);
105 }
106 
~MovingPixelsState()107 MovingPixelsState::~MovingPixelsState()
108 {
109   ContextBar* contextBar = App::instance()->contextBar();
110   contextBar->remove_observer(this);
111   contextBar->updateForActiveTool();
112 
113   removePixelsMovement();
114   removeAsEditorObserver();
115 
116   m_editor->manager()->removeMessageFilter(kKeyDownMessage, m_editor);
117   m_editor->manager()->removeMessageFilter(kKeyUpMessage, m_editor);
118 
119   m_editor->document()->generateMaskBoundaries();
120 }
121 
translate(const gfx::Point & delta)122 void MovingPixelsState::translate(const gfx::Point& delta)
123 {
124   if (m_pixelsMovement->isDragging())
125     m_pixelsMovement->dropImageTemporarily();
126 
127   m_pixelsMovement->catchImageAgain(gfx::Point(0, 0), MovePixelsHandle);
128   m_pixelsMovement->moveImage(delta, PixelsMovement::NormalMovement);
129   m_pixelsMovement->dropImageTemporarily();
130 }
131 
rotate(double angle)132 void MovingPixelsState::rotate(double angle)
133 {
134   m_pixelsMovement->rotate(angle);
135 }
136 
flip(doc::algorithm::FlipType flipType)137 void MovingPixelsState::flip(doc::algorithm::FlipType flipType)
138 {
139   m_pixelsMovement->flipImage(flipType);
140 }
141 
onEnterState(Editor * editor)142 void MovingPixelsState::onEnterState(Editor* editor)
143 {
144   StandbyState::onEnterState(editor);
145 
146   update_screen_for_document(editor->document());
147 }
148 
onLeaveState(Editor * editor,EditorState * newState)149 EditorState::LeaveAction MovingPixelsState::onLeaveState(Editor* editor, EditorState* newState)
150 {
151   TRACE("MOVPIXS: onLeaveState\n");
152 
153   ASSERT(m_pixelsMovement);
154   ASSERT(editor == m_editor);
155 
156   // If we are changing to another state, we've to drop the image.
157   if (m_pixelsMovement->isDragging())
158     m_pixelsMovement->dropImageTemporarily();
159 
160   // Drop pixels if we are changing to a non-temporary state (a
161   // temporary state is something like ScrollingState).
162   if (!newState || !newState->isTemporalState()) {
163     if (!m_discarded) {
164       try {
165         m_pixelsMovement->dropImage();
166       }
167       catch (const LockedDocException& ex) {
168         // This is one of the worst possible scenarios. We want to
169         // drop pixels because we're leaving this state (e.g. the user
170         // changed the current frame/layer, so we came from
171         // onBeforeFrameChanged) and we weren't able to drop those
172         // pixels.
173         //
174         // TODO this problem should be caught before we reach this
175         // state, or this problem should cancel the frame/layer
176         // change.
177         Console::showException(ex);
178       }
179     }
180 
181     editor->document()->resetTransformation();
182 
183     removePixelsMovement();
184 
185     editor->releaseMouse();
186 
187     // Redraw the document without the transformation handles.
188     editor->document()->notifyGeneralUpdate();
189 
190     return DiscardState;
191   }
192   else {
193     editor->releaseMouse();
194     return KeepState;
195   }
196 }
197 
onActiveToolChange(Editor * editor,tools::Tool * tool)198 void MovingPixelsState::onActiveToolChange(Editor* editor, tools::Tool* tool)
199 {
200   ASSERT(m_pixelsMovement);
201   ASSERT(editor == m_editor);
202 
203   // If the user changed the tool when he/she is moving pixels,
204   // we have to drop the pixels only if the new tool is not selection...
205   if (m_pixelsMovement) {
206     // We don't want to drop pixels in case the user change the tool
207     // for scrolling/zooming/picking colors.
208     if ((!tool->getInk(0)->isSelection() ||
209          !tool->getInk(1)->isSelection()) &&
210         (!tool->getInk(0)->isScrollMovement() ||
211          !tool->getInk(1)->isScrollMovement()) &&
212         (!tool->getInk(0)->isZoom() ||
213          !tool->getInk(1)->isZoom()) &&
214         (!tool->getInk(0)->isEyedropper() ||
215          !tool->getInk(1)->isEyedropper())) {
216       // We have to drop pixels
217       dropPixels();
218     }
219     // If we've temporarily gone to a non-selection tool and now we're
220     // back, we've just to update the context bar to show the "moving
221     // pixels" controls (e.g. OK/Cancel movement buttons).
222     else if (tool->getInk(0)->isSelection() ||
223              tool->getInk(1)->isSelection()) {
224       ContextBar* contextBar = App::instance()->contextBar();
225       contextBar->updateForMovingPixels();
226     }
227   }
228 }
229 
onMouseDown(Editor * editor,MouseMessage * msg)230 bool MovingPixelsState::onMouseDown(Editor* editor, MouseMessage* msg)
231 {
232   ASSERT(m_pixelsMovement);
233   ASSERT(editor == m_editor);
234 
235   // Set this editor as the active one and setup the ContextBar for
236   // moving pixels. This is needed in case that the user is working
237   // with a couple of Editors, in one is moving pixels and the other
238   // one not.
239   UIContext* ctx = UIContext::instance();
240   ctx->setActiveView(editor->getDocView());
241 
242   ContextBar* contextBar = App::instance()->contextBar();
243   contextBar->updateForMovingPixels();
244 
245   // Start scroll loop
246   if (editor->checkForScroll(msg) ||
247       editor->checkForZoom(msg))
248     return true;
249 
250   // Call the eyedropper command
251   tools::Ink* clickedInk = editor->getCurrentEditorInk();
252   if (clickedInk->isEyedropper()) {
253     callEyedropper(editor, msg);
254     return true;
255   }
256 
257   Decorator* decorator = static_cast<Decorator*>(editor->decorator());
258   Doc* document = editor->document();
259 
260   // Transform selected pixels
261   if (document->isMaskVisible() &&
262       decorator->getTransformHandles(editor) &&
263       (!Preferences::instance().selection.modifiersDisableHandles() ||
264        msg->modifiers() == kKeyNoneModifier)) {
265     TransformHandles* transfHandles = decorator->getTransformHandles(editor);
266 
267     // Get the handle covered by the mouse.
268     HandleType handle = transfHandles->getHandleAtPoint(editor,
269                                                         msg->position(),
270                                                         getTransformation(editor));
271 
272     if (handle != NoHandle) {
273       // Re-catch the image
274       m_pixelsMovement->catchImageAgain(
275         editor->screenToEditor(msg->position()), handle);
276 
277       editor->captureMouse();
278       return true;
279     }
280   }
281 
282   // Start "moving pixels" loop. Here we check only for left-click as
283   // right-click can be used to deselect/subtract selection, so we
284   // should drop the selection in this later case.
285   if (editor->isInsideSelection() && msg->left()) {
286     // In case that the user is pressing the copy-selection keyboard shortcut.
287     EditorCustomizationDelegate* customization = editor->getCustomizationDelegate();
288     if ((customization) &&
289         int(customization->getPressedKeyAction(KeyContext::TranslatingSelection) & KeyAction::CopySelection)) {
290       // Stamp the pixels to create the copy.
291       m_pixelsMovement->stampImage();
292     }
293 
294     // Re-catch the image
295     m_pixelsMovement->catchImageAgain(
296       editor->screenToEditor(msg->position()), MovePixelsHandle);
297 
298     editor->captureMouse();
299     return true;
300   }
301   // End "moving pixels" loop
302   else {
303     // Drop pixels (e.g. to start drawing)
304     dropPixels();
305   }
306 
307   // Use StandbyState implementation
308   return StandbyState::onMouseDown(editor, msg);
309 }
310 
onMouseUp(Editor * editor,MouseMessage * msg)311 bool MovingPixelsState::onMouseUp(Editor* editor, MouseMessage* msg)
312 {
313   ASSERT(m_pixelsMovement);
314   ASSERT(editor == m_editor);
315 
316   // Drop the image temporarily in this location (where the user releases the mouse)
317   m_pixelsMovement->dropImageTemporarily();
318 
319   // Redraw the new pivot location.
320   editor->invalidate();
321 
322   editor->releaseMouse();
323   return true;
324 }
325 
onMouseMove(Editor * editor,MouseMessage * msg)326 bool MovingPixelsState::onMouseMove(Editor* editor, MouseMessage* msg)
327 {
328   ASSERT(m_pixelsMovement);
329   ASSERT(editor == m_editor);
330 
331   // If there is a button pressed
332   if (m_pixelsMovement->isDragging()) {
333     // Auto-scroll
334     gfx::Point mousePos = editor->autoScroll(msg, AutoScroll::MouseDir);
335 
336     // Get the position of the mouse in the sprite
337     gfx::Point spritePos = editor->screenToEditor(mousePos);
338 
339     // Get the customization for the pixels movement (snap to grid, angle snap, etc.).
340     KeyContext keyContext = KeyContext::Normal;
341     switch (m_pixelsMovement->handle()) {
342       case MovePixelsHandle:
343         keyContext = KeyContext::TranslatingSelection;
344         break;
345       case ScaleNWHandle:
346       case ScaleNHandle:
347       case ScaleNEHandle:
348       case ScaleWHandle:
349       case ScaleEHandle:
350       case ScaleSWHandle:
351       case ScaleSHandle:
352       case ScaleSEHandle:
353         keyContext = KeyContext::ScalingSelection;
354         break;
355       case RotateNWHandle:
356       case RotateNHandle:
357       case RotateNEHandle:
358       case RotateWHandle:
359       case RotateEHandle:
360       case RotateSWHandle:
361       case RotateSHandle:
362       case RotateSEHandle:
363         keyContext = KeyContext::RotatingSelection;
364         break;
365     }
366 
367     PixelsMovement::MoveModifier moveModifier = PixelsMovement::NormalMovement;
368     KeyAction action = editor->getCustomizationDelegate()
369       ->getPressedKeyAction(keyContext);
370 
371     if (int(action & KeyAction::SnapToGrid))
372       moveModifier |= PixelsMovement::SnapToGridMovement;
373 
374     if (int(action & KeyAction::AngleSnap))
375       moveModifier |= PixelsMovement::AngleSnapMovement;
376 
377     if (int(action & KeyAction::MaintainAspectRatio))
378       moveModifier |= PixelsMovement::MaintainAspectRatioMovement;
379 
380     if (int(action & KeyAction::ScaleFromCenter))
381       moveModifier |= PixelsMovement::ScaleFromPivot;
382 
383     if (int(action & KeyAction::LockAxis))
384       moveModifier |= PixelsMovement::LockAxisMovement;
385 
386     // Invalidate handles
387     Decorator* decorator = static_cast<Decorator*>(editor->decorator());
388     TransformHandles* transfHandles = decorator->getTransformHandles(editor);
389     transfHandles->invalidateHandles(editor, m_pixelsMovement->getTransformation());
390 
391     // Drag the image to that position
392     m_pixelsMovement->moveImage(spritePos, moveModifier);
393 
394     editor->updateStatusBar();
395     return true;
396   }
397 
398   // Use StandbyState implementation
399   return StandbyState::onMouseMove(editor, msg);
400 }
401 
onSetCursor(Editor * editor,const gfx::Point & mouseScreenPos)402 bool MovingPixelsState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
403 {
404   ASSERT(m_pixelsMovement);
405   ASSERT(editor == m_editor);
406 
407   // Move selection
408   if (m_pixelsMovement->isDragging()) {
409     editor->showMouseCursor(kMoveCursor);
410     return true;
411   }
412 
413   // Use StandbyState implementation
414   return StandbyState::onSetCursor(editor, mouseScreenPos);
415 }
416 
onKeyDown(Editor * editor,KeyMessage * msg)417 bool MovingPixelsState::onKeyDown(Editor* editor, KeyMessage* msg)
418 {
419   ASSERT(m_pixelsMovement);
420   if (!isActiveEditor())
421     return false;
422   ASSERT(editor == m_editor);
423 
424   if (msg->scancode() == kKeyEnter || // TODO make this key customizable
425       msg->scancode() == kKeyEnterPad ||
426       msg->scancode() == kKeyEsc) {
427     dropPixels();
428 
429     // The escape key drop pixels and deselect the mask.
430     if (msg->scancode() == kKeyEsc) { // TODO make this key customizable
431       Command* cmd = Commands::instance()->byId(CommandId::DeselectMask());
432       UIContext::instance()->executeCommand(cmd);
433     }
434 
435     return true;
436   }
437 
438   // Use StandbyState implementation
439   return StandbyState::onKeyDown(editor, msg);
440 }
441 
onKeyUp(Editor * editor,KeyMessage * msg)442 bool MovingPixelsState::onKeyUp(Editor* editor, KeyMessage* msg)
443 {
444   ASSERT(m_pixelsMovement);
445   if (!isActiveEditor())
446     return false;
447   ASSERT(editor == m_editor);
448 
449   // Use StandbyState implementation
450   return StandbyState::onKeyUp(editor, msg);
451 }
452 
onUpdateStatusBar(Editor * editor)453 bool MovingPixelsState::onUpdateStatusBar(Editor* editor)
454 {
455   ASSERT(m_pixelsMovement);
456   ASSERT(editor == m_editor);
457 
458   const Transformation& transform(getTransformation(editor));
459   gfx::Size imageSize = m_pixelsMovement->getInitialImageSize();
460 
461   StatusBar::instance()->setStatusText
462     (100, ":pos: %d %d :size: %3d %3d :selsize: %d %d [%.02f%% %.02f%%] :angle: %.1f",
463      int(transform.bounds().x),
464      int(transform.bounds().y),
465      imageSize.w,
466      imageSize.h,
467      int(transform.bounds().w),
468      int(transform.bounds().h),
469      (double)transform.bounds().w*100.0/imageSize.w,
470      (double)transform.bounds().h*100.0/imageSize.h,
471      180.0 * transform.angle() / PI);
472 
473   return true;
474 }
475 
acceptQuickTool(tools::Tool * tool)476 bool MovingPixelsState::acceptQuickTool(tools::Tool* tool)
477 {
478   return
479     (!m_pixelsMovement ||
480      tool->getInk(0)->isSelection() ||
481      tool->getInk(0)->isEyedropper() ||
482      tool->getInk(0)->isScrollMovement() ||
483      tool->getInk(0)->isZoom());
484 }
485 
486 // Before executing any command, we drop the pixels (go back to standby).
onBeforeCommandExecution(CommandExecutionEvent & ev)487 void MovingPixelsState::onBeforeCommandExecution(CommandExecutionEvent& ev)
488 {
489   Command* command = ev.command();
490 
491   TRACE("MOVPIXS: onBeforeCommandExecution %s\n", command->id().c_str());
492 
493   // If the command is for other editor, we don't drop pixels.
494   if (!isActiveEditor())
495     return;
496 
497   // We don't need to drop the pixels if a MoveMaskCommand of Content is executed.
498   if (MoveMaskCommand* moveMaskCmd = dynamic_cast<MoveMaskCommand*>(ev.command())) {
499     if (moveMaskCmd->getTarget() == MoveMaskCommand::Content) {
500       // Do not drop pixels
501       return;
502     }
503   }
504   // Don't drop pixels if the user zooms/scrolls/picks a color
505   // using commands.
506   else if ((command->id() == CommandId::Zoom()) ||
507            (command->id() == CommandId::Scroll()) ||
508            (command->id() == CommandId::Eyedropper()) ||
509            // DiscardBrush is used by Eyedropper command
510            (command->id() == CommandId::DiscardBrush())) {
511     // Do not drop pixels
512     return;
513   }
514   // Intercept the "Cut" or "Copy" command to handle them locally
515   // with the current m_pixelsMovement data.
516   else if (command->id() == CommandId::Cut() ||
517            command->id() == CommandId::Copy() ||
518            command->id() == CommandId::Clear()) {
519     // Copy the floating image to the clipboard on Cut/Copy.
520     if (command->id() != CommandId::Clear()) {
521       Doc* document = m_editor->document();
522       base::UniquePtr<Image> floatingImage;
523       base::UniquePtr<Mask> floatingMask;
524       m_pixelsMovement->getDraggedImageCopy(floatingImage, floatingMask);
525 
526       clipboard::copy_image(floatingImage.get(),
527                             floatingMask.get(),
528                             document->sprite()->palette(m_editor->frame()));
529     }
530 
531     // Clear floating pixels on Cut/Clear.
532     if (command->id() != CommandId::Copy()) {
533       m_pixelsMovement->trim();
534 
535       // Should we keep the mask after an Edit > Clear command?
536       auto keepMask = PixelsMovement::DontKeepMask;
537       if (command->id() == CommandId::Clear() &&
538           Preferences::instance().selection.keepSelectionAfterClear()) {
539         keepMask = PixelsMovement::KeepMask;
540       }
541 
542       // Discard the dragged image.
543       m_pixelsMovement->discardImage(PixelsMovement::CommitChanges, keepMask);
544       m_discarded = true;
545 
546       // Quit from MovingPixelsState, back to standby.
547       m_editor->backToPreviousState();
548     }
549 
550     // Cancel the command, we've simulated it.
551     ev.cancel();
552     return;
553   }
554   // Flip Horizontally/Vertically commands are handled manually to
555   // avoid dropping the floating region of pixels.
556   else if (command->id() == CommandId::Flip()) {
557     if (FlipCommand* flipCommand = dynamic_cast<FlipCommand*>(command)) {
558       m_pixelsMovement->flipImage(flipCommand->getFlipType());
559 
560       ev.cancel();
561       return;
562     }
563   }
564   // Rotate is quite simple, we can add the angle to the current transformation.
565   else if (command->id() == CommandId::Rotate()) {
566     if (RotateCommand* rotate = dynamic_cast<RotateCommand*>(command)) {
567       if (rotate->flipMask()) {
568         m_pixelsMovement->rotate(rotate->angle());
569 
570         ev.cancel();
571         return;
572       }
573     }
574   }
575 
576   if (m_pixelsMovement)
577     dropPixels();
578 }
579 
onDestroyEditor(Editor * editor)580 void MovingPixelsState::onDestroyEditor(Editor* editor)
581 {
582   // TODO we should call ~MovingPixelsState(), we should delete the
583   //      whole "m_statesHistory" when an editor is deleted.
584   removeAsEditorObserver();
585 }
586 
onBeforeFrameChanged(Editor * editor)587 void MovingPixelsState::onBeforeFrameChanged(Editor* editor)
588 {
589   if (!isActiveDocument())
590     return;
591 
592   if (m_pixelsMovement)
593     dropPixels();
594 }
595 
onBeforeLayerChanged(Editor * editor)596 void MovingPixelsState::onBeforeLayerChanged(Editor* editor)
597 {
598   if (!isActiveDocument())
599     return;
600 
601   if (m_pixelsMovement)
602     dropPixels();
603 }
604 
onTransparentColorChange()605 void MovingPixelsState::onTransparentColorChange()
606 {
607   ASSERT(m_pixelsMovement);
608 
609   bool opaque = Preferences::instance().selection.opaque();
610   setTransparentColor(
611     opaque,
612     opaque ?
613       app::Color::fromMask():
614       Preferences::instance().selection.transparentColor());
615 }
616 
onDropPixels(ContextBarObserver::DropAction action)617 void MovingPixelsState::onDropPixels(ContextBarObserver::DropAction action)
618 {
619   if (!isActiveEditor())
620     return;
621 
622   switch (action) {
623 
624     case ContextBarObserver::DropPixels:
625       dropPixels();
626       break;
627 
628     case ContextBarObserver::CancelDrag:
629       m_pixelsMovement->discardImage(PixelsMovement::DontCommitChanges);
630       m_discarded = true;
631 
632       // Quit from MovingPixelsState, back to standby.
633       m_editor->backToPreviousState();
634       break;
635   }
636 }
637 
setTransparentColor(bool opaque,const app::Color & color)638 void MovingPixelsState::setTransparentColor(bool opaque, const app::Color& color)
639 {
640   ASSERT(m_pixelsMovement);
641 
642   Layer* layer = m_editor->layer();
643   ASSERT(layer);
644 
645   try {
646     m_pixelsMovement->setMaskColor(
647       opaque, color_utils::color_for_target_mask(color, ColorTarget(layer)));
648   }
649   catch (const LockedDocException& ex) {
650     Console::showException(ex);
651   }
652 }
653 
dropPixels()654 void MovingPixelsState::dropPixels()
655 {
656   TRACE("MOVPIXS: dropPixels\n");
657 
658   // Just change to default state (StandbyState generally). We'll
659   // receive an onLeaveState() event after this call.
660   m_editor->backToPreviousState();
661 }
662 
getTransformation(Editor * editor)663 Transformation MovingPixelsState::getTransformation(Editor* editor)
664 {
665   // m_pixelsMovement can be null in the final onMouseDown(), after we
666   // called dropPixels() and we're just going to the previous state.
667   if (m_pixelsMovement)
668     return m_pixelsMovement->getTransformation();
669   else
670     return StandbyState::getTransformation(editor);
671 }
672 
isActiveDocument() const673 bool MovingPixelsState::isActiveDocument() const
674 {
675   Doc* doc = UIContext::instance()->activeDocument();
676   return (m_editor->document() == doc);
677 }
678 
isActiveEditor() const679 bool MovingPixelsState::isActiveEditor() const
680 {
681   Editor* targetEditor = UIContext::instance()->activeEditor();
682   return (targetEditor == m_editor);
683 }
684 
removeAsEditorObserver()685 void MovingPixelsState::removeAsEditorObserver()
686 {
687   if (m_observingEditor) {
688     m_observingEditor = false;
689     m_editor->remove_observer(this);
690   }
691 }
692 
removePixelsMovement()693 void MovingPixelsState::removePixelsMovement()
694 {
695   m_pixelsMovement.reset(nullptr);
696   m_ctxConn.disconnect();
697   m_opaqueConn.disconnect();
698   m_transparentConn.disconnect();
699 }
700 
701 } // namespace app
702